1. 程式人生 > >[翻譯]python3中新的字串格式化方法-----f-string

[翻譯]python3中新的字串格式化方法-----f-string

從python3.6開始,引入了新的字串格式化方式,f-字串. 這使得格式化字串變得可讀性更高,更簡潔,更不容易出現錯誤而且速度也更快.

在本文後面,會詳細介紹f-字串的用法. 在此之前,讓我們先來複習一下python中字串格式化的方法.

python中傳統的字串格式化方法.

在python3.6之前,我們有兩種方式可以用來格式化字串.

  • 佔位符+%的方式
  • str.format()方法

首先複習一下這兩種方式的使用方法以及其短板.

佔位符+%的方式

這種方式算是第0代字串格式化的方法,很多語言都支援類似的字串格式化方法. 在python的文件中,我們也經常看到這種方式.

但是!!! BUT!!!

佔位符+%的方式並不是python推薦的方式.

Note The formatting operations described here exhibit a variety of quirks that lead to a number of common errors (such as failing to display tuples and dictionaries correctly). Using the newer formatted string literals, the str.format() interface, or template strings may help avoid these errors. Each of these alternatives provides their own trade-offs and benefits of simplicity, flexibility, and/or extensibility.(Python3 doc)

文件中也說了,這種方式對於元組等的顯示支援的不夠好. 而且很容易產生錯誤.

而且不符合python程式碼簡潔優雅的人設...

「如何使用佔位符+%的方式」

如果你接觸過其他的語言,這種方式使用起來會有一種詭異的親切感,這種親切感會讓你抓狂,內心會暗暗的罵上一句,艹,又是這德行...(這句不是翻譯,是我的個人感覺,從來都記不住那麼多資料型別的關鍵字...)


In [1]: name='Eric'
In [2]: 'Hello,%s'%name
Out[2]: 'Hello,Eric'

如果要插入多個變數的話,就必須使用元組.像這樣


In [3]: name='Eric'
In [4]: age=18
In [5]: 'Hello %s,you are %d.'%(name,age)
Out[5]: 'Hello Eric,you are 18.'

「為什麼說佔位符+%的方式不是最好的辦法(個人認為是這種方式是一種最操蛋的操作)」

上面有少量的變數需要插入到字串的時候,這種辦法還行. 但是一旦有很多變數需要插入到一個長字串中...比如...


In [6]: first_name = "Eric" ...: last_name = "Idle" ...: age = 74 ...: profession = "comedian" ...: affiliation = "Monty Python"
In [7]: "Hello, %s %s. You are %s. You are a %s. You were a member of %s." % (first_name, last_name, age, profession, affiliation)
Out[7]: 'Hello, Eric Idle. You are 74. You are a comedian. You were a member of Monty Python.'

像上面這個例子,程式碼可讀性就很差了.(對讀和寫的人都是一種折磨...)

使用str.format()的方式

在python2.6之後,引入了str.format()函式,可以用來進行字串的格式化. 它通過呼叫物件的__format__()方法(PEP3101中定義)來將物件轉化成字串.

在str.format()方法中,通過花括號佔位的方式來實現變數插入.


In [8]: 'hello,{}. You are {}.'.format(name,age)
Out[8]: 'hello,Eric. You are 74.'

甚至可以給佔位符加索引.


In [9]: 'hello,{1}. You are {0}.'.format(age,name)
Out[9]: 'hello,Eric. You are 74.'

如果要在佔位符中使用變數名的話,可以像下面這樣


In [10]: person={'name':'Eric','age':74}
In [11]: 'hello,{name}. you are {age}'.format(name=person['name'],age=person['age'])
Out[11]: 'hello,Eric. you are 74'

當然對於字典來說的話,我們可以使用**的小技巧.


In [15]: 'hello,{name}. you are {age}'.format(**person)
Out[15]: 'hello,Eric. you are 74'

str.format()方法對於%的方式來說已經是一種很大的提升了. 但是這並不是最好的方式.

