(點擊上方公眾號,可快速關注一起學Python)
來源:MLGroup 連結:
http://mp.weixin.qq.com/s/71Zbq9b8RM2WpRDVvtidNQ
原文連結:http://treyhunner.com/2018/03/tuple-unpacking-improves-python-code-readability/
無論我是教Python新手還是老手,經常發現他們沒有很好的使用多重賦值。
多重賦值允許你在一行代碼中同時分配多個值。也許你在了解它之後會覺得也不過如此嘛,但你要記住,多重賦值有可能會很棘手。
這篇文章就來詳細的介紹下多重賦值。
多重賦值如何工作在本文中,當我說到多重賦值,元組解包,迭代解包的時候,指的都是同一件事情。
Python中的多重賦值看起來像這樣:
x, y = 10, 20
這裡我們把x設為10,y設為20。
上面的代碼實際上是創建了一個包含10,20的元組,然後循環遍歷該元組,並從循環中獲取兩個items中的每一個,然後分別分配給x和y。
像這樣:
(x, y) = (10, 20)
在元組中使用括號是可選的,多重賦值也一樣,下面這幾行代碼都是等價的:
x, y = 10, 20
x, y = (10, 20)
(x, y) = 10, 20
(x, y) = (10, 20)
多元賦值通常被稱為「元組拆包」,是因為它經常與元組一起使用。但是我們也可以對任何迭代使用多個賦值,而不僅僅是元組。比如使用列表:
>>> x, y = [10, 20]
>>> x
10
>>> y
20
使用string:
>>> x, y = 'hi'
>>> x
'h'
>>> y
'i'
任何可以循環的東西都可以通過元組解包/多重賦值來「解壓縮」。
接下來說說如何使用多重賦值。
在循環中解壓縮你經常會在for循環中看到解壓縮。
這裡有一個dict:
>>> person_dictionary = {'name': "Trey", 'company': "Truthful Technology LLC"}
通常我們不會對dict這樣使用for循環:
for item in person_dictionary.items():
print(f"Key {item[0]} has value {item[1]}")
而是像下面這樣,使用多重賦值:
for key, value in person_dictionary.items():
print(f"Key {key} has value {value}")
實際上,等同於把item賦值給key,value一樣:
for item in person_dictionary.items():
key, value = item
print(f"Key {key} has value {value}")
所以多重賦值非常適合把一個字典拆分成鍵值對的形式,這一用法在其他地方也很常見。
比如使用enumerate的時候:
for i, line in enumerate(my_file):
print(f"Line {i}: {line}")
同樣的,在使用zip的時候:
for color, ratio in zip(colors, ratios):
print(f"It's {ratio*100}% {color}.")
多任務雖然適用於任何分配,但不僅僅是循環分配。
我們經常會在代碼中看到硬編碼索引(像point[0], items[1]這樣):
print(f"The first item is {items[0]} and the last item is {items[-1]}")
我們可以用多重賦值來代替硬編碼索引,這樣能讓代碼的可讀性更高。
比如,下面是一段有三個硬編碼索引的代碼:
def reformat_date(mdy_date_string):
"""Reformat MM/DD/YYYY string into YYYY-MM-DD string."""
date = mdy_date_string.split('/')
return f"{date[2]}-{date[0]}-{date[1]}"
通過使用多重賦值讓這段代碼可讀性更高一些:
def reformat_date(mdy_date_string):
"""Reformat MM/DD/YYYY string into YYYY-MM-DD string."""
month, day, year = mdy_date_string.split('/')
return f"{year}-{month}-{day}"
所以當你在代碼中見到硬編碼索引時,停下來想想是否可以用多重賦值使得代碼更可讀。
在解包我們給它的迭代時,多重賦值實際上相當嚴格。
如果試圖將更大的迭代器拆分成更少的變量,會得到一個錯誤:
>>> x, y = (10, 20, 30)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)
同樣的,如果試圖將一個較小的迭代器拆分成更多的變量,也會得到一個錯誤:
>>> x, y, z = (10, 20)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected 3, got 2)
這種嚴格的規定相當好,如果當前正在處理的item與我們預期的不同,可以及時的發現並修改。
多重賦值也可以用來代替切片中的硬編碼。
我們都知道,切片可以方便的獲取列表或者其它序列中的部分元素。
像下面的代碼就是硬編碼的方式;
all_after_first = items[1:]
all_but_last_two = items[:-2]
items_with_ends_removed = items[1:-1]
任何時候當你看到切片索引中使用硬編碼的方式,你都可以使用多重賦值來代替。要做到這一點,我們需要用到* 操作符。
在python3中,* 操作符被加入到多重賦值的語法中,它允許我們在解包列表時使用* 代替剩餘的部分,比如:
>>> numbers = [1, 2, 3, 4, 5, 6]
>>> first, *rest = numbers
>>> rest [2, 3, 4, 5, 6]
>>> first
1
* 操作符允許我們替換列表中的硬編碼,下面的兩行代碼是等價的:
>>> beginning, last = numbers[:-1], numbers[-1]
>>> *beginning, last = numbers
同樣的:
>>> head, middle, tail = numbers[0], numbers[1:-1], numbers[-1]
>>> head, *middle, tail = numbers
深入拆包是python程式設計師經常忽視的一個特性,雖然使用的不是很頻繁,但是了解了之後將很有用。
對於下面的代碼來說,並不算是深入拆包:
>>> color, point = ("red", (1, 2, 3))
>>> color
'red'
>>> point (1, 2, 3)
而下面的代碼就可以這樣說了,因為它將point元組進一步分為x,y,z三個變量:
>>> color, (x, y, z) = ("red", (1, 2, 3))
>>> color
'red'
>>> x
1
>>> y
2
如果對上面的代碼你感到困惑,它其實跟下面的代碼是一樣的,在兩邊加上括號即可:
>>> (color, (x, y, z)) = ("red", (1, 2, 3))
首先通過第一層拆包得到兩個對象,然後再將第二個對象拆包分配給x, y, z三個變量。
考慮下面兩個列表:
start_points = [(1, 2), (3, 4), (5, 6)]
end_points = [(-1, -2), (-3, 4), (-6, -5)]
下面的代碼使用淺層拆包對列表進行操作:
for start, end in zip(start_points, end_points):
if start[0] == -end[0] and start[1] == -end[1]:
print(f"Point {start[0]},{start[1]} was negated.")
我們用深層拆包可以做同樣的事:
for (x1, y1), (x2, y2) in zip(start_points, end_points):
if x1 == -x2 and y1 == -y2:
print(f"Point {x1},{y1} was negated.")
使用深層拆包能夠更清晰的看到正在處理的對象,另外,深層拆包的過程同樣嚴格。
多重賦值可以提高代碼的可讀性和代碼的正確性。它可以使您的代碼更具描述性,同時還可以對您要解包的迭代對象的大小和形狀進行隱式斷言。
多重賦值一個最常見的用途是替換硬編碼索引和硬編碼的列表。
(完)
看完本文有收穫?請轉發分享給更多人
關注「Python那些事」,做全棧開發工程師