你真的百分之百了解python中的f-string吗

python

楔子

我们之前在格式化字符串的时候会使用百分号占位符或者format函数,但Python在3.6版本的时候新增了一个格式化字符串的方法,称之为f-string。下面我们就来看看用法。

格式化字符串的方式

我们先来看看之前格式化字符串时,所使用的方式。

name = "古明地觉"

age = 17

where = "东方地灵殿"

# 使用百分号占位符格式化字符串

print("姓名: %s, 年龄: %d, 来自: %s" % (name, age, where)) # 姓名: 古明地觉, 年龄: 17, 来自: 东方地灵殿

# 使用format函数格式化字符串

print("姓名: {}, 年龄: {}, 来自: {}".format(name, age, where)) # 姓名: 古明地觉, 年龄: 17, 来自: 东方地灵殿

# 或者指定关键字也是可以的,这样format函数里面就可以无视顺序了

print("姓名: {name}, 年龄: {age}, 来自: {where}".format(name=name, where=where, age=age))

下面我们再来看看f-string,这算是格式化字符串的一把"瑞士军刀"。

name = "古明地觉"

age = 17

where = "东方地灵殿"

print(f"姓名: {name}, 年龄: {age}, 来自: {where}") # 姓名: 古明地觉, 年龄: 17, 来自: 东方地灵殿

f-string,所以在字符串的前面加上一个f即可。我们知道如果加上r,那么表示 "raw(原生的)",此时字符串里面的反斜杠不具备转义效果。如果加上f,那么此时{}里面的内容则不再是字符串,而是需要单独计算的值或者表达式、或者一个变量。我们再举个例子:

print(f"1 + 1 = {1 + 1}")  # 1 + 1 = 2

print(f"sum([1, 2, 3, 4]) = {sum([1, 2, 3, 4])}") # sum([1, 2, 3, 4]) = 10

try:

print(f"{a}")

except Exception as e:

print(e) # name 'a' is not defined

# 我们说在f-string中,{}里面的内容是需要单独计算的

# 它可以是一个普通的表达式,比如:{1 + 1}或者{2 > 1}等等

# 或者是一个变量,而我们上面的a显然没有定义,所以报错

print(f"{'a'}") # a

# 而f"{'a'}"这种方式是可以的,因为此时{}里面是一个字符串,完全没问题

print(f"{'--'.join(['a', 'b', 'c', 'd'])}") # a--b--c--d

# 或者定义一个变量

a = lambda x: x + 100

print(f"{a}") # <function <lambda> at 0x00000218B9A451F0>

print(f"{a(1)}") # 101

我们看到f-string还是很方便的,关于它的逻辑,我们可以简单地理解为:先将{}里面的内容给它拿出来单独计算,然后再将计算之后的结果放到原来的位置(或者将原来的{xxx}整体替换掉)。所以{}里面可以放任何你想放的内容,只要它可以作为一个右值、或者可以赋值给一个变量,那么它都可以出现在f-string的{}里面。

f-string在功能方面和format类似,都比百分号占位符要丰富的多。但是在性能方面f-string是最优的,因为它不是字符串常量,而是在运行时才会计算的表达式。

支持的格式化

后我们来看看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、然后回车,那么会调用__repr__方法

如果使用print(name),那么不管在什么环境,都会调用__str__方法,等价于print(name.__str__())

"""

# 那么我们需要这个''有什么意义呢?比如:数据库查询

birthday = "1995-07-05"

print("select name from where birthday > %s" % birthday) # select name from where birthday > 1995-07-05

print("select name from where birthday > %r" % birthday) # select name from where birthday > '1995-07-05'

# 看到区别了吗?如果是第一个查询,那么肯定是会报错的。

# 重点来了,如何在f-string中实现这种效果呢?

print(f"{birthday!r}") # '1995-07-05'

print(f"{birthday}") # 1995-07-05

# 我们只需要在打印的内容后面加上一个!r即可

"""

所以: "{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针对的是字符串,虽然也可以作用于整型、不过没有效果

# 另外除了!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'

整型的进制转换

我们在打印整型的时候,需要进制上的转换,这个时候怎么做呢?

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

# 此外我们还可以使用#b、#o、#d、#x

print(f"{i:#b}, {i:#o}, {i:#d}, {i:#x}") # 0b1111011, 0o173, 123, 0x7b

# 另外对于十六进制的x,也可以换成大写

print(f"{i:x}, {i:X}, {i:#x}, {i:#X}") # 7b, 7B, 0x7b, 0X7B

# 当然除了#号,我们还可以使用+、-、以及空格。注意:不可以同时出现,或者一个字符出现多次

"""

+: 显示正负号

-: 负数显示符号、正数不显示

空格: 正数显示空格、负数不显示,只能是一个空格

#: 显示前缀,比如0b、0o、0x

"""