「為什麼format()方法不是最好的方式」 相比使用佔位符+%的方式,format()方法的可讀性已經很高了. 但是同樣的,如果處理含有很多變數的字串的時候,程式碼會變得很冗長.



>>> first_name = "Eric"
>>> last_name = "Idle"
>>> age = 74
>>> profession = "comedian"
>>> affiliation = "Monty Python"
>>> print(("Hello, {first_name} {last_name}. You are {age}. " +
>>> "You are a {profession}. You were a member of {affiliation}.") \
>>> .format(first_name=first_name, last_name=last_name, age=age, \
>>> profession=profession, affiliation=affiliation)) 'Hello, Eric Idle. You are 74. You are a comedian. You were a member of Monty Python.'

當然,我們也可以通過字典的方式直接傳入一個字典來解決程式碼過長的問題. 但是,python3.6給我們提供了更便利的方式.

f-字串,一種新的增強型字串格式化方式

這種新的方式在PEP498中定義.(原文寫到這裡的時候,作者可能瘋了,balabla說了一長串,冷靜的我並沒有翻譯這些廢話...) 這種方式也被叫做formatted string literals.格式化的字串常亮...ummm...應該是這麼翻譯吧...

這種方式在字串開頭的時候,以f標識,然後通過佔位符{}+變數名的方式來自動解析物件的__format__方法. 如果想了解的更加詳細,可以參考python文件

一些簡單的例子

「使用變數名作為佔位符」


In [16]: name = 'Eric'
In [17]: age=74
In [18]: f'hello {name}, you are {age}'
Out[18]: 'hello Eric, you are 74'

「這裡甚至可以使用大寫的F」


In [19]: F'hello {name}, you are {age}'
Out[19]: 'hello Eric, you are 74'

你以為這就完了嗎?

不!

事情遠不止想象的那麼簡單...

在花括號裡甚至可以執行算數表示式


In [20]: f'{2*37}'
Out[20]: '74'

如果數學表示式都可以的話,那麼在裡面執行一個函式應該不算太過分吧...


In [22]: def to_lowercase(input): ...: return input.lower() ...:
In [23]: name = 'ERIC IDLE'
In [24]: f'{to_lowercase(name)} is funny'
Out[24]: 'eric idle is funny'

你以為這就完了嗎?

不!

事情遠不止想象的那麼簡單...

這玩意兒甚至可以用於重寫__str__()和__repr__()方法.


class Comedian:
    def __init__(self, first_name, last_name, age):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age

    def __str__(self):
        return f"{self.first_name} {self.last_name} is {self.age}."

    def __repr__(self):
        return f"{self.first_name} {self.last_name} is {self.age}. Surprise!"



>>> new_comedian = Comedian("Eric", "Idle", "74")
>>> f"{new_comedian}"'Eric Idle is 74.'

關於__str__()方法和__repr__()方法. 這是物件的兩個內建方法.__str()__方法用於返回一個便於人類閱讀的字串. 而__repr__()方法返回的是一個物件的準確釋義. 這裡暫時不做過多介紹. 如有必要,請關注公眾號吾碼2016(公眾號:wmcoding)併發送str_And_repr

預設情況下,f-關鍵字會呼叫物件的__str__()方法. 如果我們想呼叫物件的__repr__()方法的話,可以使用!r



>>> f"{new_comedian}" 'Eric Idle is 74.'
>>> f"{new_comedian!r}" 'Eric Idle is 74. Surprise!'

更多詳細內容可以參考這裡

多個f-字串佔位符

同樣的,我們可以使用多個f-字串佔位符.


>>> name = "Eric"
>>> profession = "comedian"
>>> affiliation = "Monty Python"
>>> message = ( ... f"Hi {name}. " ... f"You are a {profession}. " ... f"You were in {affiliation}." ... )
>>> message 'Hi Eric. You are a comedian. You were in Monty Python.'

但是別忘了,在每一個字串前面都要寫上f

同樣的,在字串換行的時候,每一行也要寫上f.


