DEV Community

codemee
codemee

Posted on • Edited on

4 2

Python 的格式化字串功能

看到有的 Python 書上寫『print() 支援參數格式化功能』頗感詫異, 這個格式化功能根本和 print() 八竿子打不著關係, 而是 str 本身格式化字串的功能, 本文就說明幾種格式化功能, 並比較其中的差異。

來自 C 世界的 % 運算器

% 的左邊運算元是字串時, 稱為『插值 (interpolation)』或『格式化 (formatting)』運算器 (operator), 左邊的運算元是可以內含轉換規格 (conversion specifier) 的字串, 右邊運算元則是提供用來置換的資料, % 會依據字串內的轉換規格一一將置換資料代入, 例如:

>>> "圓周率是 %5.2f" % 3.14159
'圓周率是  3.14'
Enter fullscreen mode Exit fullscreen mode

字串中的 '%5.2f' 就是轉換規格, 必須以 '%' 開頭, 接續的 '5' 是所要佔用的最小字元寬度, 也就是轉換後至少會是 5 個字元。小數點後的 '2' 是精確度, 它的意義視要轉換的資料類型而定, 最後的 'f' 表示資料是浮點數, 以浮點數來說, 精確度就是小數的位數。所以此例就是小數 2 位加上小數點本身以及個位數 3 共 4 個字, 轉換後的結果還會在 3 的前面補上一個空格, 滿足至少 5 個字元寬度的轉換規格。

如果要轉換的不只一項資料, 就必須以元素組 (tuple) 作為右邊的運算元, 例如:

>>> "圓周率取 %d 位小數是 %05.2f" % (2, 3.14159)
'圓周率取 2 位小數是 03.14'
Enter fullscreen mode Exit fullscreen mode

此例中 '%d' 的 'd' 表示整數資料, 由於沒有指定最小字元寬度, 所以轉換結果不會填補空白。'%05.2f' 中 '5' 前面的 '0' 表示轉換後不足字元寬度的部分會補 '0', 而不是預設的空格。

以參數指定最小字元寬度或是精確度

最小字元寬度以及精確度也可以由參數傳入, 此時在轉換規格中要改以 '*' 表示寬度或是精確度, 例如底下就以參數指定精確度:

>>> "圓周率取 %d 位小數是 %05.*f" % (2, 2, 3.14159)
'圓周率取 2 位小數是 03.14'
Enter fullscreen mode Exit fullscreen mode

要注意的是傳給 '*' 的參數要放在置換的資料前面。

以字典提供置換資料

你也可以透過以字串為索引鍵的字典物件提供要轉換的資料, 在轉換規格中就在 '%' 後用小括號標註對應元素的索引鍵, 例如:

>>> "圓周率是 %(pi)5.2f" % {'pi': 3.14159}
'圓周率是  3.14'
Enter fullscreen mode Exit fullscreen mode

在字串中放入 '%' 字元

如果你的字串中需要放入 '%' 字元, 就必須以 '%%' 來表示, 像是這樣:

>>> "百分比符號是 %%" % ()
'百分比符號是 %'
Enter fullscreen mode Exit fullscreen mode

如果沒有依循此規則, 會引發例外:

>>> "百分比符號是 %" % ()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: incomplete format
Enter fullscreen mode Exit fullscreen mode

Python 會認為這是不完整的轉換規格。

小結

% 運算器使用的轉換規格來自 C 程式語言的 printf() 函式, 如果想要瞭解完整的轉換規格, 可以參考〈printf() 格式字串的使用方法〉一文, 或是 Python 官網上的〈printf-style String Formatting〉說明文件。

Python 官網上的說明指出, 由於 % 存在一些問題, 因此建議不要使用, 改用接著會介紹的其他方式來建立格式化字串。

str.format() 方法

str 本身就提供有 format() 方法可以建立格式化字串, 它使用大括號來標示轉換規格, 最簡單的用法如下:

>>> "圓周率是 {}".format(3.14159)
'圓周率是 3.14159'
Enter fullscreen mode Exit fullscreen mode

