目錄
1. C風格的字串格式化方式
2. 內建format函式與str.format方法
3. f-字串
總結:
在微信公眾號「極客起源」中輸入595586,可學習全部的《Python高效編程之88條軍規》系列文章,
在Python語言中,字串有多種用途,可以用于在用戶界面和命令列實用程式中顯示訊息;用于用于將資料寫入檔案和Socket;用于指定“例外”訊息;用于除錯程式,
格式化是將預定義的文本和資料組合成一條人類可讀的訊息的程序,Python具有4種不同的格式化字串的方式,這4種方式有的是語言層面支持的,有的是通過標準庫支持的,除其中一種方式外,其他的格式化方式都有嚴重的缺點,在使用時應該盡量避免這些缺陷,
1. C風格的字串格式化方式
在Python語言中格式化字串的最常見方法是使用%格式化運算子,預定義的文本模板以格式字串的形式放在%運算子的左側,要插入模板的資料在%運算子的右側,這些資料可以是單個值,也可以是一個元組(不能是串列),表示將多個值插入模板,例如,在這里我使用%運算子將難以閱讀的二進制和十六進制值轉換為整數字串:
a = 0b10111010
b = 0xc5c
print('二進制:%d, 十六行程:%d' % (a, b))
執行這段代碼,會輸出如下內容:
二進制:186, 十六行程:3164
格式字串使用格式說明符(如%d)作為占位符,這些占位符將被%運算子右側的值替換,格式說明符的語法來自C語言的printf函式,該函式已被Python(以及其他編程語言)繼承,Python支持所有常用的printf函式格式化選項,例如%s,%x和%f格式說明符,以及對小數位,填充,填充和對齊的控制,許多不熟悉Python的程式員都以C風格的格式字串開頭,因為它們熟悉且易于使用,
但是使用C風格的格式化字串方式,會帶來如下4個問題:
問題1:
如果更改格式運算式右側的元組中資料值的型別或順序,可能會由于型別轉換不兼容而拋出例外,例如,這個簡單的格式運算式可以作業:
key = 'my_key'
value = 1.234
formatted = '%-10s = %.2f' % (key, value)
print(formatted)
執行這段代碼,會輸出如下內容:
my_key = 1.23
但如何交換key和value的值,那將會拋出運行時例外:
key = 1.234
value = 'my_key'
formatted = '%-10s = %.2f' % (key, value)
print(formatted)
執行這段代碼,會拋出如下例外:
Traceback (most recent call last):
File "/python/format.py", line 12, in <module>
formatted = '%-10s = %.2f' % (key, value)
TypeError: must be real number, not str
類似地,如果%右側元組中值的順序變化后,同樣會拋出例外,
formatted = '%-10s = %.2f' % (key, value)
為了避免這種麻煩,你需要不斷檢查%運算子的兩側的資料型別是否匹配;此程序容易出錯,因為每次修改代碼,都必須人工檢測資料型別是否匹配,
問題2:
C風格格式化運算式的第2個問題是當你需要在將值格式化為字串之前對值進行小的修改時,它們將變得難以閱讀,這是非常普遍的需求,在這里,我列出了廚房儲藏室的內容,而沒有進行行內更改:
pantry = [
('avocados', 1.25),
('bananas', 2.5),
('cherries', 15),
]
for i, (item, count) in enumerate(pantry):
print('#%d: %-10s = %.2f' % (i, item, count))
執行這段代碼,會輸出如下的結果:
#0: avocados = 1.25
#1: bananas = 2.50
#2: cherries = 15.00
現在,我對要格式化的值進行了一些修改,以便列印出更有用的資訊,這導致格式化運算式中的元組變得太長,以至于需要將其分成多行,這會損害程式的可讀性:
for i, (item, count) in enumerate(pantry):
print('#%d: %-10s = %d' % (
i + 1,
item.title(),
round(count)))
執行這段代碼,會輸出如下的內容:
#1: Avocados = 1
#2: Bananas = 2
#3: Cherries = 15
問題3:
格式化運算式的第3個問題是如果要在格式字串中多次使用相同的值,則必須在右側重復該值多次:
template = '%s loves food. See %s cook.'
name = 'Max'
formatted = template % (name, name)
print(formatted)
執行這段代碼,會輸出如下的內容:
Max loves food. See Max cook.
如果需要對這些重復的值做一些小的修改,這將特別令人討厭的事,而且非常容易出錯,為了解決這個問題,推薦使用字典取代元組為格式化字串提供資料,參考字典中值的方式是%(key),看下面的例子:
old_way = '%-10s , %.2f, %-8s' % (key, value,key) # 重復指定key
new_way = '%(key)-10s , %(value).2f, %(key)-8s' % {
'key': key, 'value': value} # 只需要指定一次key
print(old_way)
print(new_way)
執行這段代碼,會輸出如下的內容:
key1 , 1.13, key1
key1 , 1.13, key1
我們可以看到,如果需要重復參考%右側的值,在使用元組的情況下,需要重復指定這些值,如本例中的key,而使用字典,只需要指定一次key就可以了,
然后,使用字典格式化字串會引入并加劇其他問題,對于上面的問題2,由于在格式化之前對值進行了小的修改,由于%運算子右側存在鍵和冒號運算子,因此格式化運算式變得更長,并且在視覺上更加雜亂,在下面的代碼中,我分別使用字典和不使用指點來格式化相同的字串以說明此問題:
for i, (item, count) in enumerate(pantry):
before = '#%d: %-10s = %d' % (
i + 1,
item.title(),
round(count))
after = '#%(loop)d: %(item)-10s = %(count)d' % {
'loop': i + 1,
'item': item.title(),
'count': round(count),
}
assert before == after
問題4:
使用字典格式化字串還會帶了第4個問題,就是每個鍵必須至少指定兩次:在格式說明符中指定一次,另一次是在字典中指定為鍵,如果字典值本身是一個變數,也需要再次指定,
soup = 'lentil'
formatted = 'Today\'s soup is %(soup)s.' % {'soup': soup} # 這里再次指定了變數soup
print(formatted)
輸出結果如下:
Today's soup is lentil.
除了重復字符之外,這種冗余還會導致使用字典的格式化運算式很長,這些運算式通常必須跨多行,格式字串跨多行連接,并且字典賦值每個值只有一行用于格式化:
menu = {
'soup': 'lentil',
'oyster': 'kumamoto',
'special': 'schnitzel',
}
template = ('Today\'s soup is %(soup)s, '
'buy one get two %(oyster)s oysters, '
'and our special entrée is %(special)s.')
formatted = template % menu
print(formatted)
輸出結果如下:
Today's soup is lentil, buy one get two kumamoto oysters, and our special entrée is schnitzel.
由于格式化字串很長,可能會跨多行,所以要想了解整個字串想表達什么,你的眼鏡必須上下左右來回移動,而且很容易忽略本應該發現的錯誤,那么是否有更好的格式化字串的解決方案呢?請繼續往下看:
2. 內建format函式與str.format方法
Python 3添加了對高級字串格式化的支持,這種格式化方式比使用%運算子的C風格格式化字串更具表現力,對于單獨的值,可以通過格式化內建函式來訪問此新功能,例如,下面的代碼使用一些新選項(,用于千分位分隔符,使用^用于居中)來格式化值:
a = 1234.5678
formatted = format(a, ',.2f')
print(formatted)
b = 'my string'
formatted = format(b, '^20s') # 居中顯示字串
print('*', formatted, '*')
運行結果如下:
1,234.57
* my string *
您可以通過呼叫字串的format方法來格式化多個值,format方法使用{}作為占位符,而不是使用%d這樣的C風格格式說明符,在默認情況下,格式化字串中的占位符按著它們出現的順序傳遞給format方法相應位置的占位符,
key = 'my_var'
value = 1.234
formatted = '{} = {}'.format(key, value)
print(formatted)
運行結果如下:
my_var = 1.234
每個占位符內可以在冒號(:)后面指定格式化說明符,用來指定將值轉換為字串的方式,代碼如下:
formatted = '{:<10} = {:.2f}'.format(key, value)
print(formatted)
運行結果如下:
my_var = 1.23
format方法的作業原理是將格式化說明符與值(上例中的format(value,'.2f'))一起傳遞給內建函式format,然后將 該函式的回傳值替換對應的占位符,可以使用__format__方法針對每個類自定義格式化行為,
對于C風格的格式化字串,需要對%運算子進行轉換轉義,也就是寫兩個%,以免被誤認為是占位符,使用str.format方法,也需要對花括號進行轉義,
print('%.2f%%' % 12.5)
print('{} replaces {{}}'.format(1.23))
輸出結果如下:
12.50%
1.23 replaces {}
在花括號內還可以指定傳遞給format方法的引數的位置索引,以用于替換占位符,這允許在不更改format方法傳入值順序的情況下,更改格式化字串中占位符的順序,
formatted = '{1} = {0}'.format(key, value)
print(formatted)
輸出結果如下所示:
1.234 = my_var
使用位置索引還有一個好處,就是在格式化字串中要多次參考某個值時,只需要通過format方法傳遞一個值即可,在格式化字串中可以使用同一個位置索引參考多次這個值,
formatted = '{0} loves food. See {0} cook.'.format(name)
print(formatted)
輸出結果如下:
Max loves food. See Max cook.
不幸的是,format方法無法解決上面的問題2,所以在格式化之前需要對值進行小的修改時比較費勁(因為需要對齊引數的位置),下面的代碼是將%運算子和format方法在一起進行比較,其實同時同樣不容易閱讀,
for i, (item, count) in enumerate(pantry):
old_style = '#%d: %-10s = %d' % (
i + 1,
item.title(),
round(count))
new_style = '#{}: {:<10s} = {}'.format(
i + 1,
item.title(),
round(count))
assert old_style == new_style
盡管format方法使用的格式化說明符還有更多高級選項,例如在占位符中使用字典鍵和串列索引的組合,以及將值強制轉換為Unicode和repr字串:
formatted = 'First letter is {menu[oyster][0]!r}'.format(
menu=menu)
print(formatted)
運行結果如下:
First letter is 'k'
但是這些功能并不能幫助減少上述問題4中重復key的冗余性,例如,在這里,我將在C風格格式化運算式中使用字典的冗長性與將key引數傳遞給format方法的新樣式進行了比較:
old_template = (
'Today\'s soup is %(soup)s, '
'buy one get two %(oyster)s oysters, '
'and our special entrée is %(special)s.')
old_formatted = template % {
'soup': 'lentil',
'oyster': 'kumamoto',
'special': 'schnitzel',
}
new_template = (
'Today\'s soup is {soup}, '
'buy one get two {oyster} oysters, '
'and our special entrée is {special}.')
new_formatted = new_template.format(
soup='lentil',
oyster='kumamoto',
special='schnitzel',
)
assert old_formatted == new_formatted
這種樣式的噪音較小,因為它消除了詞典中的一些引號和格式化說明符中的一些字符,但是并沒有達到完美的程度,此外,在占位符中使用字典鍵和索引的高級功能僅提供了Python運算式功能的一小部分,這種缺乏表現力的局限性使得它從總體上破壞了format方法的價值,
考慮到這些缺點以及仍然存在C風格格式化運算式的問題(上面的問題2和問題4),我的建議是盡量避免使用str.format方法,了解格式化說明符(冒號之后的所有內容)中使用的新的迷你語言以及如何使用格式內置功能是非常重要的,
3. f-字串
Python 3.6添加了插值格式化字串(簡稱f字串)來徹底解決這些問題,這種新的語言語法要求您以f字符作為格式字串的前綴,這類似于位元組字串以b字符作為前綴,以及原始(未轉義的)字串以r字符作為前綴,
f-字串將格式字串的表現力發揮到極致,通過完全消除提供要格式化的鍵和值的冗余性,完全解決了問題4,它們通過允許您參考當前Python范圍中的所有變數作為格式化運算式的一部分來實作這一點:
key = 'my_var'
value = 1.234
formatted = f'{key} = {value}'
print(formatted)
輸出結果如下:
my_var = 1.234
格式化的內置迷你語言中的所有相同選項都可以在f-字串內占位符后的冒號后面使用,也可以類似于str.format方法將值強制轉換為Unicode和repr字串:
formatted = f'{key!r:<10} = {value:.2f}'
print(formatted)
輸出結果如下:
'my_var' = 1.23
在所有情況下,使用f-字串進行格式化比使用帶有%運算子和str.format方法的C風格格式化字串進行格式化要短,在這里,我按照最短到最長的順序顯示了所有這些格式化方式,以便您可以輕松進行比較:
f_string = f'{key:<10} = {value:.2f}'
c_tuple = '%-10s = %.2f' % (key, value)
str_args = '{:<10} = {:.2f}'.format(key, value)
str_kw = '{key:<10} = {value:.2f}'.format(key=key,
value=value)
c_dict = '%(key)-10s = %(value).2f' % {'key': key,
'value': value}
print(f'f_string:{f_string}')
print(f'c_tuple:{c_tuple}')
print(f'str_args:{str_args}')
print(f'str_kw:{str_kw}')
print(f'c_dict:{c_dict}')
輸出結果如下:
f_string:my_var = 1.23
c_tuple:my_var = 1.23
str_args:my_var = 1.23
str_kw:my_var = 1.23
c_dict:my_var = 1.23
f-字串還可以將完整的Python運算式放在占位符括號內,通過對使用簡明語法格式化的值進行小的修改,可以從根本上解決問題2,現在,使用C樣式格式化和str.format方法花費多行的內容現在很容易放在一行上:
for i, (item, count) in enumerate(pantry):
old_style = '#%d: %-10s = %d' % (
i + 1,
item.title(),
round(count))
new_style = '#{}: {:<10s} = {}'.format(
i + 1,
item.title(),
round(count))
f_string = f'#{i+1}: {item.title():<10s} = {round(count)}'
assert old_style == new_style == f_string
當然,如果為了讓代碼更清晰,可以將f-字串拆分為多行,即使比單行版本更長,也比其他任何多行方法都清晰得多:
for i, (item, count) in enumerate(pantry):
print(f'#{i+1}: '
f'{item.title():<10s} = '
f'{round(count)}')
輸出結果如下:
#1: Avocados = 1
#2: Bananas = 2
#3: Cherries = 15
Python運算式也可以出現在格式化說明符選項中,例如,在這里我通過使用變數而不是將其硬編碼為格式化字串來指定要輸出的浮點數位數:
places = 3
number = 1.23456
print(f'My number is {number:.{places}f}')
f-字串可以讓表達力,簡潔性和清晰度結合在一起,使它們成為Python程式員最好的內置選項,每當您發現自己需要將值格式化為字串時,都可以選擇f-字串作為替代,
總結:
1. 使用%運算子的C風格格式化字串會遇到各種陷阱和冗長的問題;
2.str.format方法在其格式說明符迷你語言中引入了一些有用的概念,但在其他方面會重復C風格格式化字串的錯誤,應避免使用;
3. f-字串是用于將值格式化為字串的新語法,解決了C風格格式化字串最大的問題;
4. f-字串簡潔而強大,因為它們允許將任意Python運算式直接嵌入格式說明符中;
對本文感興趣,可以加李寧老師微信公眾號(unitymarvel):
CSDN認證博客專家
公眾號:極客起源
達芬奇
UM
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/123610.html
標籤:AI
