看到有的 Python 書上寫『print() 支援參數格式化功能』頗感詫異, 這個格式化功能根本和 print() 八竿子打不著關係, 而是 str 本身格式化字串的功能, 本文就說明幾種格式化功能, 並比較其中的差異。
來自 C 世界的 % 運算器
%
的左邊運算元是字串時, 稱為『插值 (interpolation)』或『格式化 (formatting)』運算器 (operator), 左邊的運算元是可以內含轉換規格 (conversion specifier) 的字串, 右邊運算元則是提供用來置換的資料, %
會依據字串內的轉換規格一一將置換資料代入, 例如:
>>> "圓周率是 %5.2f" % 3.14159
'圓周率是 3.14'
字串中的 '%5.2f' 就是轉換規格, 必須以 '%' 開頭, 接續的 '5' 是所要佔用的最小字元寬度, 也就是轉換後至少會是 5 個字元。小數點後的 '2' 是精確度, 它的意義視要轉換的資料類型而定, 最後的 'f' 表示資料是浮點數, 以浮點數來說, 精確度就是小數的位數。所以此例就是小數 2 位加上小數點本身以及個位數 3 共 4 個字, 轉換後的結果還會在 3 的前面補上一個空格, 滿足至少 5 個字元寬度的轉換規格。
如果要轉換的不只一項資料, 就必須以元素組 (tuple) 作為右邊的運算元, 例如:
>>> "圓周率取 %d 位小數是 %05.2f" % (2, 3.14159)
'圓周率取 2 位小數是 03.14'
此例中 '%d' 的 'd' 表示整數資料, 由於沒有指定最小字元寬度, 所以轉換結果不會填補空白。'%05.2f' 中 '5' 前面的 '0' 表示轉換後不足字元寬度的部分會補 '0', 而不是預設的空格。
以參數指定最小字元寬度或是精確度
最小字元寬度以及精確度也可以由參數傳入, 此時在轉換規格中要改以 '*' 表示寬度或是精確度, 例如底下就以參數指定精確度:
>>> "圓周率取 %d 位小數是 %05.*f" % (2, 2, 3.14159)
'圓周率取 2 位小數是 03.14'
要注意的是傳給 '*' 的參數要放在置換的資料前面。
以字典提供置換資料
你也可以透過以字串為索引鍵的字典物件提供要轉換的資料, 在轉換規格中就在 '%' 後用小括號標註對應元素的索引鍵, 例如:
>>> "圓周率是 %(pi)5.2f" % {'pi': 3.14159}
'圓周率是 3.14'
在字串中放入 '%' 字元
如果你的字串中需要放入 '%' 字元, 就必須以 '%%' 來表示, 像是這樣:
>>> "百分比符號是 %%" % ()
'百分比符號是 %'
如果沒有依循此規則, 會引發例外:
>>> "百分比符號是 %" % ()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: incomplete format
Python 會認為這是不完整的轉換規格。
小結
%
運算器使用的轉換規格來自 C 程式語言的 printf() 函式, 如果想要瞭解完整的轉換規格, 可以參考〈printf() 格式字串的使用方法〉一文, 或是 Python 官網上的〈printf-style String Formatting〉說明文件。
Python 官網上的說明指出, 由於 %
存在一些問題, 因此建議不要使用, 改用接著會介紹的其他方式來建立格式化字串。
str.format() 方法
str 本身就提供有 format()
方法可以建立格式化字串, 它使用大括號來標示轉換規格, 最簡單的用法如下:
>>> "圓周率是 {}".format(3.14159)
'圓周率是 3.14159'
你也可以加上最小字元寬度、精確度與型別, 例如:
>>> "圓周率是 {:5.2f}".format(3.14159)
'圓周率是 3.14'
注意前面必須以半形冒號 ':' 開頭。如果有多項資料, 只要依序傳入即可:
>>> "圓周率取 {:d} 位小數是 {:5.2f}".format(2, 3.14159)
'圓周率取 2 位小數是 3.14'
若有需要, 也可以在左大括號後指定以 0 起算的資料項目序號, 例如:
>>> "圓周率是 {1:5.2f} (小數 {0} 位)".format(2, 3.14159)
'圓周率是 3.14 (小數 2 位)'
這樣一來, 就不一定要依照順序傳入資料, 甚至同一項資料還可以代換多次, 或是以不同格式呈現, 例如:
>>> "圓周率一般以 {2:05.2f} (小數 {0} 位) 或 {2:.4f} (小數 {1} 位)".format(2, 4, 3.14159)
'圓周率一般以 03.14 (小數 2 位) 或 3.1416 (小數 4 位)'
>>>
你可以看到這裡的格式轉換規格和 %
運算器非常類似, 也一樣可以用 '0' 來補足位數。
巢狀置換
你甚至可以巢狀置換, 也就是用傳入的參數來置換轉換規格的內容, 像是用參數來動態變換精確度, 而不是寫死在轉換規格中, 例如:
>>> "圓周率取 {0} 位是 {1:5.{0}f}".format(2, 3.1415976)
'圓周率取 2 位是 3.14'
使用具名參數置換資料
你也可以使用具名參數傳入要轉換的資料, 並在轉換規格中指定參數名稱:
>>> "圓周率是 {pi:05.2f} (小數 {precision} 位)".format(precision=2, pi=3.14159)
'圓周率是 03.14 (小數 2 位)'
具名參數一樣可以進行巢狀置換, 例如:
>>> "圓周率是 {pi:05.{precision}f} (小數 {precision} 位)".format(precision=2, pi=3.14159)
'圓周率是 03.14 (小數 2 位)'
有關完整的轉換規格, 可以參考 Python 官網上〈Format Specification Mini-Language〉一文的說明。
在字串中放入 '{' 或是 '}' 字元
如果需要在字串中放入 '{' 或 '}', 必須以 '{{
' 及 '}}
' 來表示, 例如:
>>> "大括號是 {{}}".format()
'大括號是 {}'
不依此規則會引發例外:
>>> "大括號是 }".format()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: Single '}' encountered in format string
>>> "大括號是 {".format()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: Single '{' encountered in format string
使用 __format__() 方法自訂格式
實際上真正格式化的工作是由個別資料的 __format__()
方法進行, 例如將浮點數依據指定格式轉換成字串的方法如下:
>>> 3.1415976.__format__('5.2f')
' 3.14'
這樣做的好處是每種資料只要負責自己的轉換工作, format()
再將個別的轉換結果串接起來即可, 甚至自訂類別只要實作了 __format__()
方法, 就可以使用在格式化字串中, 以下即是一個簡單的例子:
>>> class MyFloat:
... def __init__(self, f):
... self.f = f
... def __format__(self, format):
... return "val:" + str(self.f)
...
>>> "{}".format(MyFloat(3.1415976))
'val:3.1415976'
F 字串
F 字串的正式名稱是『字面格式字串(formatted string literal)』, 它可以視為 format() 方法的字面描述版, 可以直接用字面描述字串內容以及置換的資料, 例如:
>>> import math
>>> F"圓周率是 {math.pi}"
'圓周率是 3.141592653589793'
開頭的 'F' 也可以改用小寫, 它的轉換規格與 format()
方法幾乎一樣, 但是改成以運算式的運算結果來置換內容, 例如:
>>> F"圓周率取 {precision} 位是 {math.pi:5.2f}"
'圓周率取 2 位是 3.14'
巢狀置換
F 字串同樣可以進行巢狀置換, 像是用另一個運算式來動態指定精確度:
>>> F"圓周率取 {precision} 是 {math.pi:5.{precision}f}"
'圓周率取 2 是 3.14'
在置換結果中顯示運算式
在開發階段, 我們常常會需要顯示特定物件的內容, F 字串還提供有一個便利的功能, 只要在運算式後加上 '=', 就會在轉換結果的開頭加上 '運算式=' 這樣的字首, 便於區別各項資料, 像是這樣:
>>> F"{math.pi=:5.2f}"
'math.pi= 3.14'
如果需要, 也可以在 '=' 前後加上適當的空格, 在輸出時也會原封不動顯示:
>>> F"{precision ** 2 = }"
'precision ** 2 = 4'
使用字典置換資料
由於是使用運算式來指定置換資料, 所以即使是字典內的資料也可以取用, 例如:
>>> d = {"precision":2, "pi":3.1415976}
>>> F"圓周率取 {d['precision']} 位是 {d['pi']:5.2f}"
'圓周率取 2 位是 3.14'
在字串中放入 '{' 或是 '}' 字元
如果需要在字串中放入 '{' 或 '}', 必須以 '{{
' 及 '}}
' 來表示, 例如:
>>> F"大括號是 }}"
'大括號是 }'
>>> F"大括號是 {{"
'大括號是 {'
未遵循此規則一樣會引發例外, 例如:
>>> F"大括號是 {"
File "<stdin>", line 1
F"大括號是 {"
^
SyntaxError: f-string: expecting '}'
>>> F"大括號是 }"
File "<stdin>", line 1
F"大括號是 }"
^
SyntaxError: f-string: single '}' is not allowed
樣板字串 (template string)
在 string 模組中提供有 Template 類別, 可以建立含有置換用佔位器 (substitution placeholder) 的樣板字串, 每個置換用佔位器必須以 '$' 開頭, 並以具名參數的名稱或是字典的索引鍵表示要置換的資料, 例如:
>>> from string import Template
>>> s = Template("圓周率是 $pi")
>>> s.substitute(pi=3.14159)
'圓周率是 3.14159'
>>> s.substitute({'pi':3.14159})
'圓周率是 3.14159'
要注意的是 '$' 之後一定要跟著可以識別資料的名稱, 否則會引發例外, 例如:
>>> s.substitute({'pie':3.14159})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "D:\Program Files\Python\lib\string.py", line 121, in substitute
return self.pattern.sub(convert, self.template)
File "D:\Program Files\Python\lib\string.py", line 114, in convert
return str(mapping[named])
KeyError: 'pi'
如果你希望找不到對應的具名參數或是索引鍵時, 不要引發例外, 可以改用 safe_substitute()
方法, 它會在置換結果中保留原始的置換項目佔位器, 例如:
>>> s.safe_substitute({'pie':3.14159})
'圓周率是 $pi'
如果需要在字串中放入 '\$', 可用 '\$\$' 來表示:
>>> Template("錢幣符號是 $$").substitute({})
'錢幣符號是 $'
如果只用 '\$', 一樣會引發例外:
>>> Template("錢幣符號是 $").substitute({})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "D:\Program Files\Python\lib\string.py", line 121, in substitute
return self.pattern.sub(convert, self.template)
File "D:\Program Files\Python\lib\string.py", line 118, in convert
self._invalid(mo)
File "D:\Program Files\Python\lib\string.py", line 101, in _invalid
raise ValueError('Invalid placeholder in string: line %d, col %d' %
ValueError: Invalid placeholder in string: line 1, col 7
例外的說明文字表示單單一個 '\$' 並不是合乎語法的置換用佔位器。如果希望不要引發例外, 一樣可以使用 safe_substitute()
方法, 它會在轉換結果中保留 '\$':
>>> Template("錢幣符號是 $").safe_substitute({})
'錢幣符號是 $'
嚴格來說, 樣板字串提供的並不是格式化字串, 而只是可動態置換內容的字串。
結語
看完了本文介紹的四種格式化字串的功能後, 以下是我的建議:
-
%
運算器既然官方文件都建議不要使用, 我們可以放生它, 除非你是 C 語言的終極擁護者, 不然我們就當成沒有這個功能。 - 如果你不需要動態變化字串內容, 可以使用 F 字串直接置換後建立所需的字串, 否則
str.format()
應該會是你最好的選擇。 - 如果你並不需要格式化的功能, 只是需要動態置換資料產生新字串, 那麼可以考慮使用樣板字串。
Top comments (1)
很詳盡,推