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


    楔子

    我们之前在格式化字符串的时候会使用百分号占位符或者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,我们完全可以使用<、>、^进行替换,而且更方便。

  • 相关阅读:
    Django Admin Cookbook-27如何在Django Admin后台中添加基于日期的过滤
    Django Admin Cookbook-26如何禁用Django Admin后台分页
    Django Admin Cookbook-25如何在模型列表页上显示更多行
    Django Admin Cookbook-24如何从两个不同的模型创建一个Django Admin后台页面
    Django Admin Cookbook-23如何在Django admin中添加嵌套的内联
    Django Admin Cookbook-22如何将一对一关系添加为Admin内联字段
    Django Admin Cookbook-21如何从Django Admin后台一个页面同时编辑多个模型
    个人收集的一些Django基础及实战教程
    Django Admin Cookbook-20如何删除模型的“添加”/“删除”按钮
    操作系统 RR轮转调度算法(C++实现)
  • 原文地址:https://www.cnblogs.com/traditional/p/9217594.html
Copyright © 2020-2023  润新知