【譯】Python3.8新語法:海象運算符

做者:Brett Slatkinpython

翻譯:老齊程序員

與本文內容有關的圖書:《跟老齊學Python:輕鬆入門》、《Python大學實用教程》express


Python3.8引入了一種叫作海象運算符(walrus)的新語法(譯者注: 對於walrus的翻譯,目前還沒有出現對於Python的專門術語翻譯,因此,此處姑且用字面意思「海象」),它實際上是一種賦值語句,用於解決Python語言中長期存在的、可能致使代碼重複的問題。正常的賦值語句是a=b,讀做「a等於b」,而海象賦值語句是a:=b,讀做「a walrus /ˈwɔːlrəs/ b」(由於:=看起來像一對眼球和獠牙,相似於海象。注意:此語句尚未適合的中文讀法,總不能讀做「a海象b」吧)。bash

海象運算符的優點在於能在不容許賦值的地方(如if語句的條件表達式中)使用賦值變量。海象運算符左側有個標識符,賦值表達式的值等於分配給這個標識符的值。微信

例如,假設我有一籃子新鮮水果,我正試圖經營一家果汁店。在這裏,我定義了籃子裏的東西:app

fresh_fruit = {
    'apple': 10,
    'banana': 8,
    'lemon': 5,
}
複製代碼

當顧客到櫃檯點檸檬水時,我須要確保籃子裏至少有一個檸檬用來榨果汁。個人操做方法是檢索檸檬的數量,而後使用if語句查詢非零的值:ide

def make_lemonade(count):
    ...

def out_of_stock():
    ...

count = fresh_fruit.get('lemon', 0)
if count:
    make_lemonade(count)
else:
    out_of_stock()
複製代碼

這個看似簡單的代碼問題吸引了過多的關注。count變量僅在if語句的第一個代碼塊中使用,在if語句上方定義count會使它看起來比實際狀況更爲重要,好像後面的全部代碼(包括else塊)都須要訪問count變量,然而事實並不是如此。函數

咱們獲取一個值,檢查它是否爲非零,而後使用它。這種模式在Python中很是常見。許多程序員試圖繞過屢次出現count的狀況,甚至不惜使用各類損害可讀性的招數。如今好了,在Python3.8中增長了海象運算符,能夠簡化上面的代碼。oop

if count := fresh_fruit.get('lemon', 0):
    make_lemonade(count)
else:
    out_of_stock()
複製代碼

雖然如今只是少了一行,但可讀性提升不少。由於如今能夠清楚地看到count只與if語句的第一行相關。賦值表達式首先爲count變量賦值,而後在if語句的上下文中使用該值,以肯定如何繼續控制流程。這兩步行爲——分配和評估——是海象運算符的基本性質。ui

檸檬是很是有效的,因此個人檸檬水配方中只須要一個,這意味着非零檢查就足夠了。不過,若是顧客點了蘋果酒,我須要確保至少有四個蘋果。在這裏,我從fruit_basket字典中獲取計數,而後在if語句中使用比較表達式:

def make_cider(count):
    ...

count = fresh_fruit.get('apple', 0)
if count >= 4:
    make_cider(count)
else:
    out_of_stock()
複製代碼

這個問題和檸檬水的例子同樣,count的賦值會分散對這個變量的注意力。在這裏,我還使用了海象運算符來提升代碼的清晰度:

if (count := fresh_fruit.get('apple', 0)) >= 4:
    make_cider(count)
else:
    out_of_stock()
複製代碼

這樣作能夠收到預期的效果,並使代碼縮短了一行。須要注意的是,我須要用圓括號將賦值表達式括起來,以便與if語句中的4進行比較。在檸檬水的例子中不須要使用圓括號,由於賦值表達式自己就是一個非零檢查;它不是一個較大表達式的子表達式。與其餘表達式同樣,應儘可能避免使用圓括號把賦值表達式括起來。

有時候會出現一種相似的重複模式,那就是當我須要根據某些條件在封閉範圍內分配一個變量,而後在函數中的稍後位置引用該變量。例如,假設客戶訂購了一些香蕉冰沙。爲了製做它們,我須要至少兩個香蕉的香蕉片,不然將引起OutOfBananas異常。在這裏,我以一種典型的方式來實現這個邏輯:

def slice_bananas(count):
    ...

class OutOfBananas(Exception):
    pass

def make_smoothies(count):
    ...

pieces = 0
count = fresh_fruit.get('banana', 0)
if count >= 2:
    pieces = slice_bananas(count)

try:
    smoothies = make_smoothies(pieces)
except OutOfBananas:
    out_of_stock()
複製代碼

另外一種常見的方法是將pieces=0賦值放入else塊:

count = fresh_fruit.get('banana', 0)
if count >= 2:
    pieces = slice_bananas(count)
else:
    pieces = 0

try:
    smoothies = make_smoothies(pieces)
except OutOfBananas:
    out_of_stock()
複製代碼

第二種方法可能會讓人以爲奇怪,由於這意味着pieces變量出如今了條件語句中兩個不一樣的位置,能夠在這兩個位置進行初始定義。因爲Python的做用域規則,這種分割定義在技術上是可行的,但它的可讀性很差,也不優雅。這就是許多人喜歡上面那種結構的緣由,在它裏面的pieces = 0的賦值在前面出現。

