Python格式化字符串f-string
古明地觉 人气:0楔子
在 Python3.6 之前,格式化字符串一般会使用百分号占位符或者 format 函数,举个例子:
name = "古明地觉" address = "地灵殿" # 使用百分号占位符格式化字符串 print( "我是 %s, 来自 %s" % (name, address) ) # 我是 古明地觉, 来自 地灵殿 # 使用format函数格式化字符串 print( "我是 {}, 来自 {}".format(name, address) ) # 我是 古明地觉, 来自 地灵殿 # format 函数还支持关键字参数 print( "我是 {name}, 来自 {address}".format( address=address, name=name) ) # 我是 古明地觉, 来自 地灵殿
但是从 3.6 开始,Python 新增了一个格式化字符串的方法,称之为 f-string。f-string 的功能非常强大,可以说是一把 "瑞士军刀"。
name = "古明地觉" address = "地灵殿" print( f"我是 {name}, 来自 {address}" ) # 我是 古明地觉, 来自 地灵殿
使用 f-string 需要给字符串增加一个前缀 f,此时 {} 和它里面的内容则不再表示字符串,而是整体作为一个需要单独计算的值或者表达式、或者变量。我们再举个例子:
print(f"1 + 1 = {1 + 1}") # 1 + 1 = 2 print( f"sum([1, 2, 3]) = {sum([1, 2, 3])}" ) # sum([1, 2, 3]) = 6 print( f"{'--'.join(['a', 'b', 'c', 'd'])}" ) # a--b--c--d try: print(f"{a}") except Exception as e: print(e) # name 'a' is not defined # 在f-string中,{} 里面的内容是需要单独计算的 # 可以是常量,比如 {123}、{'hello'} 等等 # 可以是表达式,比如 {1 + 1}、{2 > 1} 等等 # 可以是变量,比如 {a}、{name},但是变量要定义好 # 而我们上面的 a 没有定义,所以报错 # 或者定义一个变量 a = lambda x: x + 100 print(f"{a}") # <function <lambda> at 0x000...> print(f"{a(1)}") # 101
我们看到 f-string 还是很方便的,并且和 format 功能类似,但是性能要优于 format 函数。
当然 f-string 的功能远没有我们介绍的这么简单,它支持的操作非常多,下面就来逐一介绍。
实现 repr 打印
有时候我们在打印的时候需要带上引号。
name = "古明地觉" print(name) # 古明地觉 print("%s" % name) # 古明地觉 # 如果使用 %r 打印,会带上单引号 print("%r" % name) # '古明地觉' # 上面类似于 str 和 repr 的区别 print(str(name)) # 古明地觉 print(repr(name)) # '古明地觉' # 等价于调用 __str__ 和 __repr__ print(name.__str__()) # 古明地觉 print(name.__repr__()) # '古明地觉'
当我们在交互式环境下,不使用 print,而是直接输入变量 name、然后回车,那么会调用 __repr__ 方法。如果使用 print(name),那么不管在什么环境,都会调用 __str__ 方法。
那么在字符串周围带上一层单引号,有什么意义呢?
birthday = "1995-07-05" print("select name from where birthday > %s" % birthday) print("select name from where birthday > %r" % birthday) """ select name from where birthday > 1995-07-05 select name from where birthday > '1995-07-05' """
看到区别了吗?如果是第一个查询,那么肯定是会报错的。重点来了,如何通过 f-string 实现这种效果呢?
birthday = "1995-07-05" # 我们只需要在打印的内容后面加上一个!r即可 print(f"{birthday!r}") # '1995-07-05' print(f"{birthday}") # 1995-07-05 # "{name}" <==> str(name) <==> name.__str__() # "{name!r}" <==> repr(name) <==> name.__repr__() print(f"{'.'.join(['a', 'b', 'c'])}") # a.b.c print(f"{'.'.join(['a', 'b', 'c'])!r}") # 'a.b.c' # 注意:!r针对的是字符串 # 虽然也可以作用于其它对象,不过没有效果 print(f"{123!r}") # 123 print(f"{3.14!r}") # 3.14 # 另外除了 !r,还有 !s 和 !a,只有这三种 # !a 和 !r 类似,!s是默认选择、加不加均可 print(f"{birthday}") # 1995-07-05 print(f"{birthday!s}") # 1995-07-05 print(f"{birthday!a}") # '1995-07-05' print(f"{birthday!r}") # '1995-07-05'
以上就是字符串的 repr 打印。
整数的进制转换
我们在打印整数的时候,有时候需要转成某个进制之后再打印。
i = 123 # 打印 2 进制 print(f"{i:b}") # 1111011 # 打印 8 进制 print(f"{i:o}") # 173 # 打印 10 进制 # 默认是 10 进制,也可以直接使用 {i} print(f"{i:d}") # 123 # 打印 16 进制 print(f"{i:x}") # 7b # 类似于内置函数 bin、oct、hex # 但是这些内置函数调用之后会带上一个前缀 print(bin(i)) # 0b1111011 print(oct(i)) # 0o173 print(hex(i)) # 0x7b # f-string 可不可以实现呢? # 答案是可以的 print(f"{i:#b}") # 0b1111011 print(f"{i:#o}") # 0o173 print(f"{i:#x}") # 0x7b # 对于表示 16 进制的 x,我们还可以将 x 大写 # 此时输出的内容也是大写格式的 print(f"{i:#X}") # 0X7B
另外除了 # 号,我们还可以使用 +、-、以及空格,功能如下:
- +:显示正负号;
- -:负数显示符号、正数不显示;
- 空格:正数显示空格、负数不显示,只能是一个空格;
- #:显示前缀,比如 0b、0o、0x
注意:这几个符号不可混用,并且最多只能出现一次。
print(f"{123:+x}, {-123:+x}") # +7b, -7b print(f"{123:-x}, {-123:-x}") # 7b, -7b print(f"{123: x}, {-123: x}") # 7b, -7b print(f"{123:#x}, {-123:#x}") # 0x7b, -0x7b
另外,Python 在创建整数的时候,还支持使用 _ 进行分隔,但是打印的时候不会将 _ 显示出来。
num = 100_000_000 print(num) # 100000000 # 但如果是 f-string 的话 print(f"{num:_d}") """ 100_000_000 """ print(f"{num:_b}") print(f"{num:#_b}") """ 101_1111_0101_1110_0001_0000_0000 0b101_1111_0101_1110_0001_0000_0000 """ print(f"{num:_o}") print(f"{num:#_o}") """ 5_7536_0400 0o5_7536_0400 """ print(f"{num:_x}") print(f"{num:#_x}") """ 5f5_e100 0x5f5_e100 """ # 只需要在 b、d、o、x 前面加上一个 _ 即可 # 这样打印出来的字符串也会带上 _ 分隔符 # 另外分隔符还可以使用逗号 print(f"{num:,d}") """ 100,000,000 """
注意:b、o、d、x 这些只能用于整数,不能是其它类型的对象。
print(f"{'aaa':b}") """ print(f"{'aaa':b}") ValueError: Unknown format code 'b' for object of type 'str' """
最后再来补充一个字符,整数除了可以使用 b、o、d、x 之外, 还可以使用一个字符,也就是 c。
num = 97 print(chr(num)) # a print(f"{num:c}") # a
以上就是整数的进制转换。
整数的填充
很多时候,打印出来的整数都会进行位数的填充,比如 1 的话,就打印 001,18 则打印 018,123 则打印本身的 123。这种需求,要怎么去处理它呢?
num = 1 # 还记得这个 d 吗? # 我们说直接打印的话,有它没它无影响 # 但是对于填充的话,它就派上用场了 print(f"{num:03d}") # 001 print(f"{num:013d}") # 0000000000001
填充只能用 0 或者空格来填充,比如 0123d,表示打印出来要占 123 个字符,够的话不管了,不够则使用 0 在左边填充。
如果是 123d,它代表的可不是占 23 个字符、不够用 1 填充,它代表的还是占 123 个字符,但是由于我们没有指定 0,所以默认使用空格在左边填充。
# 长度 23,不够使用空格填充 print(f"{1:23d}") # 1 # 长度 23,不够使用 0 填充 print(f"{1:023d}") # 00000000000000000000001 # 当然我们同样可以结合 +、-、空格、# print(f"{1:+08d}") # +0000001 # 可以的话,再将分隔符包含进来 print(f"{1:+023_d}") # +00_000_000_000_000_001 print(f"{1:+023,d}") # +00,000,000,000,000,001
当然,以上规则除了适用于十进制的 d,也同样适用于二进制的 b、八进制的 o、十六进制的 x。
print(f"{123:x}") print(f"{123:016x}") """ 7b 000000000000007b """ # 打印显示正负号,然后占 8 个字符 print(f"{123:+08d}") print(f"{123:+8d}") """ +0000123 +123 """ # 打印的时候带上前缀,占 18 个字符 print(f"{123:#018b}") print(f"{123:#18b}") # 打印的时候带上前缀和分隔符,占 18 个字符 print(f"{123:#18_b}") """ 0b0000000001111011 0b1111011 0b111_1011 """
我们看到填充的时候,如果用 0 填充,那么会填充在 0b、+ 等前缀的后面;如果用空格填充,那么会填充在前缀的前面。当然这也符合我们正常人的思维:
- 如果是 "+ 123" 或者 "00000+123",明显觉得别扭;
- 如果是 " +123" 或者 "+00000123",则明显顺眼多了;
当然工作中我们不会用的这么复杂,知道整数如何填充即可。
浮点数的小数保留
浮点数的小数比较长的话,我们打印的时候一般会只打印前两位或前三位,这在 f-string 里面如何实现呢?
num = 123.13421 # f 是保留小数,但是我们没有指定精度 # 所以默认保留后 6 位,不够用 0 补齐 print(f"{num:f}") """ 123.134210 """ # .2f 则是保留两位小数 print(f"{num:.2f}") """ 123.13 """ # 10.2f 也是保留两位小数 # 然后整体占满 10 个字符长度 # 不够的话使用空格在左边填充 print(f"{num:10.2f}") """ 123.13 """ # 如果我们不想使用空格填充的话 # 那么也可以使用(也只能使用) 0 来进行填充 # 规则和整数是类似的 print(f"{num:010.2f}") """ 0000123.13 """
当然 +、-、空格 同样可以适用于浮点数,规则也和整数类似,同样的,下面这些在工作中也不常用,所以我们知道怎么保留指定位数的小数即可。
num = 123.13421 print(f"{num:+10.2f}") print(f"{num:+010.2f}") """ +123.13 +000123.13 """ # 同理,浮点数也支持使用下划线或者逗号进行分隔 print(f"{num:+10_.2f}") print(f"{num:+10,.2f}") """ +123.13 +123.13 """ # 上面由于有效字符比较少,所以没有分隔符 # 我们用 0 填充一下 print(f"{num:+010_.2f}") print(f"{num:+010,.2f}") """ +00_123.13 +00,123.13 """
以上就是浮点数的小数保留。
任意字符的填充
我们上面介绍的还只是 f-string 的一部分,接下来就是 f-string 的杀手锏。
name = "古明地觉" print(f"~{name:>10}~") print(f"~{name:^10}~") print(f"~{name:<10}~") """ ~ 古明地觉~ ~ 古明地觉 ~ ~古明地觉 ~ """
- >n: 输出的字符串占 n 个字符,原始的内容右对齐,长度不够则在左边用空格填充;
- ^n: 输出的字符串占 n 个字符,原始的内容居中对齐,长度不够则在左右两端用空格填充;
- <n: 输出的字符串占 n 个字符,原始的内容左对齐,长度不够则在右边用空格填充;
还可以将 !r、!s、!a 结合起来使用。
print(f"{'abc'!s:>10}") print(f"{'abc'!r:>10}") print(f"{'abc'!a:>10}") """ abc 'abc' 'abc' """
这些规则也适用于数值:
print(f"{3:>10}") print(f"{3.14:>10}") """ 3 3.14 """
另外默认是使用空格填充的,那么可不可以使用指定字符填充呢?答案是可以的, 直接在 >、<、^ 的左边写上用来填充的字符即可,但是只能写一个字符,多了报错。
print(f"~{'a':1>10}~") # ~111111111a~ print(f"~{'a':1^10}~") # ~1111a11111~ # 使用空格填充,'a': >10 等价于 'a':>10 print(f"~{'a': >10}~") # ~ a~ # 这里我们实现了 {1:03d} 的效果 print(f"{1:0>3}") # 001 print(f"{123:b}") # 1111011 print(f"{123:b<}") # 123 """ 对于 f"{123:b}",里面的 b 表示整数的进制转换 此时只能作用于整数,不能是字符串 但是对于 f"{123:b<},由于里面出现了< 那么此时的 b 就不再代表进制了,而是代表填充字符 只不过 < 后面没有指定个数 所以解释器不知道要填充多少个,因此就原本输出了 """ # 但是 f"{'aaa':b}" 报错 # 因为此时 b 代表进制,无法作用于字符串 print(f"{'aaa':b<}") # aaa print(f"{'aaa':b<4}") # aaab
问题来了,如果我们希望整数在填充的时候,还能进制转化,该怎么做呢?
# 转成十六进制 print(f"{255:x}") """ ff """ # 转成十六进制,带前缀 print(f"{123:#x}") """ 0x7b """ # 转成十六进制,占满10位 # 不够使用字符 s 来左填充 print(f"{123:s>#10x}") """ ssssss0x7b """
浮点数也是类似的,在保留指定位数的同时,也可以进行填充。
num = 123.1234 # 保留一位小数 print(f"{num:.1f}") """ 123.1 """ # 保留一位小数,同时占满 10 位 # 此时只能用 0 或 空格填充 print(f"{num:10.1f}") print(f"{num:010.1f}") """ 123.1 00000123.1 """ # 如果想使用其它字符填充 print(f"{num:s<10.1f}") print(f"{num:s>10.1f}") print(f"{num:s^10.1f}") """ 123.1sssss sssss123.1 ss123.1sss """ # 填充的时候带上正负号 print(f"{num:s<+10.1f}") print(f"{num:s>+10.1f}") print(f"{num:s^+10.1f}") """ +123.1ssss ssss+123.1 ss+123.1ss """ # 填充的时候带上正负号和分隔符 num = 123123123.1234 print(f"{num:s<+20_.1f}") print(f"{num:s>+20_.1f}") print(f"{num:s^+20_.1f}") """ +123_123_123.1ssssss ssssss+123_123_123.1 sss+123_123_123.1sss """
总的来说,f-string 还是非常强大的,但说实话,工作中不会用到这么多花里胡哨的功能。常用的就以下几种:
虽然 f-string 可以写的很复杂,但是工作上几乎不会用到,基本上就是简单的填充、进制转换、保留小数。
很少会有 f"{num:s<+20_.1f}" 这种,保留小数的同时,还要带正负号、以及填充位数的情况出现。
日期的截取
很多小伙伴应该没想到 f-string 还可以操作日期,这也算是一大亮点吧。我们在格式化或者截取日期的时候,一般会使用 datetime 模块,这些也是可以使用 f-string 来实现的。
import datetime dt = datetime.datetime( 1995, 7, 5, 13, 30, 45, 100000) print(dt) """ 1995-07-05 13:30:45.100000 """ # %F: 返回年月日(使用-连接) print(f"{dt:%F}") """ 1995-07-05 """ # %D: 返回日月年(使用/连接),但是年是两位的 # 并且也不符合中国人的日期表达习惯,建议只用 %F print(f"{dt:%D}") """ 07/05/95 """ # %X: 返回时间,精确到秒(小数点后面的会截断) # 这里注意的 X 要大写,如果是 %x 那么等价于 %D print(f"{dt:%X}") """ 13:30:45 """ # 所以返回字符串格式的完整日期就可以这么写 print(f"{dt:%F %X}") """ 1995-07-05 13:30:45 """ # %Y: 返回年(四位) # %y: 返回年(两位) print(f"{dt:%Y}") print(f"{dt:%y}") """ 1995 95 """ # %m: 返回月 # %d: 返回天 # 注意:会占满两位,不够补0 print(f"{dt:%m}") print(f"{dt:%d}") """ 07 05 """ # 所以 %F,我们还可以这么做 # 这些符号是可以连用的 print(f"{dt:%Y-%m-%d}") """ 1995-07-05 """ # %H: 返回小时(24小时制度) # %I: 返回小时(12小时制度) # 注意:会占满两位,不够补0 print(f"{dt:%H}") print(f"{dt:%I}") """ 13 01 """ # %M: 返回分钟 # %S: 返回秒 # 注意:会占满两位,不够补0 print(f"{dt:%M}") print(f"{dt:%S}") """ 30 45 """ # 所以完整的 "年-月-日 时:分:秒" # 就可以这么实现 print(f"{dt:%Y-%m-%d %H:%M:%S}") """ 1995-07-05 13:30:45 """ # %f: 返回微妙 # 注意:会占满六位,不够补0 print(f"{dt:%f}") """ 100000 """ # %p: 早上还是下午(本地时间) # 早上返回 AM、下午返回 PM print(f"{dt:%p}") """ PM """ # %j: 一年中的第几天,从 1 开始,1月1号就是 1 # 注意:会占满三位,不够补 0 print(f"{dt:%j}") """ 186 """ # %w: 星期几(0是周日、1 到 6是周一到周六) # %u: 星期几(1 到 7是周一到周日) # 可以看到两种格式只有星期天不一样 print(f"{dt:%w}") print(f"{dt:%u}") """ 3 3 """ # %U: 一年中的第几周(以全年首个周日所在的星期为第0周) # %W: 一年中的第几周(以全年首个周一所在的星期为第1周) # %V: 一年中的第几周(以全年首个包含1月4日的星期为第1周) # 都是占满两位,不够补 0 print(f"{dt:%U}") print(f"{dt:%W}") print(f"{dt:%V}") """ 27 27 27 """ # 所以如果对应的年的第一天恰好是星期一,那么%U会比%W少1。 # 如果不是星期一,那么两者是相等的 # 比如2007年的1月1号恰好是星期一 dt = datetime.datetime(2007, 10, 13) print(f"{dt:%U}") print(f"{dt:%W}") print(f"{dt:%V}") """ 40 41 41 """ # %Z: 返回时区名,如果没有则返回空字符串 print(f"'{dt:%Z}'") # '' from pytz import timezone dt = datetime.datetime(2007, 10, 13, tzinfo=timezone("UTC")) print(f"'{dt:%Z}'") # 'UTC'
怎么样,是不是很方便呢?以后在做日期的格式化和解析的时候,不妨使用 f-string 试一下。
f-string 的注意事项
使用 f-string 需要注意单双引号的问题,如果限定字符串使用的是双引号,那么 {} 里面出现的必须是单引号,反之亦然。
d = {"a": 1} # 外面是双引号,{} 里面必须是单引号 # 不能是 d["a"] print(f"{d['a'] + 1}") # 2
我们限定字符串的时候使用的是双引号,{} 里面必须是单引号。可能有人好奇,如果里面仍使用双引号,但通过反斜杠 \ 进行转义的话会怎么样呢?
答案是不行的,因为f-string的{}里面不可以出现 \。注意:{} 是不可以出现 \,一个都不可以,所以也不要再想是不是可以使用两个 \ 进行转义啥的。
try: print(f"{\\}") except Exception as e: pass # 我们即便使用异常捕获,也是无用的,依旧会抛出 SyntaxError # 因为 try except 是捕捉运行时的错误 # 而 {} 里面出现反斜杠属于语法上的错误,在编译成字节码阶段就会检测出来 """ print(f"{\\}") ^ SyntaxError: f-string expression part cannot include a backslash """
因此:使用 f-string 同样需要注意单双引号的问题,并且 {} 里面不可以出现反斜杠。如果真的需要反斜杠,那么可以将反斜杠赋值给一个变量,然后将变量传递到 {} 里面去。
a = "\\" print(f"{a}") # \
另外,使用 f-string 时一定要注意:{ 和 } 的个数要匹配。
# 如果不使用 f-string,没有任何问题 # 但是使用了f-string,那么会报错 # 因为里面出现了 { 但是却没有对应的 } # 这段代码不会通过编译 print(f"我永远喜欢{古明地觉") """ print(f"我永远喜欢{古明地觉") ^ SyntaxError: f-string: expecting '}' """
可能有人好奇了,如果我只是想单纯地输入 { 这个字符呢?答案是用两个 { 进行转义。
print(f"我永远喜欢{{古明地觉") """ 我永远喜欢{古明地觉 """ # } 也是同理,需要使用两个 }} 进行转义 print(f"我永远喜欢古明地觉}}") """ 我永远喜欢古明地觉} """ print(f"我永远喜欢{{古明地觉}}") """ 我永远喜欢{古明地觉} """
不过这就又产生了一个问题,如果我希望外面的 {} 表示限定符,里面的 {} 表示集合该怎么办?
name = "古明地觉" # 打印的不是我们想要的结果 print(f"{{name}}") """ {name} """ # 在内部的 {} 周围套上一层小括号即可 print(f"{({name})}") """ {'古明地觉'} """ # 字典也是同理 print(f"{{'name': name}}") print(f"{({'name': name})}") """ {'name': name} {'name': '古明地觉'} """
还有字符串的拼接:
# 等价于 "你好世界" s = "你好" "世界" print(s) # 你好世界 name = "古明地觉" address = "地灵殿" # 每一部分都要带上 f s = f"{name}" f"{address}" print(s) # 古明地觉,地灵殿 s = f"{name}" "{address}" print(s) # 古明地觉{address} # 多行显示也是同理 s = (f"{name}" f"{address}") print(s) # 古明地觉地灵殿
最后则是 lambda 表达式的问题。
# 使用lambda表达式的时候一定要使用括号括起来 # 否则会将lambda中的:解释成表达式与格式描述符之间的分隔符 print(f"{(lambda x: x + 123)(123)}") # 246
小结
个人觉得 f-string 算是 Python3.6 新增的一大亮点,虽然有着一些限制,但是这都不是什么问题,毕竟在做分词解析的时候肯定是有一些限制的,但总体来说 f-string 是非常强大的一个工具了。
因此在格式化字符串的时候,推荐使用f-string,相信它一定可以在格式化字符串的时候给你提供很大的帮助。
对了,再补充一点,在 3.8 的时候给 f-string 增加了一个功能:
num = 123 print(f"{num=}") print(f"{num =}") print(f"{num = }") """ num=123 num =123 num = 123 """ # 可以看到加上了 = # 还会将 {} 里面的内容输出出来 # 像我们之前的例子 print(f"sum([1, 2, 3]) = {sum([1, 2, 3])}") """ sum([1, 2, 3]) = 6 """ # 加上 = 就会简洁很多 print(f"{sum([1, 2, 3]) = }") """ sum([1, 2, 3]) = 6 """ print(f"{1 + 1 = }") """ 1 + 1 = 2 """ print(f"{len('古明地觉') = }") """ len('古明地觉') = 4 """
加载全部内容