一、形參與實參介紹
函數的參數分為形式參數和實際參數,簡稱形參和實參:
形參即在定義函數時,括號內聲明的參數。形參本質就是一個變量名,用來接收外部傳來的值。
實參即在調用函數時,括號內傳入的值,值可以是常量、變量、表達式或三者的組合:
-
實參是常量
res = my_min(1, 2)
-
實參是變量
a = 1 b = 2 res = my_min(a, b)
-
實參是表達式
res = my_min(10 * 2, 10 * my_min(3, 4))
-
實參可以是常量、變量、表達式的任意組合
a = 2 my_min(1, a, 10 * my_min(3, 4))
在調用有參函數時,實參(值)會賦值給形參(變量名)。在Python中,變量名與值只是單純的綁定關系,而對於函數來說,這種綁定關系只在函數調用時生效,在調用結束后解除。
二、形參與實參的具體使用
2.1 位置參數
位置即順序,位置參數指的是按順序定義的參數,需要從兩個角度去看:
-
在定義函數時,按照從左到右的順序依次定義形參,稱為位置形參,凡是按照這種形式定義的形參都必須被傳值:
def register(name, age, sex): # 定義位置形參:name,age,sex,三者都必須被傳值 print('Name:%s Age:%s Sex:%s' % (name, age, sex)) register() # TypeError:缺少3個位置參數
-
在調用函數時,按照從左到右的順序依次定義實參,稱為位置實參,凡是按照這種形式定義的實參會按照從左到右的順序與形參一一對應:
>>> register('lili', 18, 'male') Name:lili Age:18 Sex:male
對應關系為:name = 'lili', age = 18, sex = 'male'
2.2 關鍵字參數
在調用函數時,實參可以是key=value的形式,稱為關鍵字參數,凡是按照這種形式定義的實參,可以完全不按照從左到右的順序定義,但仍能為指定的形參賦值:
>>> register(sex='male', name='lili', age=18)
Name:lili Age:18 Sex:male
需要注意在調用函數時,實參也可以是按位置或按關鍵字的混合使用,但必須保證關鍵字參數在位置參數后面,且不可以對一個形參重復賦值:
>>> register('lili', sex='male', age=18) # 正確使用
>>> register(name='lili', 18, sex='male') # SyntaxError:關鍵字參數name=‘lili’在位置參數18之前
>>> register('lili', sex='male', age=18, name='jack') # TypeError:形參name被重復賦值
2.3 默認參數
在定義函數時,就已經為形參賦值,這類形參稱之為默認參數。當函數有多個參數時,需要將值經常改變的參數定義成位置參數,而將值改變較少的參數定義成默認參數。
例如編寫一個注冊學生信息的函數,如果大多數學生的性別都為男,那完全可以將形參sex定義成默認參數:
默認sex的值為male:
>>> def register(name, age, sex='male'):
... print('Name:%s Age:%s Sex:%s' % (name, age, sex))
...
定義時就已經為參數sex賦值,意味着調用時可以不對sex賦值,這降低了函數調用的復雜度:
大多數情況,無需為sex傳值,默認為male:
>>> register('tom', 17)
Name:tom Age:17 Sex:male
少數情況,可以為sex傳值female:
>>> register('Lili', 18, 'female')
Name:Lili Age:18 Sex:female
需要注意:
- 默認參數必須在位置參數之后;
- 默認參數的值僅在函數定義階段被賦值一次;
- 默認參數的值通常應設為不可變類型;
定義階段arg已被賦值為1,此處的修改與默認參數arg無任何關系:
>>> x = 1
>>> def foo(arg=x):
... print(arg)
...
>>> x = 5
>>> foo()
1
每次調用是在上一次的基礎上向同一列表增加值:
>>> def foo(n, arg=[]):
... arg.append(n)
... return arg
...
>>> foo(1)
[1]
>>> foo(2)
[1, 2]
>>> foo(3)
[1, 2, 3]
解決上述問題的代碼(修改后)如下:
>>> def foo(n, arg=None):
... if arg is None:
... arg = []
... arg.append(n)
... return arg
...
>>> foo(1)
[1]
>>> foo(2)
[2]
>>> foo(3)
[3]
2.4 可變長參數(*與**的用法)
參數的長度可變指的是在調用函數時,實參的個數可以不固定,而在調用函數時,實參的定義無非是按位置或者按關鍵字兩種形式,這就要求形參提供兩種解決方案來分別處理兩種形式的可變長度的參數。
2.4.1 可變長度的位置參數
如果在最后一個形參名前加*號,那么在調用函數時,溢出的位置實參,都會被接收,並組織成元組的形式保存下來賦值給該形參:
在最后一個形參名args前加*號:
>>> def foo(x, y, z=1, *args):
... print(x)
... print(y)
... print(z)
... print(args)
...
>>> foo(1, 2, 3, 4, 5, 6, 7)
1
2
3
(4, 5, 6, 7)
"""
實參1、2、3按位置為形參x、y、z賦值,多余的位置實參4、5、6、7都被*接收,以元組的形式保存下來,賦值給args,即args = (4, 5, 6,7)
"""
如果我們事先生成了一個列表,仍然是可以傳值給*args的:
>>> def foo(x, y, *args):
... print(x)
... print(y)
... print(args)
...
>>> L = [3, 4, 5]
>>> foo(1, 2, *L)
1
2
(3, 4, 5)
"""
*L就相當於位置參數3,4,5, foo(1, 2, *L)就等同於foo(1, 2, 3, 4, 5)
"""
注意:如果在傳入L時沒有加*,那L只是一個普通的位置參數了
>>> foo(1, 2, L) # 僅多出一個位置實參L
1
2
([3, 4, 5],)
如果形參為常規的參數(位置或默認),實參仍可以是*的形式:
>>> def foo(x, y, z=3):
... print(x)
... print(y)
... print(z)
...
>>> foo(*[1, 2]) # 等同於foo(1, 2)
1
2
3
如果我們想要求多個值的和,*args就派上用場了:
>>> def add(*args):
... res = 0
... for i in args:
... res += i
... return res
...
>>> add(1, 2, 3, 4, 5)
15
2.4.2 可變長度的關鍵字參數
如果在最后一個形參名前**加號,那么在調用函數時,溢出的關鍵字參數,都會被接收,並組織成字典的形式保存下來賦值給該形參:
在最后一個參數kwargs前加**:
>>> def foo(x, **kwargs):
... print(x)
... print(kwargs)
...
>>> foo(y=2, x=1, z=3)
1
{'y': 2, 'z': 3}
"""
溢出的關鍵字實參y=2,z=3都被**接收,以字典的形式保存下來,賦值給kwargs
"""
如果我們事先生成了一個字典,仍然是可以傳值給**kwargs的:
>>> def foo(x, y, **kwargs):
... print(x)
... print(y)
... print(kwargs)
...
>>> dic = {'a':1, 'b':2}
>>> foo(1, 2, **dic)
1
2
{'a': 1, 'b': 2}
"""
**dic就相當於關鍵字參數a=1,b=2,foo(1, 2, **dic)等同foo(1, 2, a=1, b=2)
"""
注意:如果在傳入dic時沒有加**,那dic就只是一個普通的位置參數了
>>> foo(1, 2, dic) # TypeError:函數foo只需要2個位置參數,但是傳了3個
如果形參為常規參數(位置或默認),實參仍可以是**的形式:
>>> def foo(x, y, z=3):
... print(x)
... print(y)
... print(z)
...
>>> foo(**{'x':1, 'y':2}) # 等同於foo(x=1, y=2)
1
2
3
如果我們要編寫一個用戶認證的函數,起初可能只基於用戶名密碼的驗證就可以了,可以使用**kwargs為日后的擴展提供良好的環境,同時保持了函數的簡潔性。
>>> def auth(user, password, **kwargs):
... pass
...
2.5 命名關鍵字參數
在定義了**kwargs參數后,函數調用者就可以傳入任意的關鍵字參數key=value,如果函數體代碼的執行需要依賴某個key,必須在函數內進行判斷:
>>> def register(name, age, **kwargs):
... if 'sex' in kwargs:
... # 有sex參數
... pass
... if 'height' in kwargs:
... # 有height參數
... pass
...
想要限定函數的調用者必須以key=value的形式傳值,Python3提供了專門的語法:需要在定義形參時,用*作為一個分隔符號,之后的形參稱為命名關鍵字參數。
對於這類參數,在函數調用時,必須按照key=value的形式為其傳值,且必須被傳值。
sex,height為命名關鍵字參數:
>>> def register(name, age, *, sex, height):
... pass
...
正確使用:
>>> register('lili', 18, sex='male', height='1.8m')
錯誤使用:
>>> register('lili', 18, 'male', '1.8m') # TypeError:未使用關鍵字的形式為sex和height傳值
>>> register('lili', 18, height='1.8m') # TypeError:沒有為命名關鍵字參數sex傳值
命名關鍵字參數也可以有默認值,從而簡化調用:
>>> def register(name, age, *, sex='male', height):
... print('Name:%s,Age:%s,Sex:%s,Height:%s' % (name, age, sex, height))
...
>>> register('lili', 18, height='1.8m')
Name:lili,Age:18,Sex:male,Height:1.8m
需要強調的是:sex不是默認參數,height也不是位置參數,因為二者均在※號后,所以都是命名關鍵字參數,形參sex=‘male’屬於命名關鍵字參數的默認值,因而即便是放到形參height之前也不會有問題。另外,如果形參中已經有一個*args了,命名關鍵字參數就不再需要一個單獨的※號作為分隔符號了。
sex與height仍為命名關鍵字參數:
>>> def register(name, age, *args, sex='male', height):
... print('Name:%s,Age:%s,Args:%s,Sex:%s,Height:%s' % (name, age, args, sex, height))
...
>>> register('lili', 18, 1, 2, 3, height='1.8m')
Name:lili,Age:18,Args:(1, 2, 3),Sex:male,Height:1.8m
2.6 組合使用
綜上所述所有參數可任意組合使用,但定義順序必須是:位置參數、默認參數、*args、命名關鍵字參數、**kwargs;
可變參數*args與關鍵字參數**kwargs通常是組合在一起使用的;
如果一個函數的形參為*args與**kwargs,那么代表該函數可以接收任何形式、任意長度的參數:
>>> def wrapper(*args, **kwargs):
... pass
...
在該函數內部還可以吧接收到的參數傳給另外一個函數(這在后面裝飾器的實現中大有用處):
>>> def func(x, y, z):
... print(x, y, z)
...
>>> def wrapper(*args, **kwargs):
... func(*args, **kwargs)
...
>>> wrapper(1, z=3, y=2)
1 2 3
按照上述寫法,在為函數wrapper傳參時,其實遵循的是函數func的參數規則,調用函數wrapper的過程分析如下:
-
位置1被*接收,以元組的形式保存下來,賦值給args,即args=(1,),關鍵字實參z=3,y=2被**接收,以字典的形式保存下來,賦值給kwargs,即kwargs = {'y': 2, 'z': 3};
-
執行func(*args, **kwargs)
即func(*(1,), **{'y': 2, 'z': 3}),等同於func(1, y=2, z=3);
提示:*args、**kwargs中的args和kwargs被替換成其他名字並無語法錯誤,但在Python中推薦使用args、kwargs,是約定俗成的。