<sup id="ai8i2"><center id="ai8i2"></center></sup>
<rt id="ai8i2"><small id="ai8i2"></small></rt>

Python高效編程之88條軍規(2):你真的會格式化字符串嗎?

目錄

1. ?C風格的字符串格式化方式

2. 內建format函數與str.format方法

3. f-字符串

總結:

?

在微信公眾號「極客起源」中輸入595586,可學習全部的《Python高效編程之88條軍規》系列文章。

在Python語言中,字符串有多種用途??梢杂糜谠谟脩艚缑婧兔钚袑嵱贸绦蛑酗@示消息;用于用于將數據寫入文件和Socket;用于指定“異?!毕?#xff1b;用于調試程序。

格式化是將預定義的文本和數據組合成一條人類可讀的消息的過程。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?,F在,使用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
更多精彩內容請關注微信公眾號:「極客起源」,東北大學計算機專業碩士。UnityMarvel創始人,企業內訓講師、IT暢銷書作者,CSDN學院講師,擁有多年軟件開發經驗和培訓經驗。主要著作包括《Python從菜鳥到高手》、《Python爬蟲技術:深入理解原理、技術與開發》,培訓企業學員數千人。制作在線課程數千小時。更多視頻課程,請訪問我的B站:https://space.bilibili.com/477001733
??2020 CSDN 皮膚主題: 代碼科技 設計師:Amelia_0503 返回首頁
實付 49.90元
使用余額支付
點擊重新獲取
掃碼支付
錢包余額 0

抵扣說明:

1.余額是錢包充值的虛擬貨幣,按照1:1的比例進行支付金額的抵扣。
2.余額無法直接購買下載,可以購買VIP、C幣套餐、付費專欄及課程。

余額充值
彩61彩票