函數(shù)名其實就是指向一個函數(shù)對象的引用,完全可以把函數(shù)名賦給一個變量,相當于給這個函數(shù)起了一個“別名”:
a = ab//把函數(shù)賦給一個變量a print(a(-1))
1、定義函數(shù)
在Python中,定義一個函數(shù)要使用def語句,依次寫出函數(shù)名、括號、括號中的參數(shù)和冒號:,然后,在縮進塊中編寫函數(shù)體,函數(shù)的返回值用return語句返回。
# 定義一個函數(shù)my_abs()求絕對值
def my_abs(x): if x > 0: return x else : return -x x = -10 print(my_abs(x))
空函數(shù)
如果想定義一個什么事也不做的空函數(shù),可以用pass語句:
def nop(): pass
2、函數(shù)的分類
- 必選參數(shù)
- 默認參數(shù)
# 定義含有默認參數(shù)的函數(shù)
def enroll (name, gender, age=18, city='Beijing'): print('name:', name, 'gender:', gender, 'age:', age, 'city:', city) enroll('chenli', '男')
結(jié)果:
name: chenli gender: 男 age: 18 city: Beijing
有多個默認參數(shù)時,調(diào)用的時候,既可以按順序提供默認參數(shù),比如調(diào)用enroll('Bob', 'M', 7),意思是,除了name,gender這兩個參數(shù)外,最后1個參數(shù)應(yīng)用在參數(shù)age上,city參數(shù)由于沒有提供,仍然使用默認值。
也可以不按順序提供部分默認參數(shù)。當不按順序提供部分默認參數(shù)時,需要把參數(shù)名寫上。比如調(diào)用enroll('Adam', 'M', city='Tianjin'),意思是,city參數(shù)用傳進去的值,其他默認參數(shù)繼續(xù)使用默認值。
Python 默認參數(shù)提高了變成效率,但是這里面的坑也是坑死人不償命,使用稍微不正確,會造成致命的失誤
#!/usr/bin/env python
# -*- coding: utf-8 -*-
def test(lang=[]): lang.append('python') return lang
當老實調(diào)用時,結(jié)果如你所愿
print(test(['js','go'])) print(test(['c','java']))
結(jié)果如下
['js', 'go', 'python'] ['c', 'java', 'python'] [Finished in 0.1s]
但是如果多次使用默認參數(shù)調(diào)用時,奇跡出現(xiàn)了
print(test()) print(test())
結(jié)果如下:
['python'] ['python', 'python'] [Finished in 0.1s]
或許有人對此感到很困惑,為什么默認參數(shù)會記住更新過的默認參數(shù)
分析:
函數(shù)在定義的時候,默認參數(shù)lang的值就已經(jīng)聲明了,即空的 [],也就是說 默認參數(shù) 指向?qū)ο?[],在多次調(diào)用默認參數(shù)的情況下,就改變了默認參數(shù)指向?qū)ο蟮臄?shù)據(jù),默認參數(shù) 指向?qū)ο蟮臄?shù)據(jù)變了,下次再調(diào)用時,默認參數(shù)已經(jīng)變了,即不再是你希望的空的[]
為了便于理解等同下面這段:
temp = [] def test(lang=temp): lang.append('python') return lang
這樣就好看多了,多次調(diào)用時,temp 也在不斷的變化
注意函數(shù)默認參數(shù)和函數(shù)中的參數(shù)的上下文環(huán)境
結(jié)論
定義函數(shù)默認參數(shù)時,函數(shù)默認參數(shù)必須指向不變對象,建議使用 None,str 這些不可變對象處理
重新修改代碼
#!/usr/bin/env python
# -*- coding: utf-8 -*-
`
def test(lang=None):
if lang is None:
lang = []
lang.append('python')
return lang
調(diào)用
print(test())
print(test())
結(jié)果:
['python']
['python']
[Finished in 0.2s]
`
可變參數(shù)
定義可變參數(shù)和定義一個list或tuple參數(shù)相比,僅僅在參數(shù)前面加了一個*號。可變參數(shù)允許你傳入0個或任意個參數(shù),這些可變參數(shù)在函數(shù)調(diào)用時自動組裝為一個tuple。在函數(shù)內(nèi)部,參數(shù)numbers接收到的是一個tuple,因此,函數(shù)代碼完全不變。但是,調(diào)用該函數(shù)時,可以傳入任意個參數(shù),包括0個參數(shù):
#定義一個可選參數(shù)的函數(shù)
def calc (*numbers):
sum = 0
for n in numbers:
sum += n
return sum
print(calc())
print(calc(1,23,4))
結(jié)果:
0 28 [Finished in 0.2s]
如果已經(jīng)有一個list或者tuple,要調(diào)用一個可變參數(shù)怎么辦?可以這樣做:
num = (1, 2, 3) print(calc(*num))
結(jié)果:
6 [Finished in 0.1s]
關(guān)鍵字參數(shù)
而關(guān)鍵字參數(shù)允許你傳入0個或任意個含參數(shù)名的參數(shù),這些關(guān)鍵字參數(shù)在函數(shù)內(nèi)部自動組裝為一個dict。請看示例:
#定義關(guān)鍵字參數(shù)的函數(shù)
def person(name, age, **kw): print('name:', name, 'age:', age, 'other', kw) P1=person('chenli', '19') P2=person('haha', '20', city='Beijing') print(P1, P2)
結(jié)果:
name: chenli age: 19 other {} name: haha age: 20 other {'city': 'Beijing'} None None [Finished in 0.2s]
關(guān)鍵字參數(shù)有什么用?它可以擴展函數(shù)的功能。命名關(guān)鍵字參數(shù)
如果要限制關(guān)鍵字參數(shù)的名字,就可以用命名關(guān)鍵字參數(shù),例如,只接收city和job作為關(guān)鍵字參數(shù)。這種方式定義的函數(shù)如下:
#定義含命名關(guān)鍵字參數(shù)的函數(shù),命名關(guān)鍵字的作用是限定關(guān)鍵字參數(shù)的名字
def person(name, age, *, city, job): print(name, age, city, job) person('chenli', 10, city = 'Beijing', job='Engineer')``#調(diào)用方式
結(jié)果:
chenli 10 Beijing Engineer [Finished in 0.2s]
和關(guān)鍵字參數(shù) **kw不同,命名關(guān)鍵字參數(shù)需要一個特殊分隔符 *,*后面的參數(shù)被視為命名關(guān)鍵字參數(shù)。
如果函數(shù)定義中已經(jīng)有了一個可變參數(shù),后面跟著的命名關(guān)鍵字參數(shù)就不再需要一個特殊分隔符*了:
使用命名關(guān)鍵字參數(shù)時,要特別注意,如果沒有可變參數(shù),就必須加一個*作為特殊分隔符。如果缺少*,Python解釋器將無法識別位置參數(shù)和命名關(guān)鍵字參數(shù):參數(shù)組合
在Python中定義函數(shù),可以用必選參數(shù)、默認參數(shù)、可變參數(shù)、關(guān)鍵字參數(shù)和命名關(guān)鍵字參數(shù),這5種參數(shù)都可以組合使用。但是請注意,參數(shù)定義的順序必須是:必選參數(shù)、默認參數(shù)、可變參數(shù)、命名關(guān)鍵字參數(shù)和關(guān)鍵字參數(shù)。
def f1(a, b, c=0, *args, **kw): print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw) def f2(a, b, c=0, *, d, **kw): print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw) f1(1, 2) f1(1, 2, c=3) f1(1, 2, 3, 'a', 'b') f1(1, 2, 3, 'a', 'b', x=99) f2(1, 2, d =99, ext =None) args = (1, 2, 3) args1 = (1, 2, 3, 4) kw = {'d': 99, 'x': '#'} f1(*args1, **kw)``#最神奇的是通過一個tuple和dict,你也可以調(diào)用上述函數(shù):
f2(*args, **kw)``#最神奇的是通過一個tuple和dict,你也可以調(diào)用上述函數(shù):
對于任意函數(shù),都可以通過類似func(*args, **kw)的形式調(diào)用它,無論它的參數(shù)是如何定義的。
3.遞歸函數(shù)
在函數(shù)內(nèi)部,可以調(diào)用其他函數(shù)。如果一個函數(shù)在內(nèi)部調(diào)用自身本身,這個函數(shù)就是遞歸函數(shù)。
遞歸函數(shù)的優(yōu)點是定義簡單,邏輯清晰。理論上,所有的遞歸函數(shù)都可以寫成循環(huán)的方式,但循環(huán)的邏輯不如遞歸清晰。
使用遞歸函數(shù)需要注意防止棧溢出。在計算機中,函數(shù)調(diào)用是通過棧(stack)這種數(shù)據(jù)結(jié)構(gòu)實現(xiàn)的,每當進入一個函數(shù)調(diào)用,棧就會加一層棧幀,每當函數(shù)返回,棧就會減一層棧幀。由于棧的大小不是無限的,所以,遞歸調(diào)用的次數(shù)過多,會導(dǎo)致棧溢出。可以試試fact(1000):
def fact(n): if n == 1: return 1 return n*fact(n-1) print(fact(1000))
結(jié)果:
RecursionError: maximum recursion depth exceeded in comparison
解決遞歸調(diào)用棧溢出的方法是通過尾遞歸優(yōu)化,事實上尾遞歸和循環(huán)的效果是一樣的,所以,把循環(huán)看成是一種特殊的尾遞歸函數(shù)也是可以的。
尾遞歸是指,在函數(shù)返回的時候,調(diào)用自身本身,并且,return語句不能包含表達式。這樣,編譯器或者解釋器就可以把尾遞歸做優(yōu)化,使遞歸本身無論調(diào)用多少次,都只占用一個棧幀,不會出現(xiàn)棧溢出的情況。
def fact(n): return fact_iter(n, 1) def fact_iter(num, product): if num == 1: return product return fact_iter(num-1, num * product) print(fact(100)) print(fact_iter(100,1))
可以看到,return fact_iter(num - 1, num * product)僅返回遞歸函數(shù)本身,num - 1和num * product在函數(shù)調(diào)用前就會被計算,不影響函數(shù)調(diào)用。
尾遞歸調(diào)用時,如果做了優(yōu)化,棧不會增長,因此,無論多少次調(diào)用也不會導(dǎo)致棧溢出。
遺憾的是,大多數(shù)編程語言沒有針對尾遞歸做優(yōu)化,Python解釋器也沒有做優(yōu)化,所以,即使把上面的fact(n)函數(shù)改成尾遞歸方式,也會導(dǎo)致棧溢出。