海象運算符也能夠用一行代碼來縮短這個例子。這個小變化消除了對count變量的任何強調。如今,很明顯,除了if語句以外,pieces也很重要:

pieces = 0
if (count := fresh_fruit.get('banana', 0)) >= 2:
    pieces = slice_bananas(count)

try:
    smoothies = make_smoothies(pieces)
except OutOfBananas:
    out_of_stock()
複製代碼

使用海象運算符還能夠提升在條件語句中分別在兩個分支中的pieces複製的可讀性。當count定義再也不位於if語句以前時,跟蹤pieces變量變得更容易:

if (count := fresh_fruit.get('banana', 0)) >= 2:
    pieces = slice_bananas(count)
else:
    pieces = 0

try:
    smoothies = make_smoothies(pieces)
except OutOfBananas:
    out_of_stock()
複製代碼

初學Python的程序員常常遇到的一個難題是缺乏靈活的switch/case語句,與此類功能近似的通常作法是使用多個if、elif和else語句的深度嵌套。

例如,假設我想實現一個優先級系統,這樣每一個客戶均可以自動得到最好的果汁,而沒必要預約。在這裏,我設置這樣的流程,讓它先供應香蕉冰沙,而後供應蘋果酒,最後供應檸檬水:

count = fresh_fruit.get('banana', 0)
if count >= 2:
    pieces = slice_bananas(count)
    to_enjoy = make_smoothies(pieces)
else:
    count = fresh_fruit.get('apple', 0)
    if count >= 4:
        to_enjoy = make_cider(count)
    else:
        count = fresh_fruit.get('lemon', 0)
        if count:
            to_enjoy = make_lemonade(count)
        else:
            to_enjoy = 'Nothing'
複製代碼

像這樣難看的結構在Python代碼中司空見慣,幸運的是,海象運算符提供了一個優雅的解決方案,它幾乎能夠像switch/case語句的專用語法同樣通用:

if (count := fresh_fruit.get('banana', 0)) >= 2:
    pieces = slice_bananas(count)
    to_enjoy = make_smoothies(pieces)
elif (count := fresh_fruit.get('apple', 0)) >= 4:
    to_enjoy = make_cider(count)
elif count := fresh_fruit.get('lemon', 0):
    to_enjoy = make_lemonade(count)
else:
    to_enjoy = 'Nothing'
複製代碼

使用海象運算符版本只比原來的版本短五行,可是因爲嵌套和縮進的減小,可讀性有了很大提升。若是在你的代碼中看到像上面那樣醜陋的代買,我建議你儘可能使用海象運算符重寫。

初學Python的程序員經常遇到的另外一個挫折是缺乏do/while循環構造。例如,假設我想在新水果到貨時將果汁裝入瓶中,直到沒有剩餘的水果爲止。在這裏,我用while循環實現這個邏輯:

def pick_fruit():
    ...

def make_juice(fruit, count):
    ...

bottles = []
fresh_fruit = pick_fruit()
while fresh_fruit:
    for fruit, count in fresh_fruit.items():
        batch = make_juice(fruit, count)
        bottles.extend(batch)
    fresh_fruit = pick_fruit()
複製代碼

這裏存在重複,前後執行了兩次fresh_fruit = pick_fruit(),一個在循環前設置初始條件,另外一個在循環結束時補充到貨的水果列表。

在這種狀況下,改進代碼複用的策略是使用loop-and-a-half(若是出現這種狀況,須要當即退出並跳過循環體中的任何剩餘語句)。這消除了多餘的行,但它也破壞了while循環,使其成爲一個愚蠢的無限循環。如今,循環的全部流控制都依賴於break條件語句:

bottles = []
while True:                     # Loop
    fresh_fruit = pick_fruit()
    if not fresh_fruit:         # And a half
        break
    for fruit, count in fresh_fruit.items():
        batch = make_juice(fruit, count)
        bottles.extend(batch)
複製代碼

海象運算符消除了對loop-and-a-half的須要。方法是:容許從新設置fresh_fruit變量,而後每次都經過while循環有條件地求值。此解決方案簡短易讀,應該是代碼中的首選方法:

bottles = []
while fresh_fruit := pick_fruit():
    for fruit, count in fresh_fruit.items():
        batch = make_juice(fruit, count)
        bottles.extend(batch)
複製代碼

在許多其餘狀況下,可使用還有海象運算符的賦值表達式來消除冗餘。一般,當你發現本身在許多行中屢次重複同一個表達式或賦值時,應該考慮使用海象運算符來提升可讀性。

牢記

  • 賦值表達式使用walrus運算符(:=)在單個表達式中同時對變量名進行賦值和計算,從而減小重複。
  • 當賦值表達式是一個較大表達式的子表達式時,它必須用圓括號括起來。
  • 儘管在Python中不能用switch/case語句和do/while循環,可是經過使用海象運算符的賦值表達式能夠更清楚地模擬它們的功能。

原文連接:effectivepython.com/2020/02/02/…

關注微信公衆號:老齊教室。讀深度文章,得精湛技藝,享絢麗人生。

相關文章
相關標籤/搜索