本次我要給大家分享一些更加高級(jí)和不常見的函數(shù)定義與使用模式。
涉及到的內(nèi)容包括默認(rèn)參數(shù)命锄、任意數(shù)量參數(shù)、強(qiáng)制關(guān)鍵字 參數(shù)偏化、注解和閉包等脐恩。
*
表達(dá)式
我們知道在定義函數(shù)的時(shí)候,用 *
表達(dá)式來收集所有未指明的位置參數(shù)侦讨。
def avg(first, *rest):
"""求平均值"""
return (first + sum(rest)) / (1 + len(rest))
# 簡單使用
avg(1, 2) # 1.5
avg(1, 2, 3, 4) # 2.5
當(dāng)然在調(diào)用函數(shù)的時(shí)候驶冒,也可以使用 *
表達(dá)式來把一個(gè)序列類型數(shù)據(jù)中的元素一一解開。
def show_args(*args):
print(args)
show_args(['a','b']) # (['a', 'b'],)
show_args(*['a','b']) # ('a', 'b')
print()
函數(shù)的另外一種用法
print(['a','b'], sep='\n')
# ['a', 'b']
print(*['a','b'], sep='\n')
# a
# b
print(*'ab', sep='\n')
# a
# b
其實(shí) print()
函數(shù)的原型是這樣定義的
def print(self, *args, sep=' ', end='\n', file=None):
pass
print()
函數(shù)中的sep
關(guān)鍵字參數(shù)定義的是當(dāng)打印多個(gè)參數(shù)時(shí)韵卤,它們中間的分隔符是什么
**
表達(dá)式
def foo(**kwargs):
print(kwargs) # 是一個(gè)字典
一個(gè)
*
參數(shù)只能出現(xiàn)在函數(shù)定義中最后一個(gè)位置參數(shù)的后面骗污,而**
參數(shù)只能出現(xiàn)在最后一個(gè)參數(shù)都位置。
有一點(diǎn)要注意的是沈条,在*
參數(shù)后面其實(shí)還可以定義其他參數(shù)需忿。下面就會(huì)用到。
在函數(shù)中實(shí)現(xiàn)強(qiáng)制關(guān)鍵字參數(shù)
有的時(shí)候你希望在調(diào)用函數(shù)的時(shí)候,必須用關(guān)鍵字參數(shù)屋厘,因?yàn)檫@樣更易懂涕烧。
可以將要限定的強(qiáng)制關(guān)鍵字參數(shù)放到某個(gè) *參數(shù)
或者單個(gè) *
后面就能達(dá)到這種效果。
像下面這樣汗洒。
def query_keyword(max_file, *, servers):
pass
query_keyword(65535, servers=False) # ok
query_keyword(65535) # TypeError
使用函數(shù)注解
函數(shù)注解是為了讓看這個(gè)函數(shù)源碼的人更能清楚參數(shù)的類型和用法
def add(x:int, y:int) -> int:
return x + y
python
解釋器不會(huì)對(duì)這些注解添加任何的語義议纯。它們不會(huì)被類型檢查,運(yùn)行時(shí)跟 沒有加注解之前的效果也沒有任何差距仲翎。
這些注解存儲(chǔ)在函數(shù)的
__annotations__
屬性中痹扇。
print(add.__annotations__)
感受 return
的強(qiáng)大
def foo():
return '千鋒', 8, 1000000
name, *nums = foo()
print(name) # 千鋒
print(nums) # [8, 1000]
其實(shí)在函數(shù)返回之前铛漓,先創(chuàng)建了一個(gè)元組溯香,之后的賦值就是我們之前講的元組解包
在定義函數(shù)的默認(rèn)參數(shù)時(shí),不要用可變類型的數(shù)據(jù)
假如你的確需要一個(gè)默認(rèn)參數(shù)是一個(gè)可邊類型的數(shù)據(jù)(比如列表)
可以把默認(rèn)參數(shù)的值先定義為 None
def foo(a, b=None):
if b is None:
b = []
驗(yàn)證同一性
假如你想在一個(gè)函數(shù)中判斷使用者有沒有給一個(gè)參數(shù)傳參浓恶,你可能想到這樣:
def spam(a, b=None)
if not b: # 判斷是否是 False
print("用戶沒有傳遞變量")
這樣顯然會(huì)有問題的玫坛,因?yàn)閷?duì)于 python
來說, 長度為 0 的字符串 ''
、列表 []
包晰、元組 ()
湿镀、字典 {}
都會(huì)認(rèn)為是 False
, 并且數(shù)字 0
和 布爾值的 False
都認(rèn)為是假的伐憾。
也就是說用戶傳入這些參數(shù)是屬于合法的參數(shù)勉痴。
解決辦法:
_no_value = object()
def spam(a, b=_no_value):
if b is _no_value:
print("用戶沒有傳遞變量")
_no_value
是object()
的是個(gè)實(shí)例, 這樣可以判斷變量b
的值和_no_value
的值是否是同一個(gè)對(duì)象來判斷用戶是否傳入了值树肃。
匿名函數(shù)
在匿名函數(shù)中使用了一個(gè)變量的值蒸矛,這會(huì)是很有意思的一件事。
li = [lambda n=30: n for n in range(10)]
現(xiàn)在你回答下面幾個(gè)問題
-
li[0]
是什么類型的對(duì)象胸嘴?
li
是一個(gè)列表雏掠,其中的元素都是匿名函數(shù)
-
li[0]()
和li[1]()
分別都是什么值?
其實(shí)
li[0]()
和li[1]()
的值都是9
lambda
表達(dá)式中的n
是一個(gè)自由變量劣像,是在運(yùn)行時(shí)給其綁定值乡话,而不是在定義時(shí)就給其綁定值,這跟函數(shù)的默認(rèn)值參數(shù)定義是不同的耳奕。
因此绑青,在調(diào)用這個(gè)lambda
表達(dá)式的時(shí)候,n
的值是執(zhí)行時(shí)的值屋群。
還有要考慮到, 在 Python 中只有在函數(shù)中定義的變量才是局部變量时迫,其他都是全局的變量。for
循環(huán)到最后n
的值被綁定為9
谓晌, 所以li
所用函數(shù)中的n
的值在運(yùn)行時(shí)都是9
掠拳。
現(xiàn)在對(duì) lambda
函數(shù)稍作修改就會(huì)有不一樣的效果
funcs = [lambda n=n: n for n in range(10)]
funcs[0]() # 0
funcs[1]() # 1
等號(hào)左邊的
n
是函數(shù)的形參,右邊是函數(shù)的實(shí)參纸肉,實(shí)參也就是迭代的變量的值溺欧。
這里利用默認(rèn)參數(shù)喊熟,就可以在定義函數(shù)時(shí),把值綁定給變量姐刁。
未完, 待續(xù)...