#473 Python f-string

2021-01-25

以前的字符串格式换方法

1. format 方法格式化

这种方法用的不多(对应 string.Formatter)。

Format String Syntax

replacement_field ::=  "{" [field_name] ["!" conversion] [":" format_spec] "}"

field_name        ::=  arg_name ("." attribute_name | "[" element_index "]")*
arg_name          ::=  [identifier | digit+]
attribute_name    ::=  identifier
element_index     ::=  digit+ | index_string
index_string      ::=  <any source character except "]"> +

conversion        ::=  "r" | "s" | "a"

format_spec       ::=  [[fill]align][sign][#][0][width][grouping_option][.precision][type]
fill              ::=  <any character>
align             ::=  "<" | ">" | "=" | "^"
sign              ::=  "+" | "-" | " "
width             ::=  digit+
grouping_option   ::=  "_" | ","
precision         ::=  digit+
type              ::=  "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%"
print('{} {}'.format('Hello', 'World'))
print('There are three people in my family: {0}, {1}, and I, and I love my {0} a litte more.'.format('father', 'mother'))

2. 模板字符串格式化

这种方法我只在文档中看到,从没真的用过。
PS: 被废弃的 PEP 215 曾建议采用 $'a = $a, b = $b' 这种语法。

Template strings

from string import Template
Template('$who likes $what').substitute(who='tim', what='kung pao')
Template('$who likes $what').safe_substitute({'who': 'time'})

3. 百分号格式化

这应该是现在的最主流的字符串格式化方式。

printf-style String Formatting

print('Hello %s' % 'World')
print('action %s cost %.3f seconds' % ('download', 0.123456789))
print('%(language)s has %(number)03d quote types.' % {'language': "Python", "number": 2})

Python 3.6 新加入 f-string

Formatted string literals

f_string          ::=  (literal_char | "{{" | "}}" | replacement_field)*
replacement_field ::=  "{" f_expression ["="] ["!" conversion] [":" format_spec] "}"
f_expression      ::=  (conditional_expression | "*" or_expr)
                         ("," conditional_expression | "," "*" or_expr)* [","]
                       | yield_expression
conversion        ::=  "s" | "r" | "a"
format_spec       ::=  (literal_char | NULL | replacement_field)*
literal_char      ::=  <any code point except "{", "}" or NULL>

format 方法格式化语法中复用了很多 (格式化和 conversion 部分), 不过变得更强大了。
主要是里面支持条件语句,表达式 (包括 yield)。

a = 3.1415926
f'{a}'
f'{a:.2f}'

f"{1 + 1}", f"{{1 + 1}}", f"{{{1 + 1}}}"
# ('2', '{1 + 1}', '{2}')

注意:f-sting 里面不能使用反斜杠转义!

f'{John\'s}'
# SyntaxError: f-string expression part cannot include a backslash

r, s, a

  • !r -> repr()
  • !s -> str()
  • !a -> ascii()

PS: ascii 方法是 Python 3 引入,和 repr 相似,但是 ascii 方法仅使用 ASCII 字符。
例如:a = '中国'; print(f'{a!a} {ascii(a)}') 输出 '\u4e2d\u56fd' '\u4e2d\u56fd'

print(f'{a!r}')

=

有人提议加入 !d 表示输出表达式本身,然后加上等于号,加上计算值,例如 f'1 + 1!d' => 1 + 1=2
后来实现成了这样:

a = 3.14
b = 1
print(f'a + b=')   # a + b=4.140000000000001
print(f'a + b = ') # a + b = 4.140000000000001

很有趣!

参考资料与拓展阅读

#471 云原生

2021-01-23

网络上各种定义,都说是引用自某个大公司或者知名组织,看得人眼花缭乱、云里雾里。
我还是坚持我一贯的态度,大部分名词都是不同利益团体炒作出来的营销概念,世界不会因这些概念而美好半分,我们应该透过现象看本质。

#470 权力从何而来?

2021-01-21

近日,小区为了一些公共事务举行投票表决,但是搜集的投票人数却一直达不到要求。我所在的业主群楼主就在群里说了这么一段话:

@所有人 我这里有本栋没有投票的名单,截止今天晚上,我这里查到还没有投电子票的,清理出群,以后有什么事情也不用反映给我,我也不会看!!!待在群里发里天天发消息不看,私下发消息也不看,不支持小区改进工作,那你进群来当间谍?如果有填了纸质票的,私发消息给我,以免踢错。
我这个人虽然比较懒,多一事不如少一事,但如果事情和我息息相关还是会积极参与的。不过还有更多人,比我还懒,基本上不怎么关心身边发生的事情,哪怕和自己有关,直到权利被严重侵犯才会有所表示(甚至默默忍受)。对这种现状,我也表示很无奈。参与业委会管理工作的人,面对着无动于衷的业主,事情推动不下去,可能更加着急上火,这种心情我可以理解。不过看到这么一段话,我还是觉得不舒服,感觉就是不太好。

#469 Redis 真是单线程的?

2021-01-20

Redis 核心模块是单线程架构,其高效利用 epoll I/O 多路复用机制实现了单线程高并发。
但是 Redis 服务有很多模块,整体上来说是多线程的。

#467 Redis 客户端缓存

2021-01-17

对于一些热点数据,我们从 Redis 获取之后,常常会在本地线程中也存一份(或者根据语言不同,有些别的第三方组件来专门做这个),也可以有效减小网络消耗。
不过这也算是增加了一级缓存,不可避免的需要涉及数据的同步问题。如果需要比较强的实时性保障,我们就只能放弃采用这个缓存。
可现如今,Redis 能在数据过期数据更新(包括重新设置导致 TTL 变化)之后通知客户端了,这也太贴心了吧。

#466 Golang SMTP Login 认证

2021-01-16

官方库里面只看到 PlainAuth 和 CramMd5Auth 两种认证方法。

type loginAuth struct {
    username, password string
}

func LoginAuth(username, password string) smtp.Auth {
    return &loginAuth{username, password}
}

func (a *loginAuth) Start(server *smtp.ServerInfo) (proto string, toServer []byte, err error) {
    return "LOGIN", []byte{}, nil
}

func (a *loginAuth) Next(fromServer []byte, more bool) (toServer []byte, err error) {
    if more {
        switch strings.ToUpper(string(fromServer)) {
        case "USERNAME:":
            return []byte(a.username), nil
        case "PASSWORD:":
            return []byte(a.password), nil
        default:
            return nil, fmt.Errorf("unknown command %v", fromServer)
        }
    }
    return nil, nil
}

#465 Go SMTP 发送邮件

2021-01-15
package main

import (
    "crypto/tls"
    "errors"
    "fmt"
    "net"
    "net/smtp"
)

// ============================================================================

type Transaction struct {
    Host      string
    Port      uint16
    LocalName string
    TlsEnable bool
    Username  string
    Password  string
    MailFrom  string
    RcptTo    []string
    Data      []byte
}

func NewTransaction() Transaction {
    trans := Transaction{}
    return trans
}

func (trans Transaction) Send() error {
    addr := fmt.Sprintf("%s:%d", trans.Host, trans.Port)

    // SendMail(addr string, a Auth, from string, to []string, msg []byte) error
    c, err := smtp.Dial(addr)
    if err != nil {
        return err
    }
    defer c.Close()

    c.Hello(trans.LocalName)

    if ok, _ := c.Extension("STARTTLS"); ok {
        serverName, _, _ := net.SplitHostPort(addr)
        config := &tls.Config{
            InsecureSkipVerify: true,
            ServerName:         serverName,
        }
        if err = c.StartTLS(config); err != nil {
            return err
        }
    } else {
        fmt.Printf("smtp: server doesn't support STARTTLS\n")
    }

    if trans.Username != "" {
        if ok, _ := c.Extension("AUTH"); !ok {
            return errors.New("smtp: server doesn't support AUTH")

        }
        auth := smtp.PlainAuth("", trans.Username, trans.Password, trans.Host)
        if err = c.Auth(auth); err != nil {
            fmt.Println("smtp: authentication failed")
            return err
        }
    }

    if err = c.Mail(trans.MailFrom); err != nil {
        return err
    }

    for _, addr := range trans.RcptTo {
        if err = c.Rcpt(addr); err != nil {
            return err
        }
    }

    w, err := c.Data()
    if err != nil {
        return err
    }

    _, err = w.Write(trans.Data)
    if err != nil {
        return err
    }

    err = w.Close()
    if err != nil {
        return err
    }

    return c.Quit()
}

// ============================================================================

func main() {
    from := "ninedoors@126.com"
    to := "ninedoors@qq.com"
    msg := []byte{}
    fmt.Println("============================================================")
    trans := Transaction{
        "smtp.126.com", 25, "peach", false,
        from, "password",
        from, []string{to},
        msg,
    }
    trans.Send()
}
  1. net/smtp 没有发现有好的日志实现,我只能定制了一个版本实现了日志
  2. smtp.Client -> textproto.Conn -> textproto.Writer