引言
有些小伙伴有其他編程語言的學習、使用的經驗,然后遷移到Python。一般會比完全的新手小白,更快速地把Python用起來。這是他們的優勢,但也是他們的劣勢。
之所以這么說,是因為從其他編程語言帶過來的,除了相通的編程思維、框架性的東西,還有編程語言的使用習慣。
而這些其他編程語言中的使用習慣,就會導致他們寫出來的Python代碼不是那么的Pythonic,當然,這是一種感覺。
雖說我們首要追求的是能用、夠用。但是,偶爾也要稍微留意一下。就好像學習英語一樣,雖然不追求能操著一口正宗的英倫腔,但也不應該對自己的chinglish迷之自信。
此外,故步自封于其他編程語言中的編程習慣,也可能限制我們更加靈活、便捷地發揮Python中的強大特性。
變量值交換
其他語言中,如果需要交換兩個變量,通常需要引入一個中間變量,比如:
a = 5
b = 10
c = b
b = a
a = c
在Python中還這樣寫,就顯得有些冗余、不夠地道了。
其實,在Python中有一種更加簡便的寫法:
x = 5
y = 10
x, y = y, x
不需要引入中間變量,直接完成變量值的交換。我看有些地方把Python中的這種變量值交換,稱之為unpacking機制。所以,我在今天的這篇講解unpacking機制的文章中提到了這一點。
但是,也許是Python不同版本、Python解釋器實現的差異,在我的Python3.11、CPython解釋器的環境下,查看對應的字節碼,發現并沒有用到unpacking機制,而是Python中提供了一個用于進行棧頂兩個值交換的swap指令。對應的字節碼指令及解釋,如下圖所示:感興趣的可以自行查看自己環境中變量值交換的實現。
基礎unpacking機制
接下來,說回今天的主題,unpacking機制。
既然是unpacking,有些地方翻譯為拆包,自然首先要有包可拆。這里所謂的拆包,其實是針對容器/集合類型的數據結構來說的。
通常情況下,我們把一個列表、元組或者是字典中的元素取出來,可以使用下標索引的方式。
比如,有如下場景:
我們有一些人員信息存放在一個列表中,每個列表元素是一個元組,元組中的元素,分別是姓名、年齡、性別。
現在,我們需要遍歷人員信息的這個列表,然后將人員信息進行格式化打印輸出。
使用下標索引的方式,可以這樣實現:
persons = [('張三', 18, '女'), ('小紅', 23, '男')]
for p in persons:name = p[0]age = p[1]gender = p[2]print(f"姓名: {name}, 年齡: {age}, 性別: {gender}")
但是,這樣的寫法,不夠地道,沒有使用Python給我們提供的更加好用的寫法。
接下來,我們用unpacking的方式重新寫一下:
# unpacking
persons = [('張三', 18, '女'), ('小紅', 23, '男')]
for p in persons:name, age, gender = pprint(f"姓名: {name}, 年齡: {age}, 性別: {gender}")
這樣寫下來,用到了Python中的unpacking,首先代碼行數減少了。
是否真的應用到了unpacking機制,還是說跟變量值交換一樣,也是人云亦云,我們可以看下對應的字節碼序列:
字節碼指令序列的其他指令可以不用關心,我們重點看源碼第4行的對應指令序列,可以看到:
1、確實觸發了unpacking機制;
2、UNPACK_SEQUENCE指令,用于將棧頂的集合變量進行拆包的操作,拆為多少個,指令有一個操作數,此時是3,由操作數決定;
3、拆包指令實現的結果是,棧頂集合變量出棧,按照操作數拆分,然后依次入棧。
unpacking的機制,看似好用,但是,細心的你,可能立馬會發現一個問題,如果集合中元素有很多個,此時,我們只需要其中的一部分,怎么辦呢。
解決的方法,就是我們在前面的文章中提到過的占位符_的用法。
比如,我們當前,只需要姓名、性別,可以這樣改寫:
# unpacking
persons = [('張三', 18, '女'), ('小紅', 23, '男')]
for p in persons:name, _, gender = pprint(f"姓名: {name}, 性別: {gender}")
所有我們不需要的元素,都可以用占位符進行舍棄,但是,占位符_只是省去了我們給變量取名的麻煩,不需要的元素比較多的時候,似乎還是不太方便,反而不如索引操作方便,好在Python中提供了對應的解決方案。
擴展的unpacking
帶的變量,在Python中為可擴展的變量。
如下,為帶的unpacking的寫法:
# * unpacking
persons = [('張三', 18, 190, '女'), ('小紅', 23, 165, '男')]
for p in persons:name, *others, gender = pprint(others)print(type(others))print(f"姓名: {name}, 性別: {gender}")
代碼中,會將除了name接收的第一個元素,以及gender接收的最后一個元素,之外的所有元素,封裝為一個列表,由others接收。
從對應的字節碼,可以看出有些不同:
首先,翻譯為字節碼指令序列時,多了EXTENDED_ARG指令,用于擴展變量others;其次拆包的指令,從之前的UNPACK_SEQUENCE變成了UNPACK_EX。
注意,關于帶星號定義的可擴展變量,可以理解為不定長列表,可以接收零個或者多個值,后續在函數的定義中也會用到。