翻譯:Python 開發者 - 一汀, 英文:Trey Hunner程序員http://blog.jobbole.com/114655/編程
Python開發者網絡
在 Python 中有不少地方能夠看到*
和**
。在某些情形下,不管是對於新手程序員,仍是從其餘不少沒有徹底相同操做符的編程語言遷移過來的人來講,這兩個操做符均可能有點神祕。所以,我想討論一下這些操做符的本質及其使用方式。dom
多年以來,*
和**
操做符的功能不斷加強。在本文中,我將討論目前這些操做符全部的使用方法,並指出哪些使用方法只能在目前的 Python 版本中應用。所以,若是你學習過 Python 2 中*
和**
的使用方法,那麼我建議你至少瀏覽一下本文,由於 Python 3 中添加了許多*
和**
的新用途。編程語言
若是你是新接觸 Python 不久,還不熟悉關鍵字參數(亦稱爲命名參數),我建議你首先閱讀我有關Python中的關鍵字參數的文章。函數
在本文中, 當我討論*
和**
時,我指的是*
和**
學習
也就是說,我講述的不是乘法和指數運算:ui
>>> 2 * 5翻譯
10code
>>> 2 ** 5
32
咱們討論的是*
和**
前綴運算符,即在變量前使用的*
和**
運算符。例如:
>>> numbers = [2, 1, 3, 4, 7]
>>> more_numbers = [*numbers, 11, 18]
>>> print(*more_numbers, sep=', ')
2, 1, 3, 4, 7, 11, 18
上述代碼中展現了*
的兩種用法,沒有展現**
的用法。
這其中包括:
使用*
和**
向函數傳遞參數
使用*
和**
捕獲被傳遞到函數中的參數
使用*
接受只包含關鍵字的參數
使用*
在元組解包時捕獲項
使用*
將迭代項解壓到列表/元組中
使用**
將字典解壓到其餘字典中
即便你認爲本身已經熟悉*
和 **
的全部使用方法,我仍是建議你查看下面的每一個代碼塊,以確保都是你熟悉的內容。在過去的幾年裏,Python 核心開發人員不斷地爲這些操做符添加新的功能,對於使用者來講很容易忽略*
和 **
‘的一些新用法。
當調用函數時,*
運算符可用於將一個迭代項解壓縮到函數調用中的參數中:
>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']
>>> print(fruits[0], fruits[1], fruits[2], fruits[3])
lemon pear watermelon tomato
>>> print(*fruits)
lemon pear watermelon tomato
print(*fruits)
代碼行將fruits
列表中的全部項做爲獨立的參數傳遞給print
函數調用,甚至不須要咱們知道列表中有多少個參數。
*
運算符在這裏遠不止是語法糖而已。要想用一個特定的迭代器將全部項做爲獨立的參數傳輸,若不使用*
是不可能作到的,除非列表的長度是固定的。
下面是另外一個例子:
def transpose_list(list_of_lists):
return [
list(row)
for row in zip(*list_of_lists)
]
這裏咱們接受一個二維列表並返回一個「轉置」的二維列表。
>>> transpose_list([[1, 4, 7], [2, 5, 8], [3, 6, 9]])
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
**
操做符完成了相似的操做,只不過使用了關鍵字參數。**
運算符容許咱們獲取鍵-值對字典,並在函數調用中將其解壓爲關鍵字參數。
>>> date_info = {'year': "2020", 'month': "01", 'day': "01"}
>>> filename = "{year}-{month}-{day}.txt".format(**date_info)
>>> filename '2020-01-01.txt' `
根據個人經驗,使用**
將關鍵字參數解壓縮到函數調用中並不常見。我最常看到它的地方是在實現繼承時:對uper()
的調用一般包括*
和**
。
如 Python 3.5 那樣,在函數調用中,*
和**
均可以被屢次使用。
有時,屢次使用*
會很方便:
>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']
>>> numbers = [2, 1, 3, 4, 7]
>>> print(*numbers, *fruits)
2 1 3 4 7 lemon pear watermelon tomato `
屢次使用**
也能夠達到類似的效果:
>>> date_info = {'year': "2020", 'month': "01", 'day': "01"}
>>> track_info = {'artist': "Beethoven", 'title': 'Symphony No 5'}
>>> filename = "{year}-{month}-{day}-{artist}-{title}.txt".format(
... **date_info,
... **track_info,
... )
>>> filename
'2020-01-01-Beethoven-Symphony No 5.txt'
不過,在屢次使用**
時須要特別當心。Python 中的函數不能屢次指定相同的關鍵字參數,所以在每一個字典中與**
一塊兒使用的鍵必須可以相互區分,不然會引起異常。
在定義函數時,*
運算符可用於捕獲傳遞給函數的位置參數。位置參數的數量不受限制,捕獲後被存儲在一個元組中。
from random import randint
def roll(*dice):
return sum(randint(1, die) for die in dice)
這個函數接受的參數數量不受限制:
>>> roll(20)
18
>>> roll(6, 6)
9
>>> roll(6, 6, 6)
8
Python 的print
和zip
函數接受的位置參數數量不受限制。*
的這種參數壓縮用法,容許咱們建立像print
和zip
同樣的函數,接受任意數量的參數。
**
運算符也有另一個功能:咱們在定義函數時,可使用**
捕獲傳進函數的任何關鍵字參數到一個字典當中:
def tag(tag_name, **attributes):
attribute_list = [
f'{name}="{value}"'
for name, value in attributes.items()
]
return f"<{tag_name} {' '.join(attribute_list)}>"
**
將捕獲咱們傳入這個函數中的任何關鍵字參數,並將其放入一個字典中,該字典將引用attributes
參數。
>>> tag('a', href="http://treyhunner.com")
'<a href="http://treyhunner.com">'
>>> tag('img', height=20, width=40, src="face.jpg")
'<img height="20" width="40" src="face.jpg">'
在 Python 3 中,咱們如今擁有了一種特殊的語法來接受只有關鍵字的函數參數。只有關鍵字的參數是
在定義函數時,爲了接受只有關鍵字的參數,咱們能夠將命名參數放在*
後:
def get_multiple(*keys, dictionary, default=None):
return [
dictionary.get(key, default)
for key in keys
]
上面的函數能夠像這樣使用:
>>> fruits = {'lemon': 'yellow', 'orange': 'orange', 'tomato': 'red'}
>>> get_multiple('lemon', 'tomato', 'squash', dictionary=fruits,default='unknown')
['yellow', 'red', 'unknown']
參數dictionary
和default
在*keys
後面,這意味着它們
>>> fruits = {'lemon': 'yellow', 'orange': 'orange', 'tomato': 'red'}
>>> get_multiple('lemon', 'tomato', 'squash', fruits, 'unknown')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: get_multiple() missing 1 required keyword-only argument: 'dictionary'
這種行爲是經過 PEP 3102 被引入到 Python 中的。
只使用關鍵字參數的特性很酷,可是若是您但願只使用關鍵字參數而不捕獲無限的位置參數呢?
Python 使用一種有點奇怪的 單獨*
語法來實現:
def with_previous(iterable, *, fillvalue=None):
"""Yield each iterable item along with the item before it."""
previous = fillvalue
for item in iterable:
yield previous, item
previous = item
這個函數接受一個迭代器參數,能夠按照位置或名字來指定此參數(做爲第一個參數),以及關鍵字參數fillvalue,這個填充值參數只使用關鍵字。這意味着咱們能夠像下面這樣調用 with_previous:
>>> list(with_previous([2, 1, 3], fillvalue=0))
[(0, 2), (2, 1), (1, 3)]
但像這樣就不能夠:
>>> list(with_previous([2, 1, 3], 0))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: with_previous() takes 1 positional argument but 2 were given `
這個函數接受兩個參數,其中fillvalue
參數
我一般在獲取任意數量的位置參數時只使用關鍵字參數,但我有時使用這個*
強制按照位置指定一個參數。
實際上,Python 的內置sorted
函數使用了這種方法。若是你查看sorted
的幫助信息,將看到如下信息:
>>> help(sorted)
Help on built-in function sorted in module builtins:
sorted(iterable, /, *, key=None, reverse=False)
Return a new list containing all items from the iterable inascending order.
A custom key function can be supplied to customize the sort order, and the
reverse flag can be set to request the result in descending order.
在sorted
的官方說明中,有一個單獨的*
參數。
Python 3 還新添了一種 *
運算符的使用方式,它只與上面定義函數時和調用函數時*
的使用方式相關。
如今,*
操做符也能夠用於元組拆包:
>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']
>>> first, second, *remaining = fruits
>>> remaining
['watermelon', 'tomato']
>>> first, *remaining = fruits
>>> remaining
['pear', 'watermelon', 'tomato']
>>> first, *middle, last = fruits
>>> middle
['pear', 'watermelon']
若是你想知道什麼狀況下能夠在你本身的代碼中使用它,請查看我關於 Python 中的 tuple 解包 文章中的示例。在那篇文章中,我將展現如何使用*
操做符做爲序列切片的替代方法。
一般當我教*
的時候,我告訴你們只能在多重賦值語句中使用一個*
表達式。實際來講這是不正確的,由於能夠在嵌套解包中使用兩個*
(我在元組解包文章中討論了嵌套解包):
>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']
>>> first, second, *remaining = fruits
>>> remaining
['watermelon', 'tomato']
>>> first, *remaining = fruits
>>> remaining
['pear', 'watermelon', 'tomato']
>>> first, *middle, last = fruits
>>> middle
['pear', 'watermelon']
可是,我歷來沒見過它有什麼實際用處,即便你由於它看起來有點神祕而去尋找一個例子,我也並不推薦這種使用方式。
將此添加到 Python 3.0 中的 PEP 是 PEP 3132,其篇幅不是很長。
Python 3.5 經過 PEP 448 引入了大量與*
相關的新特性。其中最大的新特性之一是可以使用*
將迭代器轉儲到新列表中。
假設你有一個函數,它以任一序列做爲輸入,返回一個列表,其中該序列和序列的倒序鏈接在了一塊兒:
def palindromify(sequence):
return list(sequence) + list(reversed(sequence))
此函數須要屢次將序列轉換爲列表,以便鏈接列表並返回結果。在 Python 3.5 中,咱們能夠這樣編寫函數:
def palindromify(sequence):
return [*sequence, *reversed(sequence)]
這段代碼避免了一些沒必要要的列表調用,所以咱們的代碼更高效,可讀性更好。
下面是另外一個例子:
def rotate_first_item(sequence):
return [*sequence[1:], sequence[0]]
該函數返回一個新列表,其中給定列表(或其餘序列)中的第一項被移動到了新列表的末尾。
*
運算符的這種使用是將不一樣類型的迭代器鏈接在一塊兒的好方法。*
運算符適用於鏈接任何種類的迭代器,然而 +
運算符只適用於類型都相同的特定序列。
除了建立列表存儲迭代器之外,咱們還能夠將迭代器轉儲到新的元組或集合中:
>>> fruits = ['lemon', 'pear', 'watermelon', 'tomato']
>>> (*fruits[1:], fruits[0])
('pear', 'watermelon', 'tomato', 'lemon')
>>> uppercase_fruits = (f.upper() for f in fruits)
>>> {*fruits, *uppercase_fruits}
{'lemon', 'watermelon', 'TOMATO', 'LEMON', 'PEAR','WATERMELON', 'tomato', 'pear'}
注意,上面的最後一行使用了一個列表和一個生成器,並將它們轉儲到一個新的集合中。在此以前,並無一種簡單的方法能夠在一行代碼中完成這項工做。曾經有一種方法能夠作到這一點,但是並不容易被記住或發現:
PEP 448 還經過容許將鍵/值對從一個字典轉儲到一個新字典擴展了**
操做符的功能:
>>> date_info = {'year': "2020", 'month': "01", 'day': "01"}
>>> track_info = {'artist': "Beethoven", 'title': 'Symphony No 5'}
>>> all_info = {**date_info, **track_info}
>>> all_info
{'year': '2020', 'month': '01', 'day': '01', 'artist': 'Beethoven', 'title':'Symphony No 5'}
我還寫了另外一篇文章:在Python中合併字典的慣用方法。
不過,**
操做符不只僅能夠用於合併兩個字典。
例如,咱們能夠在複製一個字典的同時添加一個新值:
>>> date_info = {'year': '2020', 'month': '01', 'day': '7'}
>>> event_info = {**date_info, 'group': "Python Meetup"}
>>> event_info
{'year': '2020', 'month': '01', 'day': '7', 'group': 'Python Meetup'}
或者在複製/合併字典的同時重寫特定的值:
>>> event_info = {'year': '2020', 'month': '01', 'day': '7', 'group':'Python Meetup'}
>>> new_info = {**event_info, 'day': "14"}
>>> new_info
{'year': '2020', 'month': '01', 'day': '14', 'group': 'Python Meetup'}
Python 的 *
和 **
運算符不只僅是語法糖。 *
和 **
運算符容許的某些操做能夠經過其餘方式實現,可是每每更麻煩和更耗費資源。並且 *
和 **
運算符提供的某些特性沒有替代方法實現:例如,函數在不使用 *
時就沒法接受任意數量的位置參數。
在閱讀了*
和 **
運算符的全部特性以後,您可能想知道這些奇怪操做符的名稱。不幸的是,它們的名字並不簡練。我據說過*
被稱爲「打包」和「拆包「運算符。我還據說過其被稱爲「splat」(來自 Ruby 世界),也據說過被簡單地稱爲「star」。
我傾向於稱這些操做符爲「星」和「雙星」或「星星」。這種叫法並不能區分它們和它們的中綴關係(乘法和指數運算),可是一般咱們能夠從上下文清楚地知道是在討論前綴運算符仍是中綴運算符。
請勿在不理解*
和 **
運算符的前提下記住它們的全部用法!這些操做符有不少用途,記住每種操做符的具體用法並不重要,重要的是瞭解你什麼時候可以使用這些操做符。我建議使用這篇文章做爲一個備忘單或者製做你本身的備忘單來幫助你在 Python 中使用解*
和 **
。
想了解更多關於 Python 的知識嗎?我每週經過實時聊天分享我最喜歡的 Python 資源、回答 Python 問題。
尚學堂推出《13天搞定Python網絡爬蟲》視頻教程,學習成爲Python爬蟲工程師,薪資槓槓的!