print(f"{i:+x}, {-123:-x}") # +7b, -7b

print(f"{i:-x}, {-123:-x}") # 7b, -7b

print(f"{i: x}, {-123: x}") # 7b, -7b

# 当然,我们知道python在创建整型的时候,还支持使用_进行分隔

print(10_000_00_00) # 100000000

# 同理在f-string中也是可以的,并且除了下划线之外,还可以使用逗号

print(f"{10000000:_d}") # 10_000_000

print(f"{10000000:,d}") # 10,000,000

print(f"{10000000:+_d}") # +10_000_000

print(f"{10000000:+,d}") # +10,000,000

另外需要注意:b、o、d、x这些只用于整型,不能是其它的类型。

print(f"{'aaa':b}")

"""

print(f"{'aaa':b}")

ValueError: Unknown format code 'b' for object of type 'str'

"""

我们之前提到了!r、!a、!s,这些是作用于字符串的,那么它们和b、o、d、x可不可以混用呢?其实,不用想也知道不行,因为前者作用于字符串,后者作用于整型。

print(f"{123}")  # 123

print(f"{123!r}") # 123

# 两个都是123,因为我们说!r可以作用于整型,但是会没有效果

# 但是

print(f"{123:b}") # 1111011

print(f"{123!r:b}")

"""

print(f"{123!r:b}")

ValueError: Unknown format code 'b' for object of type 'str'

"""

# 我们看到print(f"{123!r:b}")报错了,虽然我们说!r作用整型会没有效果,但是已经把它变成字符串了

# 而b针对于整型,不能用于字符串,所以报错

整型的填充和浮点数的小数保留

我们之前使用过这种格式的打印,打印一个整型的时候至少打印3位,比如1的话,就打印001,18则打印018,123则打印本身的123;以及浮点数保留多少位小数等等,这种需求要怎么做呢?

a = 1

# 还记得这个d吗?我们说直接打印的话有它没它无影响

# 但是对于填充的话,它就派上用场了

print(f"{a:03d}") # 001

print(f"{a:013d}") # 0000000000001

"""

填充只能用0或者空格来填充,比如:0123d,表示打印出来要占123个字符,够的话不管了,不够则使用0在左边填充

如果是:123d,它代表的可不是占23位、不够用1填充,它代表的还是占123位,但是由于我们没有指定0,所以默认使用空格在左边填充

"""

print(f"{a:23d}") # 1

print(f"{a:023d}") # 00000000000000000000001

# 当然我们同样可以结合+、-、空格、#

print(f"{b:+08d}") # +0000123

# 可以的话,再将_或者,放进来

print(f"{a:+023_d}") # +00_000_000_000_000_001

print(f"{a:+023,d}") # +00,000,000,000,000,001

# 因此:d前面的必须是"数字",或者"+ - 空格 #"之一, 或者用于分隔的"_ ,"

# 并且它们出现的顺序是:("+ - 空格 #"之一 "数字" "_ ,"之一),当然不需要全部同时出现

# 然后填充的时候,如果数字的第一个是0,那么0后面的表示占多少个字符,不够在左边用0填充

# 如果数字的第一个不是0,那么整体表示输出占的字符个数,不够用空格填充

# 当然,以上规则除了适用于十进制的d,也同样适用于二进制的b、八进制的o、十六进制的x

b = 123

print(f"{b:x}") # 7b

print(f"{b:016x}") # 000000000000007b

# 当然我们同样可以结合+、-、空格、#

print(f"{b:+08d}") # +0000123

print(f"{b:+8d}") # +123

print(f"{b:#018b}") # 0b0000000001111011

print(f"{b:#18b}") # 0b1111011

print(f"{b:#18_b}") # 0b111_1011

# 所以如果带上0b或者+、-等前缀的时候,我们看到填充的时候:

# 如果用0填充,那么会填充在0b、+等前缀的后面,如果是空格,填充在前缀的前面。

# 当然这也符合我们正常人的思维,如果是"+ 123"或者"00000+123"明显觉得别扭

# 而" +123"和"+000000123"则明显顺眼多了

# 下面来看看浮点数的转化

c = 123.13421

# f是保留小数,但是我们没有指定精度,所以默认是小数点后6位,不够6位使用0补齐

print(f"{c:f}") # 123.134210

# .2f则是保留两位小数

print(f"{c:.2f}") # 123.13

# 10.2f也是保留两位小数,然后整体占满10个字符长度,不够的话使用空格在左边填充

print(f"{c:10.2f}") # 123.13

# 如果我们不想使用空格填充的话,那么也可以使用(也只能使用)0来进行填充,规则和整型是类似的

print(f"{c:010.2f}") # 0000123.13