你也可以加上最小字元寬度、精確度與型別, 例如:

>>> "圓周率是 {:5.2f}".format(3.14159)
'圓周率是  3.14'
Enter fullscreen mode Exit fullscreen mode

注意前面必須以半形冒號 ':' 開頭。如果有多項資料, 只要依序傳入即可:

>>> "圓周率取 {:d} 位小數是 {:5.2f}".format(2, 3.14159)
'圓周率取 2 位小數是  3.14'
Enter fullscreen mode Exit fullscreen mode

若有需要, 也可以在左大括號後指定以 0 起算的資料項目序號, 例如:

>>> "圓周率是 {1:5.2f} (小數 {0} 位)".format(2, 3.14159)
'圓周率是  3.14 (小數 2 位)'
Enter fullscreen mode Exit fullscreen mode

這樣一來, 就不一定要依照順序傳入資料, 甚至同一項資料還可以代換多次, 或是以不同格式呈現, 例如:

>>> "圓周率一般以 {2:05.2f} (小數 {0} 位) 或 {2:.4f} (小數 {1} 位)".format(2, 4, 3.14159)
'圓周率一般以 03.14 (小數 2 位) 或 3.1416 (小數 4 位)'
>>>
Enter fullscreen mode Exit fullscreen mode

你可以看到這裡的格式轉換規格和 % 運算器非常類似, 也一樣可以用 '0' 來補足位數。

巢狀置換

你甚至可以巢狀置換, 也就是用傳入的參數來置換轉換規格的內容, 像是用參數來動態變換精確度, 而不是寫死在轉換規格中, 例如:

>>> "圓周率取 {0} 位是 {1:5.{0}f}".format(2, 3.1415976)
'圓周率取 2 位是  3.14'
Enter fullscreen mode Exit fullscreen mode

使用具名參數置換資料

你也可以使用具名參數傳入要轉換的資料, 並在轉換規格中指定參數名稱:

>>> "圓周率是 {pi:05.2f} (小數 {precision} 位)".format(precision=2, pi=3.14159)
'圓周率是 03.14 (小數 2 位)'
Enter fullscreen mode Exit fullscreen mode

具名參數一樣可以進行巢狀置換, 例如:

>>> "圓周率是 {pi:05.{precision}f} (小數 {precision}  位)".format(precision=2, pi=3.14159)
'圓周率是 03.14 (小數 2 位)'
Enter fullscreen mode Exit fullscreen mode

有關完整的轉換規格, 可以參考 Python 官網上〈Format Specification Mini-Language〉一文的說明。

在字串中放入 '{' 或是 '}' 字元

如果需要在字串中放入 '{' 或 '}', 必須以 '{{' 及 '}}' 來表示, 例如:

>>> "大括號是 {{}}".format()
'大括號是 {}'
Enter fullscreen mode Exit fullscreen mode

不依此規則會引發例外:

>>> "大括號是 }".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
Enter fullscreen mode Exit fullscreen mode

使用 __format__() 方法自訂格式

實際上真正格式化的工作是由個別資料的 __format__() 方法進行, 例如將浮點數依據指定格式轉換成字串的方法如下:

>>> 3.1415976.__format__('5.2f')
' 3.14'
Enter fullscreen mode Exit fullscreen mode

這樣做的好處是每種資料只要負責自己的轉換工作, 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'
Enter fullscreen mode Exit fullscreen mode

F 字串

F 字串的正式名稱是『字面格式字串(formatted string literal)』, 它可以視為 format() 方法的字面描述版, 可以直接用字面描述字串內容以及置換的資料, 例如:

>>> import math
>>> F"圓周率是 {math.pi}"
'圓周率是 3.141592653589793'
Enter fullscreen mode Exit fullscreen mode

開頭的 'F' 也可以改用小寫, 它的轉換規格與 format() 方法幾乎一樣, 但是改成以運算式的運算結果來置換內容, 例如:

>>> F"圓周率取 {precision} 位是 {math.pi:5.2f}"
'圓周率取 2 位是  3.14'
Enter fullscreen mode Exit fullscreen mode

巢狀置換

