Python基礎手冊22——函數(shù)的參數(shù)

二、函數(shù)的參數(shù)

在 Python 中,兩種類型的參數(shù):在函數(shù)定義時函數(shù)名后面的括號中定義的參數(shù),我們稱之為 形參;在函數(shù)調用時,函數(shù)名后面的括號中傳遞的參數(shù),我們稱之為 實參。

形參只有在被調用時才分配內存單元,在調用結束時,即刻釋放所分配的內存單元。因此形參只在函數(shù)內部有效,形參的作用域只限于函數(shù)內,函數(shù)調用結束返回主調函數(shù)后則不能再使用該形參變量。

實參可以是變量名或對象,無論實參是何種類型,在進行函數(shù)調用時,他們都必須有確定的值,以便把這些值賦值給形參。

在這里我們明確形參和實參的概念,以便于我們下面對參數(shù)的定義和賦值進行更清晰、更深層次的理解。


1、參數(shù)的傳遞

在 Python 中,函數(shù)參數(shù)(argument,也叫作 parameter)的傳遞是通過自動將對象(實參)賦值給參數(shù)名(形參)來實現(xiàn)的,也就是將實參所指向的對象(或者其本身就是一個對象)的引用賦值給形參。

函數(shù)參數(shù)的賦值,在實際中只是 Python 賦值的另一個實例而已。因為引用是以指針的形式實現(xiàn)的,所有的參數(shù)實際上都是通過指針進行傳遞的。作為參數(shù)被傳遞的對象從來不會進行拷貝。

函數(shù)參數(shù)的賦值和創(chuàng)建并不會影響調用者程序中的變量名(而不是變量名所指向的對象)的作用域和使用。因為在函數(shù)運行時,在函數(shù)頭部的參數(shù)名(形參)是一個新的、本地的變量名,這個變量名是在函數(shù)的本地作用域內的。

如果賦值給函數(shù)參數(shù)的是可變對象,那么改變參數(shù)的值會對調用者有影響。因為對象是簡單的通過引用賦值給函數(shù)的參數(shù),函數(shù)能夠就地改變傳入的可變對象,因此其結果會影響調用者。可變參數(shù)對于函數(shù)來說是可以做輸入和輸出的。

像整數(shù)和字符串這樣的不可變對象是不可以原處修改的,因此如果函數(shù)修改了傳入的不可變對象,實際的效果就是用新的對象重新賦值給了函數(shù)參數(shù)。


如何避免可變參數(shù)的修改

對可變參數(shù)原處修改的行為不是一個bug——它只是參數(shù)傳遞在Python中的工作方式。在Python中,默認通過引用(也就是指針)進行函數(shù)的參數(shù)傳遞,是因為這通常是我們想要的:這意味著不需要創(chuàng)建多個對象的拷貝(只用將對象指向新的變量名)就可以在我們的程序中傳遞很大的對象,并且能夠按照需要方便的更新這些對象。

如果不想要函數(shù)內部在原處的修改影響傳遞給它的對象,那么,我們可以簡單地創(chuàng)建一個明確的可變對象的拷貝。對于函數(shù)參數(shù),我們總是能夠在調用時針對列表進行拷貝。

如果不想改變傳入的對象,無論函數(shù)是如何調用的,我們同樣可以在函數(shù)內部進行拷貝。

這兩種拷貝的機制都不會阻止函數(shù)改變對象:這樣做僅僅是防止了這些改變會影響調用者。為了真正意義上防止這些改變,我們總是能夠將可變對象轉換為不可變對象來杜絕這種問題。

這種方法從某種意義上來說有些過于極端:因為這種方法強制函數(shù)寫成絕不改變傳入?yún)?shù)的樣子,這種辦法強制對函數(shù)比原本應該的進行了更多的限制,所以通常意義下應該避免出現(xiàn)。或許將來你會發(fā)現(xiàn)對于一些調用來說改變參數(shù)是有用的一件事情。使用這種技術會讓函數(shù)失去一種參數(shù)能夠調用任意列表特定方法的能力,包括不會在原處改變對象的那些方法都不再能夠使用。

這里最需要記住的就是,函數(shù)能夠升級為傳入可變對象(例如,列表和字典)
的形式,這不會是一個問題,并且有時候這對于有些用途很有用處。此外,原處修改傳入的可變對象的函數(shù),可能是為此而設計并有意而為之——修改可能是一個定義良好的API的一部分,而我們不應該通過產(chǎn)生副本來違反改API。


2、函數(shù)定義時參數(shù)的類型

這里我們主要介紹的是 Python 的形參。

2.1 標準參數(shù)

就是我們最常用的參數(shù),在函數(shù)定義時,直接以變量名的形式定義。在函數(shù)調用時,標準參數(shù)必須提供,否則會報錯。