>>> message = f"Hi {name}. " \ ... f"You are a {profession}. " \ ... f"You were in {affiliation}."...
>>> message 'Hi Eric. You are a comedian. You were in Monty Python.'

但是如果我們使用"""的時候,不需要每一行都寫.


>>> message = f""" ... Hi {name}. ... You are a {profession}. ... You were in {affiliation}. ... """ ...
>>> message '\n Hi Eric.\n You are a comedian.\n You were in Monty Python.\n'

關於f-字串的速度

f-字串的f可能代表的含義是fast,因為f-字串的速度比佔位符+%的方式和format()函式的方式都要快.因為它是在執行時計算的表示式而不是常量值.(那為啥就快了呢...不太懂啊...)

“F-strings provide a way to embed expressions inside string literals, using a minimal syntax. It should be noted that an f-string is really an expression evaluated at run time, not a constant value.
In Python source code, an f-string is a literal string, prefixed with f, which contains expressions inside braces. The expressions are replaced with their values.”(PEP498)

(官方文件,咱不敢翻,大意就是f-字串是一個在執行時參與計算的表示式,而不是像常規字串那樣是一個常量值)

在執行時,花括號內的表示式在其自己的作用域內求職,單號和字串的部分拼接到一起,然後返回.

下面我們來看一個速度的對比.

import timeit

time1 = timeit.timeit("""name = 'Eric'\nage =74\n'%s is %s'%(name,age)""",number=100000)
time2 = timeit.timeit("""name = 'Eric'\nage =74\n'{} is {}'.format(name,age)""",number=100000)
time3 = timeit.timeit("""name = 'Eric'\nage =74\nf'{name} is {age}'""",number=100000)

從結果上看的話,f-字串的方式速度要比其他兩種快.

0.030868000000000007
0.03721939999999996
0.0173276

f-字串的一些細節問題

「引號的問題」 在f-字串中,注意成對的引號使用.


f"{'Eric Idle'}"
f'{"Eric Idle"}'
f"""Eric Idle"""
f'''Eric Idle'''

以上這幾種引號方式都是支援的. 如果說我們在雙引號中需要再次使用雙引號的時候,就需要進行轉義了. f"The \"comedian\" is {name}, aged {age}."

「字典的注意事項」

在字典使用的時候,還是要注意逗號的問題.


>>> comedian = {'name': 'Eric Idle', 'age': 74}
>>> f"The comedian is {comedian['name']}, aged {comedian['age']}."
>>> f'The comedian is {comedian['name']}, aged {comedian['age']}.'

比如上面兩條語句,第三句就是有問題的,主要還是引號引起的歧義.

「花括號」 如果字串中想使用花括號的話,就要寫兩個花括號來進行轉義. 同理,如果想輸出兩個花括號的話,就要寫四個...


>>> f"{{74}}"'{74}'
>>> f"{{{{74}}}}"

「反斜槓」 反斜槓可以用於轉義. 但是!!!BUT!!!在f-字串中,不允許使用反斜槓.


>>> f"{\"Eric Idle\"}" File "<stdin>", line 1 f"{\"Eric Idle\"}" ^SyntaxError: f-string expression part cannot include a backslash

像上面這個的解決辦法就是


>>> name = "Eric Idle"
>>> f"{name}"'Eric Idle'

「行內註釋」 f-字串表示式中不允許使用#符號.

總結和參考資料

我們依舊可以使用老的方式進行字串格式化輸出. 但是通過f-字串,我們現在有了一種更便捷,更快,可讀性更高的方式. 根據python教義,Zen of Python:

「there should be one– and preferably only one –obvious way to do it.」 (程式設計還編出哲理來了...實在不會翻,有一種醍醐灌頂的感覺,內心浮現一個聲音,臥槽!好有道理,彷彿自己昇華了,但是仔細想想...這句話到底啥意思呢...)

更多的參考資料(我也只是寫在這裡,反正我是沒有閒心看它的...):

  • PEP502:String Interpolation - Extended Discussion
  • PEP 536:Final Grammar for Literal String Interpolation