這篇文章是偉兄給我的稿子,總結(jié)實(shí)用、到位。另外,歡迎訪問(wèn)并關(guān)注他的博客:https://jl-zhenlaixiaowei.blog.csdn.net/我曾經(jīng)和一個(gè)聰明的 Pythonista 結(jié)對(duì)編程,每次他輸入帶有可選或關(guān)鍵字參數(shù)的函數(shù)定義時(shí),他都會(huì)驚呼“argh!”和“kwargh!”。
要不然我們相處的很好,我猜想這就是學(xué)術(shù)界編程最終對(duì)人所帶來(lái)的影響吧。現(xiàn)在args和 kwargs參數(shù)仍然是 Python 中非常有用的特性,而且理解它們的威力將使您成為更有效的開(kāi)發(fā)人員。那么“args”和“kwargs”參數(shù)用來(lái)做什么呢?
它們?cè)试S一個(gè)函數(shù)接受可選參數(shù),因此你能夠在你的模塊和類里創(chuàng)建彈性APIs。In?[2]:?def?foo(required,?*args,?**kwargs):
???...:?????print(required)
???...:?????if?args:
???...:?????????print(args)
???...:?????if?kwargs:
???...:?????????print(kwargs)
上面的函數(shù)需要至少一個(gè)叫做“必須的”參數(shù),但是它也能接受額外的位置參數(shù)和關(guān)鍵字參數(shù)。如果我們調(diào)用帶有附加參數(shù)的函數(shù),參數(shù)將會(huì)收集額外的位置參數(shù)作為一個(gè)元組,因?yàn)檫@個(gè)參數(shù)的名字有一個(gè)*(單星號(hào))前綴。同樣地,kwargs將收集額外的關(guān)鍵字參數(shù)作為一個(gè)字典,因?yàn)檫@個(gè)參數(shù)名字有**(雙星號(hào))前綴。如果沒(méi)有附加參數(shù)被傳遞給函數(shù)。args 和 kwargs 可以為空。當(dāng)我們調(diào)用帶有參數(shù)的不同組合的函數(shù)時(shí),你會(huì)看到在args和kwargs內(nèi)部參數(shù)。Python如何收集它們,根據(jù)它們是否為位置參數(shù)或者關(guān)鍵字參數(shù)。代碼如下:In?[3]:?foo()
---------------------------------------------------------------------------
TypeError?????????????????????????????????Traceback?(most?recent?call?last)
-3-c19b6d9633cf>?in?
---->?1?foo()
TypeError:?foo()?missing?1?required?positional?argument:?'required'
In?[4]:?foo('hello')
hello
In?[5]:?foo('hello',?1,?2,?3)
hello
(1,?2,?3)
In?[6]:?foo('hello',?1,?2,?3,?key1='value',?key2=999)
hello
(1,?2,?3)
{'key1':?'value',?'key2':?999}
我要明確一下,調(diào)用args和kwargs參數(shù)是簡(jiǎn)單的命名慣例。像之前的例子里,如果稱他們*parms和**argv也可以。實(shí)際上語(yǔ)法分別是單星號(hào)(*)或者雙星號(hào)(**)。然而,我還是推薦你還是堅(jiān)持可接受的命名慣例以避免混淆。(而且每隔一段時(shí)間還有機(jī)會(huì)喊“argh!”和“kwargh!”)。## 轉(zhuǎn)發(fā)可選或者關(guān)鍵字參數(shù)
有可能從一個(gè)函數(shù)到另一個(gè)函數(shù)傳遞可選或者關(guān)鍵字參數(shù)。當(dāng)你調(diào)用要轉(zhuǎn)發(fā)參數(shù)的函數(shù)時(shí),你可以通過(guò)使用解包參數(shù)操作符*和**。在你傳遞之前這也給你一個(gè)機(jī)會(huì)修改參數(shù)。In?[8]:?def?foo(x,?*args,?**kwargs):
???...:?????kwarg['name']?=?'Alice'
???...:?????new_args?=?args?+?('extra',?)
???...:?????bar(x,?*new_args,?**kwargs)
這種技術(shù)對(duì)于子類化和編寫(xiě)包裝函數(shù)很有用。例如,您可以使用它來(lái)擴(kuò)展父類的行為,而不必在子類中復(fù)制其構(gòu)造函數(shù)的完整簽名。如果您使用的 API 可能會(huì)在您的控制之外發(fā)生變化,這會(huì)非常方便,示例代碼如下:In?[9]:?class?Car:
???...:?????def?__init__(self,?color,?mileage):
???...:?????????self.color?=?color
???...:?????????self.mileage?=?mileage
???...:?
In?[10]:?class?AlwaysBlueCar(Car):
????...:?????def?__init__(self,?*args,?**kwargs):
????...:?????????super().__init__(*args,?**kwargs)
????...:?????????self.color?=?'blue'
In?[12]:?AlwaysBlueCar('green',?48392).color
Out[12]:?'blue'
AlwaysBlueCar 構(gòu)造函數(shù)只是將所有參數(shù)傳遞給它的父類,然后覆蓋一個(gè)內(nèi)部屬性。這意味著如果父類構(gòu)造函數(shù)發(fā)生更改,AlwaysBlueCar 很有可能仍會(huì)按預(yù)期運(yùn)行。這里的缺點(diǎn)是 AlwaysBlueCar 構(gòu)造函數(shù)現(xiàn)在有一個(gè)相當(dāng)無(wú)用的簽名——如果不查找父類,我們不知道它需要什么參數(shù)。通常,您不會(huì)將這種技術(shù)用于您自己的類層次結(jié)構(gòu)。更有可能的情況是您想要修改或覆蓋某些您無(wú)法控制的外部類中的行為。但這總是危險(xiǎn)的領(lǐng)域,所以最好小心(否則你可能很快就會(huì)有另一個(gè)理由尖叫“argh!”)。這種技術(shù)可能有用的另一種情況是編寫(xiě)包裝函數(shù),例如裝飾器。在那里,您通常還希望接受要傳遞給包裝函數(shù)的任意參數(shù)。而且,如果我們可以在不必復(fù)制和粘貼原始函數(shù)簽名的情況下做到這一點(diǎn),那可能更易于維護(hù):import?functools
def?trace(f):
????...:[email protected](f)
????...:?????def?decorated_function(*args,?**kwargs):
????...:?????????print(f,?args,?kwargs)
????...:?????????result?=?f(*args,?**kwargs)
????...:?????????print(result)
????...:?????return?decorated_function
????
In?[11]:?@trace
????...:?def?greet(greeting,?name):
????...:?????return?'{},?{}!'.format(greeting,?name)
In?[14]:?greet('Hello',?'Bob')
0x7fefa69db700>?('Hello',?'Bob')?{}
Hello,?Bob!
使用像這樣的技術(shù),有時(shí)很難在使代碼足夠明確的想法和遵守不要重復(fù)自己(DRY)原則的想法之間取得平衡。這可能永遠(yuǎn)是一個(gè)艱難的選擇。如果你能從同事那里得到第二個(gè)意見(jiàn),我鼓勵(lì)你嘗試一下。