任務
需要變換一個列表的列表,將行換成列,列換成行。
解決方案
需要一個列表,其中的每一項都是同樣長度的列表,像這樣
arr = [[1,2,3],[4,5,6],[7,8,9],[10,11,12]]
列表推導提供了簡單方便的方法以完成二維陣列的轉換:
print [[r[col] for r in arr] for col in range(len(arr[0]))]
[[1,4,7,10],[2,5,8,11],[3,6,9,12]]
另一個更快也更讓人困惑的方法(輸出是一樣的)是利用內建函數zip 實現的:
print map(list,zip(*arr))
討論
本節展示了一種簡潔而清晰的轉換方式,還有一個更快速的備選方案。在需要簡潔和清晰并存的時候,列表推導通常是很好的選擇,而備選方案利用內建函數 zip 以另外一種方式達到目的,顯得很晦澀難懂。
有時,你獲得的數據的順序是不正確的。舉個例子,如果使用微軟的ActiveX DataObjects(ADO)數據庫接口,由于Python和微軟的首選實現語言(VisualBasic)在對數組元素排序上的差異,Getrows方法返回的實際上是Python中的列。本節針對這種常見需求提出的兩種解決方案,讓你有機會在清晰和速度之間進行選擇。
在列表推導的解決方案中,內層推導改變的是(從行中)選出的元素,外層推導則影響選擇子(selector,即列)。由此實現轉換。
而基于 zip 的解決方案,我們使用了*a語法將 arr 中的每個元素(行),根據順序,作為分隔開的參數傳遞給 zip。zip 返回的是元組的列表,其實已經完成了轉換。通過 map調用,我們可以對每個元組調用 list,以獲得一個列表的列表。既然我們不能將 zip 的結果直接當做列表使用,我們可以通過使用itertools.izip 來得到一點改進(因為 izip 并不會將結果當做列表載入內存,而是每次生成一個子項):
import itertools
print map(list,itertools.izip(*arr))
不過,對這個例子而言,這一點速度提升也許并不能抵消它所帶來的復雜性。
如果要轉換非常巨大的數字陣列,可以考慮Numeric Python和其他的第三方包。NumericPython 支持一系列變換以及軸旋轉,這些數學轉換能把大多數人繞暈。
*args 和**kwds 語法
*args(*通常緊跟一個標識符,你會看到a或者 args 都是標識符)是Python用于接收或者傳遞任意基于位置的參數的語法。當你接收到一個用這種語法描述的參數時(比如你在函數的def語句中對函數簽名使用了星號語法),Python 會將此標識符綁定到一個元組,該元組包含了所有基于位置的隱式地接收到的參數。當你用這種語法傳遞參數時,標識符可以被綁定到任何可選代對象(事實上,它也可以是任何表達式,并不必須是一個標識符,只要這個表達式的結果是一個可迭代對象即可)。
**kwds(標識符可以是任意的,通常用k或者kwds 表示)是Python 用于接收或者傳遞任意命名的參數的語法。(Python有時候會將命名參數稱為關鍵字參數,它們其實并不是關鍵字——只是用它們來給關鍵字命名,比如pass、for或 yield,還有很多。不幸的是,這種讓人疑惑的術語目前仍是這門語言及其文化的根深蒂固的一個組成部分。)當你接收到用這種語法描述的一個參數時(比如你在函數的def語句中對函數簽名使用了雙星號語法),Python 會將標識符綁定到一個字典,該字典包含了所有接收到的隱式的命名參數。當你用這種語法傳遞參數時,標識符只能被綁定到字典(其實它也可以是表達式,不一定是一個標識符,只要這個表達式的結果是一個字典即可)。
當你在定義或調用一個函數的時候,必須確保*a和**k 在其他所有參數之后。如果這兩者同時出現,要將**k放在*a之后。