# 当然+、-、空格、#同样可以适用于浮点数,规则也和整型类似

# 如果使用空格填充,那么在+等前缀的前面;如果使用0填充,那么则填充在前缀的后面

print(f"{c:+10.2f}") # +123.13

print(f"{c:+010.2f}") # +000123.13

# 但是#针对于二进制、十进制、十六进制整数的,浮点数没有效果

print(f"{c:#10.2f}") # 123.13

# 同理,浮点数也支持使用下划线或者逗号进行分隔

print(f"{c:#10_.2f}") # 123.13

print(f"{c:#10,.2f}") # 123.13

# 上面由于字符比较少,所以没有分割,我们用0填充一下

print(f"{c:#010_.2f}") # 000_123.13

print(f"{c:#010,.2f}") # 000,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个字符,原始的内容左对齐,长度不够则在右边用空格填充

"""

# 上面的格式,也适用于整型

print(f"~{1:>3}~") # ~ 1~

# 我们看到默认是使用空格填充的,那么可不可以使用指定字符填充呢?

# 答案是可以的, 直接在>、<、^的左边写上用来填充的字符即可,但是只能写一个字符,多了报错

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就不再代表进制了,而是代表的填充字符

只不过,<后面没有指定个数,所以python解释器不知道要填充多少个,因此就原本输出了。

所以此时的这个b既可以作用整型、也可以作用于字符串

"""

print(f"{'aaa':b<}") # aaa

try:

# 如果不是b<,而是b的话

print(f"{'aaa':b}")

except Exception as e:

print(e) # Unknown format code 'b' for object of type 'str'

# 所以很简单,格式就是 变量:填充字符[^><]长度,比如: 123:a>10 表示输出占10位,不够在左边用字符a进行填充

# 我们同样可以使用!r、!a、!s,此时符号也是算在内的

print(f"{'abc'!s:x>10}") # xxxxxxxabc

print(f"{'abc'!r:x>10}") # xxxxx'abc'

print(f"{'abc'!a:x>10}") # xxxxx'abc'

# 但是此时可不可以和代表进制的b、d、o、x使用呢?

# 想想也知道不可以,因为我们说出现了>、^、<的话,其前面的是填充字符,不再代表进制了

try:

print(f"{123:08d>10}")

except Exception as e:

print(e) # Invalid format specifier

# 报错,提示无效的格式化字符,因为>前面出现了3个字符

# 而且这种写法逻辑上也讲不通啊

# 同理浮点数也是,我们这里没有指定精度,这里默认是小数点后6位

print(f"{123.1234:f}") # 123.123400

print(f"{123.1234:.1f}") # 123.1

try:

# 这里我们想保留一位小数,然后整体占10个字符

print(f"{123.1234:.1f>10}")

except Exception as e:

print(e) # Invalid format specifier

# 显示无效的格式化字符,原因还是我们说的,>前面的代表格式化字符,并且只能出现一个字符

# 如果对于浮点数,真的想占满指定长度,那么就只能使用我们之前介绍的下面这种方式

print(f"{123.1234:10.1f}") # 123.1

print(f"{123.1234:010.1f}") # 00000123.1

print(f"{123.1234:+010.1f}") # +0000123.1

# 此外,刚才在介绍浮点型的时候,没有说

# 其实在使用0或者空格填充的时候,也是可以指定^、>、<的,并且要放在最开始的位置

# 之所以放在这里说,主要想介绍完>、<、^再提

print(f"{123.1234:^+010.1f}") # 00+123.100

print(f"{123.1234:>+010.1f}") # 0000+123.1

print(f"{123.1234:<+010.1f}") # +123.10000

# 但是我们看到一旦指定了>、<、^,那么+等前缀会先和数字结合,然后0再填充

# 虽然我们实现了填充,只不过此时只能用0或者空格来填充了,当然我个人觉得已经足够了。浮点型,谁会搞这么多花里胡哨的

日期的截取

f-string还可以进行日期的操作是我没想到的,算是一大亮点吧。我们在格式化或者截取日期的时候,一般会使用datetime.strftime、datetime.date、time、year、month等等,这些也是可以使用f-string来实现的。

import datetime

dt = datetime.datetime(1995, 7, 5, 13, 30, 45, 100000)

print(dt) # 1995-07-05 13:30:45.100000

print(str(dt)) # 1995-07-05 13:30:45.100000

# %F: 返回年月日(使用-连接)

# %D: 返回日月年(使用/连接),但是年是两位的,并且也不符合中国人的日期表达习惯,建议只用%F

print(f"{dt:%F}, {dt:%D}") # 1995-07-05, 07/05/95

# %X: 返回时间,精确到秒(小数点后面的会截断)。这里注意X要大写,如果是%x那么等价于%D

