首頁 > 軟體

Python格式化字串f-string的使用教學

2022-07-19 14:01:35

楔子

在 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
"""

到此這篇關於Python格式化字串f-string的使用教學的文章就介紹到這了,更多相關Python格式化字串f-string內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com