2.2 默認參數(shù)

函數(shù)能夠為參數(shù)定義接收的默認值,在函數(shù)定義中, 默認參數(shù)以賦值語句 name=value 的形式提供。在函數(shù)調用時如果沒有提供這個參數(shù), 它就使用這個默認值。

python 中所有的標準參數(shù)必須出現(xiàn)在任何一個默認參數(shù)之前。如果一個參數(shù)具有默認值,所有隨后的參數(shù)直到 “*” 號也必須具有默認值 —— 這個限制在語法中沒有表達出來的。

在函數(shù)定義被執(zhí)行時時,Python 會從左到右的計算默認參數(shù)值。這意味著當函數(shù)被定義,默認參數(shù)已經(jīng)被創(chuàng)建并被賦值了,如果在以后的調用中沒有為默認參數(shù)重新賦值(重新指向新的對象),那么它將一直使用函數(shù)定義時創(chuàng)建的值(對象)。理解這點對于默認參數(shù)是可變對象時特別重要,例如列表或字典:如果函數(shù)在調用時修改了該對象(例如,向列表添加一個元素),默認值將受影響被修改,這將會影響函數(shù)的下一次調用。這通常不是想要的。你可以將默認參數(shù)指定為不可變對象或者在函數(shù)中避免修改默認參數(shù)。

默認參數(shù)是在 def 語句運行時評估并保存的,而不是在這個函數(shù)調用時。從內部來講,Python會將每一個默認參數(shù)保存成一個對象,附加在這個函數(shù)本身。

因為默認參數(shù)是在 def 時被評估的,如果必要的話,它能夠從整個作用域(函數(shù)定義的作用域)中保存值,但是因為默認參數(shù)在調用之間都保存了一個對象,必須對修改可變的默認參數(shù)十分小心。

有些人把這種行為當做一種特性。因為可變類型的默認參數(shù)在函數(shù)調用之間保存了他們的狀態(tài),從某種意義上講它們能夠充當C語言中的靜態(tài)本地函數(shù)變量的角色。在一定程度上,它們工作起來就像全局變量,但是它們的變量名對于函數(shù)來說是本地變量,而且不會與程序中的其他變量名發(fā)生沖突。

為什么使用默認參數(shù)

默認參數(shù)讓程序的健壯性上升到極高的級別,因為它們補充了標準位置參數(shù)沒有提供的一些靈活性。當少幾個需要操心的參數(shù)時候,生活不再那么復雜。利用默認參數(shù)你可以為你的API 接口提供一個常用的默認配置,這在一個程序員剛接觸到一個 API 接口并沒有足夠的知識來給參數(shù)提供更對口的值時顯得尤為有幫助。

使用默認參數(shù)的概念與在你的電腦上安裝軟件的過程類似。一個人會有多少次選擇默認安裝而不是自定義安裝?我可以說可能幾乎都是默認安裝。這既方便,易于操作,又能節(jié)省時間。如果你是那些總是選擇自定義安裝的頑固分子,請記著你只是少數(shù)人之一

另外一個讓開發(fā)者受益的地方在于,使開發(fā)者更好地控制為顧客開發(fā)的軟件。當提供了默認值的時候,他們可以精心選擇“最佳“的默認值,所以用戶不需要馬上面對繁瑣的選項。隨著時間流逝,當用戶對系統(tǒng)或者 api 越來越熟悉的時候,他們最終能自行給出參數(shù)值,便不再需要使用“學步車“了。


2.3 可變長參數(shù)(參數(shù)組)

在Python中可以通過定義參數(shù)組,從而讓函數(shù)可以接收任意多額外沒有匹配的基于位置或關鍵字的參數(shù)。基本上,你可以將所有參數(shù)放進參數(shù)組中,僅僅用這些裝有參數(shù)的容器作為參數(shù)來調用一個函數(shù),而不必顯式地將所有的參數(shù)都放在函數(shù)調用中。

(1)非關鍵字參數(shù)(元組)

當函數(shù)被調用的時候,調用者傳遞的位置參數(shù)(實參)都將按照順序賦給了在函數(shù)聲明中相對應參數(shù)(形參)。剩下的非關鍵字參數(shù)按順序插入到一個元組中以便于訪問。可變長的參數(shù)元組必須在位置參數(shù)和默認參數(shù)之后。

在函數(shù)定義時,使用帶 * 號的參數(shù)名(通常我們命名為 args)把收集到的不匹配的位置參數(shù)包含在一個元組中作為參數(shù)傳遞給函數(shù)。


(2)關鍵字參數(shù)(字典)

