楔子
在 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}") #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 个字符,原始的内容居中对齐,长度不够则在左右两端用空格填充;
还可以将 !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 """
到此这篇关于Python格式化字符串f-string的使用教程的文章就介绍到这了,更多相关Python格式化字符串f-string内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!