print(f"{dt:%X}") # 13:30:45

# 所以返回字符串格式的完整日期就可以这么写

print(f"{dt:%F} {dt:%X}") # 1995-07-05 13:30:45

# %Y: 返回年(四位) %y: 返回年(两位)

print(f"{dt:%Y}, {dt:%y}") # 1995, 95

# %m: 返回月 %d: 返回天 注意:会占满两位,不够补0

print(f"{dt:%m}, {dt:%d}") # 07, 05

# %H: 返回小时(24小时制度) %I: 返回小时(12小时制度) 注意:会占满两位,不够补0

print(f"{dt:%H}, {dt:%I}") # 13, 01

# %M: 返回分钟 %S: 返回秒 注意:会占满两位,不够补0

print(f"{dt:%M}, {dt:%S}") # 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是周日、6是周六) %u: 星期几(1是周一、7是周日)

# 可以看到两种格式只有星期天不一样

print(f"{dt:%w}, {dt:%u}") # 3, 3

# %U: 一年中的第几周(以全年首个周日所在的星期为第0周,占满两位,不够补0)

# %W: 一年中的第几周(以全年首个周一所在的星期为第1周,占满两位,不够补0)

# %V: 一年中的第几周(以全年首个包含1月4日的星期为第1周,以 0 补足两位)

print(f"{dt:%U}, {dt:%W}, {dt:%V}") # 27, 27, 27

"""

所以如果对应的年的第一天恰好是星期一,那么%U会比%W少1。

如果不是星期一,那么两者是相等的

"""

# 比如2007年的1月1号恰好是星期一

dt = datetime.datetime(2007, 10, 13)

print(f"{dt:%U}, {dt:%W}, {dt:%V}") # 40, 41, 41

# %Z: 返回时区名,如果没有则返回空字符串

print(f"'{dt:%Z}'") # ''

# 这里面的符号还可以连用

print(f"{dt:%F %X}") # 2007-10-13 00:00:00

print(f"{dt:%F %X %y %Y %m}") # 2007-10-13 00:00:00 07 2007 10

f-string的注意事项

使用f-string需要注意单双引号的问题,如果限定字符串使用的是双引号,那么{}里面出现的必须是单引号,反之亦然。

d = {"a": 1}

print(f"{d['a'] + 1}") # 2

# 我们限定字符串的时候使用的是双引号,{}里面必须是单引号,不能是{d["a"] + 1}

# 可能有人好奇,那我使用反斜杠(\) 对里面引号(") 进行转义的话会怎么样呢?

# 答案是不行的,因为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中最好不要出现嵌套的{},可以出现多个{},但是{}里面不要再出现{}。如果需要直接写集合或者字典的话,那么一定要使用括号括起来。

name = "satori"

age = 17

print(f"{({name, age}.pop())}")

# 如果写成f"{{name, age}.pop()}",那么是不符合语法规则的

# 是不会通过编译的

此外,f-string中一定要注意:{}的个数要匹配。

# 下面这段代码如果不使用f-string,那么没有任何问题

print(f"我永远喜欢{古明地觉")

# 但是一旦使用了f-string,那么会报错,因为里面出现了{,但是却没有对应的}。这段代码也不会通过编译

"""

print(f"我永远喜欢{古明地觉")

^

SyntaxError: f-string: expecting '}'

"""

然后是多行的问题了。

a = 17

print(f"age: {a} "

"age: {a}") # age: 17 age: {a}

"""

如果是多行,那么会自动将多行当成是一个字符串

所以每一行都需要f,否则就会得到上面的结果

"""

print(f"age: {a} "

f"age: {a}") # age: 17 age: 17

print(f"""

age: {a},

age: {a}

""")

# 或者使用""",打印如下

"""

age: 17,

age: 17

"""

最后则是lambda表达式的问题。

# 使用lambda表达式的时候一定要使用括号括起来

# 否则会将lambda中的:解释成表达式与格式描述符之间的分隔符

print(f"{(lambda x: x + 123)(123)}") # 246

总结

个人觉得f-string算是Python3.6新增的一大亮点,虽然有着一些限制:比如{和}的个数要匹配,里面不能出现反斜杠之类的。但是个人觉得这都不是什么问题,毕竟在做分词解析的时候肯定是有一些限制的,但总体来说f-string是非常强大的一个工具了。因此在格式化字符串的时候,推荐使用f-string,相信它一定可以在格式化字符串的时候给你提供很大的帮助。

另外使用f-string,甚至可以无需再使用字符串的一些内置方法,比如:ljust、rjust、center,我们完全可以使用<、>、^进行替换,而且更方便。

以上是 你真的百分之百了解python中的f-string吗 的全部内容, 来源链接: utcz.com/z/387927.html

回到顶部