在函數(shù)調用時,當有額外的關鍵字參數(shù)(實參)沒有與函數(shù)定義的參數(shù)(形參)相匹配時,這些關鍵字參數(shù)會被放入一個字典中,字典中鍵為參數(shù)名,值為相應的參數(shù)值。
在函數(shù)定義時,使用帶 ** 號的參數(shù)名(通常我們命名為 kwargs)把接收的關鍵字參數(shù)包含在一個字典中作為參數(shù)傳遞給函數(shù)。


2.4 Keyword-only 參數(shù)

Python3.0 把函數(shù)頭部的排序規(guī)則通用化了,并允許我們指定 keyword-only 參數(shù)(形參)——即必須只按照關鍵字傳遞并且不能由一個位置參數(shù)(實參)來填充的參數(shù)(可以理解成必須按照關鍵字形式賦值的默認參數(shù))。如果想要一個函數(shù)只能按照關鍵字形式來傳遞配置選項的話,這是很有用的。

從語法上將,keyword-only 參數(shù)編碼為命名的參數(shù),出現(xiàn)在參數(shù)(形參)列表中的 *args 之后。所有這些參數(shù)都必須在調用中使用關鍵字語法來傳遞。

例如,如下代碼中,a可能按照名稱或位置傳遞,b收集任何額外的位置參數(shù),并且c必須只按照關鍵字傳遞。

我們也可以在參數(shù)列表中使用一個字符,來表示一個函數(shù)不會接受一個變量長度的參數(shù)列表,而是仍然期待跟在后面的所有參數(shù)都作為關鍵字傳遞。

仍然可以對 keyword-only 參數(shù)使用默認值,即便它們出現(xiàn)在函數(shù)頭部的 * 后面。


排序規(guī)則

keyword-only 參數(shù)必須在一個單個星號后面指定,而不是兩個星號——命名的參數(shù)不能出現(xiàn)在 **kwargs 形式的后面,并且一個 ** 不能獨自出現(xiàn)在參數(shù)列表中。

這意味著,在一個函數(shù)頭部,keyword-only 參數(shù)必須編寫在 **kwargs 參數(shù)之前,且在 *args 參數(shù)之后(當二者都有的時候)。無論何時,一個參數(shù)名稱出現(xiàn)在 *args 之前,它可能是默認位置參數(shù),而不會是 keyword-only 參數(shù)。

在函數(shù)調用中,類似的排序規(guī)則也是成立的:當傳遞 keyword-only 參數(shù)的時候,它們必須出現(xiàn)在一個 **name(實參) 之前。keyword-only 參數(shù)(實參)可以放在在 *name(實參) 之前或者之后,并且可能包含在 **args 中。


為何使用 keyword-only 參數(shù)

簡而言之,它們使得很容易允許一個函數(shù)既接收任意多個要處理的位置參數(shù),也接收作為關鍵字傳遞的配置選項。


3、參數(shù)的排列順序

在函數(shù)定義的頭部,參數(shù)必須以此順序出現(xiàn):標準參數(shù)(name),默認參數(shù)(name=value),如果有的話,后面是 *args 的形式,后面跟著任何 name=value 的keyword-only 參數(shù),后面跟著 **kwargs 參數(shù)。


《Python基礎手冊》系列:

Python基礎手冊 1 —— Python語言介紹
Python基礎手冊 2 —— Python 環(huán)境搭建(Linux)
Python基礎手冊 3 —— Python解釋器
Python基礎手冊 4 —— 文本結構
Python基礎手冊 5 —— 標識符和關鍵字
Python基礎手冊 6 —— 操作符
Python基礎手冊 7 —— 內建函數(shù)
Python基礎手冊 8 —— Python對象
Python基礎手冊 9 —— 數(shù)字類型
Python基礎手冊10 —— 序列(字符串)
Python基礎手冊11 —— 序列(元組&列表)
Python基礎手冊12 —— 序列(類型操作)
Python基礎手冊13 —— 映射(字典)
Python基礎手冊14 —— 集合
Python基礎手冊15 —— 解析
Python基礎手冊16 —— 文件
Python基礎手冊17 —— 簡單語句
Python基礎手冊18 —— 復合語句(流程控制語句)
Python基礎手冊19 —— 迭代器
Python基礎手冊20 —— 生成器
Python基礎手冊21 —— 函數(shù)的定義
Python基礎手冊22 —— 函數(shù)的參數(shù)
Python基礎手冊23 —— 函數(shù)的調用
Python基礎手冊24 —— 函數(shù)中變量的作用域
Python基礎手冊25 —— 裝飾器
Python基礎手冊26 —— 錯誤 & 異常
Python基礎手冊27 —— 模塊
Python基礎手冊28 —— 模塊的高級概念
Python基礎手冊29 —— 包

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容