F 字串同樣可以進行巢狀置換, 像是用另一個運算式來動態指定精確度:

>>> F"圓周率取 {precision}{math.pi:5.{precision}f}"
'圓周率取 2 是  3.14'
Enter fullscreen mode Exit fullscreen mode

在置換結果中顯示運算式

在開發階段, 我們常常會需要顯示特定物件的內容, F 字串還提供有一個便利的功能, 只要在運算式後加上 '=', 就會在轉換結果的開頭加上 '運算式=' 這樣的字首, 便於區別各項資料, 像是這樣:

>>> F"{math.pi=:5.2f}"
'math.pi= 3.14'
Enter fullscreen mode Exit fullscreen mode

如果需要, 也可以在 '=' 前後加上適當的空格, 在輸出時也會原封不動顯示:

>>> F"{precision ** 2 = }"
'precision ** 2 = 4'
Enter fullscreen mode Exit fullscreen mode

使用字典置換資料

由於是使用運算式來指定置換資料, 所以即使是字典內的資料也可以取用, 例如:

>>> d = {"precision":2, "pi":3.1415976}
>>> F"圓周率取 {d['precision']} 位是 {d['pi']:5.2f}"
'圓周率取 2 位是  3.14'
Enter fullscreen mode Exit fullscreen mode

在字串中放入 '{' 或是 '}' 字元

如果需要在字串中放入 '{' 或 '}', 必須以 '{{' 及 '}}' 來表示, 例如:

>>> F"大括號是 }}"
'大括號是 }'
>>> F"大括號是 {{"
'大括號是 {'
Enter fullscreen mode Exit fullscreen mode

未遵循此規則一樣會引發例外, 例如:

>>> F"大括號是 {"
  File "<stdin>", line 1
    F"大括號是 {"
             ^
SyntaxError: f-string: expecting '}'
>>> F"大括號是 }"
  File "<stdin>", line 1
    F"大括號是 }"
             ^
SyntaxError: f-string: single '}' is not allowed
Enter fullscreen mode Exit fullscreen mode

樣板字串 (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'
Enter fullscreen mode Exit fullscreen mode

要注意的是 '$' 之後一定要跟著可以識別資料的名稱, 否則會引發例外, 例如:

>>> 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'
Enter fullscreen mode Exit fullscreen mode

如果你希望找不到對應的具名參數或是索引鍵時, 不要引發例外, 可以改用 safe_substitute() 方法, 它會在置換結果中保留原始的置換項目佔位器, 例如:

>>> s.safe_substitute({'pie':3.14159})
'圓周率是 $pi'
Enter fullscreen mode Exit fullscreen mode

如果需要在字串中放入 '\$', 可用 '\$\$' 來表示:

>>> Template("錢幣符號是 $$").substitute({})
'錢幣符號是 $'
Enter fullscreen mode Exit fullscreen mode

如果只用 '\$', 一樣會引發例外:

>>> 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
Enter fullscreen mode Exit fullscreen mode

例外的說明文字表示單單一個 '\$' 並不是合乎語法的置換用佔位器。如果希望不要引發例外, 一樣可以使用 safe_substitute() 方法, 它會在轉換結果中保留 '\$':

>>> Template("錢幣符號是 $").safe_substitute({})
'錢幣符號是 $'
Enter fullscreen mode Exit fullscreen mode

嚴格來說, 樣板字串提供的並不是格式化字串, 而只是可動態置換內容的字串。

結語

看完了本文介紹的四種格式化字串的功能後, 以下是我的建議:

  • % 運算器既然官方文件都建議不要使用, 我們可以放生它, 除非你是 C 語言的終極擁護者, 不然我們就當成沒有這個功能。
  • 如果你不需要動態變化字串內容, 可以使用 F 字串直接置換後建立所需的字串, 否則 str.format() 應該會是你最好的選擇。
  • 如果你並不需要格式化的功能, 只是需要動態置換資料產生新字串, 那麼可以考慮使用樣板字串

Top comments (1)

Collapse
 
wastu01 profile image
wa.code

很詳盡,推

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more