python高級之函數
本節內容
- 函數的介紹
- 函數的創建
- 函數參數及返回值
- LEGB作用域
- 特殊函數
- 函數式編程
1.函數的介紹
為什么要有函數?因為在平時寫代碼時,如果沒有函數的話,那么將會出現很多重復的代碼,這樣代碼重用率就比較低。。。並且這樣的代碼維護起來也是很有難度的,為了解決這些問題,就出現了函數,用來將一些經常出現的代碼進行封裝,這樣就可以在任何需要調用這段代碼的地方調用這個函數就行了。
函數的定義:函數是指將一組語句的集合通過一個名字(函數名)封裝起來,要想執行這個函數,只需調用其函數名即可
特性:
- 代碼重用
- 保持一致性
- 可擴展性
2.函數的創建
在python中函數定義的格式如下:
def 函數名(形參):
函數體內部代碼塊
函數的調用使用 函數名(實參) 就可以調用函數了。
函數名的命名規則和變量的命名規則一樣:
- 函數名必須以下划線或字母開頭,可以包含任意字母、數字或下划線的組合。不能使用任何的標點符號;
- 函數名是區分大小寫的。
- 函數名不能是保留字。
形參和實參的區別:
函數在定義的時候,函數名后面的括號中可以添加參數,這些參數就叫做形參,形參:顧名思義就是形式參數,只是一個代號。
實參是在調用函數的時候函數名后面的括號中的參數,形參和實參需要一一對應起來,否則調用函數會報錯。
3.函數參數及返回值
前面提到函數的形參和實參要一一對應,那么參數對應有如下幾種:
- 必須參數
- 關鍵字參數
- 默認參數
- 不定長參數 *args
- 不定長參數 **kwargs
1.必須參數:
必須參數必須以對應的關系一個一個傳遞進入函數,函數調用時傳遞的實參必須和函數定義時的形參一一對應,不能多也不能少,順序也得一致。
舉個栗子:
1 def f(name,age): 2 print(name,age) 3 f("小明",18)
2.關鍵字參數
關鍵字參數是實參里面的概念,在調用函數的時候聲明某個參數是屬於某個關鍵字的。使用關鍵字參數允許函數調用時參數的順序與聲明時不一致,因為 Python 解釋器能夠用參數名匹配參數值。
舉個栗子:
1 def f(name,age): 2 print(name,age) 3 f(name="小明",18)
3.默認參數
默認參數是在函數聲明的時候,可以給某個參數指定默認值,這樣的參數叫做默認值參數。如果在調用函數的時候,默認參數沒有接收到對應的實參,那么就會將默認值賦值給這個參數。
舉個栗子:
1 def f(name,age,sex="male"): 2 print(name,age,sex) 3 f(name="小明",18)
這樣,就會把默認參數male賦值給sex了。
4.不定長參數 *args
在python里面,函數在聲明的時候,參數中可以使用(*變量名)的方式來接受不確定長度的參數,但是在python里面大家約定俗成使用*args接受不定長參數,這樣在調用函數的時候傳遞的參數就可以是不定長度的了。args接受了不定長參數之后,將這些參數放到一個tuple里面,可以通過訪問args來獲取這些不定長參數。
舉個栗子:
1 def f(*args): 2 print(args) 3 f("小明",18,"male")
打印出來的是一個tuple,里面存放了("小明",18,"male")這三個元素。
不定長參數 **kwargs
但是上面的args只能接收未命名的參數,那假如有類似於關鍵字參數的不定長參數該怎么辦呢?python里面使用(**變量名)來接收不定長的命名變量參數。同樣,python里面也約定俗成使用**kwargs接收不定長命名參數。kwargs接收了不定長參數之后,將這些參數放到一個字典里面,可以通過key獲取到相應的參數值。
舉個栗子:
1 def f(**kwargs): 2 print(kwargs) 3 f(name="小明",age=18,sex="male")
介紹完了這些參數之后,接下來要介紹的是關於這些參數混合使用的情況:
假如一個函數使用了上面所有種類的參數,那該怎么辦?為了不產生歧義,python里面規定了假如有多種參數混合的情況下,遵循如下的順序使用規則:
1 def f(必須參數,默認參數,*args,**kwargs): 2 pass
如果同時存在args和kwargs的話,args在左邊
默認參數在必須參數的右邊,在*args的左邊
關鍵字參數的位置不固定(ps:關鍵字參數也不在函數定義的時候確定)
那么,假如有一個列表想要傳遞進入一個不定長的未命名參數的函數中去,可以在該列表前面加上*實現,同理如果想傳遞一個字典進入不定長命名參數的函數中去,可以在該字典前面加上**
舉個栗子:
1 def f(*args,**kwargs): 2 print(args) 3 for i in kwargs: 4 print("%s:%s"%(i,kwargs[i])) 5 6 f(*[1,2,3],**{"a":1,"b":2})
函數的返回值
要想獲取函數的執行結果,就可以用return語句把結果返回
注意:
函數在執行過程中只要遇到return語句,就會停止執行並返回結果,也可以理解為 return 語句代表着函數的結束 如果未在函數中指定return,那這個函數的返回值為None
return多個對象,解釋器會把這多個對象組裝成一個元組作為一個一個整體結果輸出。
4.LEGB作用域
python中的作用域分4種情況:
L:local,局部作用域,即函數中定義的變量;
E:enclosing,嵌套的父級函數的局部作用域,即包含此函數的上級函數的局部作用域,但不是全局的;
G:globa,全局變量,就是模塊級別定義的變量;
B:built-in,系統固定模塊里面的變量,比如int, bytearray等。 搜索變量的優先級順序依次是:作用域局部>外層作用域>當前模塊中的全局>python內置作用域,也就是LEGB。
local和enclosing是相對的,enclosing變量相對上層來說也是local。
在Python中,只有模塊(module),類(class)以及函數(def、lambda)才會引入新的作用域,其它的代碼塊(如if、try、for等)不會引入新的作用域。
變量的修改(錯誤修改,面試題里經常出):
1 x=6 2 def f2(): 3 print(x) 4 x=5 5 f2() 6 7 # 錯誤的原因在於print(x)時,解釋器會在局部作用域找,會找到x=5(函數已經加載到內存),但x使用在聲明前了,所以報錯: 8 # local variable 'x' referenced before assignment.如何證明找到了x=5呢?簡單:注釋掉x=5,x=6 9 # 報錯為:name 'x' is not defined 10 #同理 11 x=6 12 def f2(): 13 x+=1 #local variable 'x' referenced before assignment. 14 f2()
global關鍵字
當內部作用域想修改外部作用域的變量時,就要用到global和nonlocal關鍵字了,當修改的變量是在全局作用域(global作用域)上的,就要使用global先聲明一下,代碼如下:
1 count = 10 2 def outer(): 3 global count 4 print(count) 5 count = 100 6 print(count) 7 outer()
nonlocal關鍵字
global關鍵字聲明的變量必須在全局作用域上,不能嵌套作用域上,當要修改嵌套作用域(enclosing作用域,外層非全局作用域)中的變量怎么辦呢,這時就需要nonlocal關鍵字了
1 def outer(): 2 count = 10 3 def inner(): 4 nonlocal count 5 count = 20 6 print(count) 7 inner() 8 print(count) 9 outer()
小結
-
變量查找順序:LEGB,作用域局部>外層作用域>當前模塊中的全局>python內置作用域;
-
只有模塊、類、及函數才能引入新作用域;
-
對於一個變量,內部作用域先聲明就會覆蓋外部變量,不聲明直接使用,就會使用外部作用域的變量;
-
內部作用域要修改外部作用域變量的值時,全局變量要使用global關鍵字,嵌套作用域變量要使用nonlocal關鍵字。nonlocal是python3新增的關鍵字,有了這個 關鍵字,就能完美的實現閉包了。
5.特殊函數
遞歸函數定義:遞歸函數就是在函數內部調用自己
有時候解決某些問題的時候,邏輯比較復雜,這時候可以考慮使用遞歸,因為使用遞歸函數的話,邏輯比較清晰,可以解決一些比較復雜的問題。但是遞歸函數存在一個問題就是假如遞歸調用自己的次數比較多的話,將會使得計算速度變得很慢,而且在python中默認的遞歸調用深度是1000層,超過這個層數將會導致“爆棧”。。。所以,在可以不用遞歸的時候建議盡量不要使用遞歸。
舉個栗子:
1 def factorial(n): # 使用循環實現求和 2 Sum=1 3 for i in range(2,n+1): 4 Sum*=i 5 return Sum 6 print(factorial(7)) 7 8 def recursive_factorial(n): # 使用遞歸實現求和 9 return (2 if n==2 else n*recursive_factorial(n-1)) 10 11 print(recursive_factorial(7)) 12 13 def feibo(n): # 使用遞歸實現菲波那切數列 14 if n==0 or n==1:return n 15 else:return feibo(n-1)+feibo(n-2) 16 print(feibo(8)) 17 18 def feibo2(n): # 使用循環實現菲波那切數列 19 before,after=0,1 20 for i in range(n): 21 before,after=after,before+after 22 return before 23 print(feibo2(300))
遞歸函數的優點:定義簡單,邏輯清晰。理論上,所有的遞歸函數都可以寫成循環的方式,但循環的邏輯不如遞歸清晰。
遞歸特性:
-
必須有一個明確的結束條件
-
每次進入更深一層遞歸時,問題規模相比上次遞歸都應有所減少
-
遞歸效率不高,遞歸層次過多會導致棧溢出(在計算機中,函數調用是通過棧(stack)這種數據結構實現的,每當進入一個函數調用,棧就會加一層棧幀,每當函數返 回,棧就會減一層棧幀。由於棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出。)
6.函數式編程
關於函數式編程,我理解的也不是很深,但是python中有4個比較重要的內置函數,組合起來使用有時候能大大提高編程效率。
1 filter(function, sequence)
1 str = ['a', 'b','c', 'd'] 2 def fun1(s): 3 if s != 'a': 4 return s 5 ret = filter(fun1, str)
print(list(ret))# ret是一個迭代器對象
對sequence中的item依次執行function(item),將執行結果為True的item做成一個filter object的迭代器返回。可以看作是過濾函數。
2 map(function, sequence)
1 str = [1, 2,'a', 'b'] 2 def fun2(s): 3 return s + "alvin" 4 ret = map(fun2, str) 5 print(ret) # map object的迭代器 6 print(list(ret))# ['aalvin', 'balvin', 'calvin', 'dalvin']
對sequence中的item依次執行function(item),將執行結果組成一個map object迭代器返回. map也支持多個sequence,這就要求function也支持相應數量的參數輸入:
1 def add(x,y): 2 return x+y 3 print (list(map(add, range(10), range(10))))##[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
3 reduce(function, sequence, starting_value)
1 from functools import reduce 2 def add1(x,y): 3 return x + y 4 5 print (reduce(add1, range(1, 101)))## 4950 (注:1+2+...+99) 6 print (reduce(add1, range(1, 101), 20))## 4970 (注:1+2+...+99+20)
對sequence中的item順序迭代調用function,如果有starting_value,還可以作為初始值調用.
4 lambda
普通函數與匿名函數的對比:
1 #普通函數 2 def add(a,b): 3 return a + b 4 5 print add(2,3) 6 7 8 #匿名函數 9 add = lambda a,b : a + b 10 print add(2,3) 11 12 13 #========輸出=========== 14 5 15 5
匿名函數的命名規則,用lamdba 關鍵字標識,冒號(:)左側表示函數接收的參數(a,b) ,冒號(:)右側表示函數的返回值(a+b)。
因為lamdba在創建時不需要命名,所以,叫匿名函數
