一、Python函數剖析
1、函數的調用順序
#!/usr/bin/env python # -*- coding:utf-8 -*- #-Author-Lian #函數錯誤的調用方式 def func(): #定義函數func() print("in the func") foo() #調用函數foo() func() #執行函數func() def foo(): #定義函數foo() print("in the foo") ###########打印輸出########### #報錯:函數foo沒有定義 #NameError: name 'foo' is not defined #函數正確的調用方式 def func(): #定義函數func() print("in the func") foo() #調用函數foo() def foo(): #定義函數foo() print("in the foo") func() #執行函數func() ###########打印輸出########### #in the func #in the foo
總結:被調用函數要在執行之前被定義
2、高階函數
滿足下列條件之一就可成函數為高階函數
-
某一函數當做參數傳入另一個函數中
-
函數的返回值包含一個或多個函數
剛才調用順序中的函數稍作修改就是一個高階函數
#高階函數 def func(): #定義函數func() print("in the func") return foo() #調用函數foo() def foo(): #定義函數foo() print("in the foo") return 100 res = func() #執行函數func() print(res) #打印函數返回值 ###########打印輸出########### #in the func #in the foo #100
從上面的程序得知函數func的返回值為函數foo的返回值,如果foo不定義返回值的話,func的返回值默認為None;
下面我來看看更復雜的高階函數:
#更復雜的高階函數 import time #調用模塊time def bar(): time.sleep(1) print("in the bar") def foo(func): start_time=time.time() func() end_time=time.time() print("func runing time is %s"%(end_time-start_time)) foo(bar) ###########打印輸出########### #in the bar #func runing time is 1.0000572204589844
其實上面這段代碼已經實現了裝飾器一些功能,即在不修改bar()代碼的情況下,給bar()添加了功能;但是改變了bar()調用方式
下面我們對上面的code進行下修改,不改變bar()調用方式的情況下進行功能添加
#更復雜的高階函數,不改變調用方式 import time #調用模塊time def bar(): time.sleep(1) print("in the bar") def foo(func): start_time=time.time() print("in the foo") return func #返回bar函數的內存地址 end_time=time.time() print("func runing time is %s"%(end_time-start_time)) bar = foo(bar) #bar重新賦值 bar() ###########打印輸出########### #in the foo #in the bar
我們沒有對bar()源碼進行過修改,也沒有改變bar()的調用方式,當執行bar()函數時,多加了一些功能,裝飾器的一些雛形已經呈現;但是我們又發現之前添加的計算bar()執行時間的功能沒有打印出來,return執行后函數就結束了。
3、內嵌函數和作用域
定義:在一個函數體內創建另外一個函數,這種函數就叫內嵌函數(基於python支持靜態嵌套域)
#內嵌函數示例 def foo(): print("in the foo") def bar(): print("in the bar") bar() foo() ###########打印輸出########### #in the foo #in the bar
嵌套函數有什么用呢?我們暫時先記住這個內容
局部作用域和全局作用域的訪問順序
#嵌套函數變量與全部變量 x = 0 def grandpa(): x=1 def dad(): x=2 def son(): x=3 print(x) son() dad() grandpa() print(x) ###########打印輸出########### # 3 # 0
注:內嵌函數中定義的函數在全局中是無法直接執行的
4、裝飾器
定義:本質是函數(裝飾其他函數),為其他函數添加附加功能的。
遵循原則:①不能修改被裝飾函數的源代碼
②不能修改被裝飾函數的調用方式
組成:裝飾器由高階函數+內嵌函數組成
之前說了那么多其實都是了給裝飾器做鋪墊,回到剛才高階函數中最后一個示例,能不能給函數加上運算時間計算?
#!/usr/bin/env python # -*- coding:utf-8 -*- #-Author-Lian #裝飾器 import time def timer(func): def deco(): start_time=time.time() func() #執行形參func() end_time=time.time() print("func runing time is %s"%(end_time-start_time)) return deco #返回函數deco的內存地址 def test1(): print("in the test1") time.sleep(1) test1 = timer(test1) #重新賦值test1 此時test1=deco的內存地址 test1() #執行test1 ###########打印輸出########### #in the test1 #func runing time is 1.0000572204589844
現在我們已經實現了裝飾器的功能,但是如果test1有形參的話,上面的代碼就會報錯了,下面我們對代碼做下修改
#!/usr/bin/env python # -*- coding:utf-8 -*- #-Author-Lian #裝飾器 import time def timer(func): def deco(*args,**kwargs): start_time=time.time() func(*args,**kwargs) #執行形參func() end_time=time.time() print("func runing time is %s"%(end_time-start_time)) return deco #返回函數deco的內存地址 @timer #test1 = timer(test1) test1=deco def test1(name): print("in the test1 name %s"%name) time.sleep(1) test1("lzl") #執行test1 ###########打印輸出########### #in the test1 #func runing time is 1.0000572204589844
上面的代碼是不是覺得很完美了,呵呵,假如test1()有return返回值怎么辦?你會發現最后執行test1返回值丟失了,所以要對上面的代碼再完善一下了。
#!/usr/bin/env python # -*- coding:utf-8 -*- #-Author-Lian #裝飾器 import time def timer(func): def deco(*args,**kwargs): start_time=time.time() res = func(*args,**kwargs) #執行形參func() end_time=time.time() print("func runing time is %s"%(end_time-start_time)) return res return deco #返回函數deco的內存地址 @timer #test1 = timer(test1) test1=deco def test1(name): print("in the test1 name %s"%name) time.sleep(1) return "return form test1" print(test1("lzl")) #執行test1 ###########打印輸出########### #in the test1 #func runing time is 1.0000572204589844 #return form test1
好了現在我們探討一個問題,函數可以被多個裝飾器裝飾嗎?!
#!/usr/bin/env python # -*- coding:utf-8 -*- #-Author-Lian #函數用多個裝飾器 def w1(func): def inner(*args, **kwargs): print("in the w1") return func(*args, **kwargs) return inner def w2(func): def inner(*args, **kwargs): print("in the w2") return func(*args, **kwargs) return inner @w1 @w2 def f1(*args, **kwargs): print("in the f1") f1() ###########打印輸出########### #in the w1 #in the w2 #in the f1
終極版裝飾器來了......
#!/usr/bin/env python # -*- coding:utf-8 -*- #-Author-Lian #終極版裝飾器 def Before(*args, **kwargs): print("before") def After(*args, **kwargs): print("after") def Filter(before_func, after_func): def outer(main_func): def wrapper(*args, **kwargs): before_result = before_func(*args, **kwargs) if (before_result != None): return before_result main_result = main_func(*args, **kwargs) if (main_result != None): return main_result after_result = after_func(*args, **kwargs) if (after_result != None): return after_result return wrapper return outer @Filter(Before, After) #Filter(Before,After)=outer Index=outer(Index)=wrapper def Index(*args, **kwargs): print("index") Index() #Index() = wrapper() ###########打印輸出########### #before #index #after
5、生成器
學習生成器之前,我們先來看看什么是列表生成式
#列表生成式 b = [ i*2 for i in range(10)] print(b) ###########打印輸出########### #[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
通過列表生成式,我們可以直接創建一個列表。但是,受到內存限制,列表容量肯定是有限的。而且,創建一個包含100萬個元素的列表,不僅占用很大的存儲空間,還需要花費很長時間,如果我們僅僅需要訪問前面幾個元素,那后面絕大多數元素占用的空間都白白浪費了。所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環的過程中不斷推算出后續的元素呢?這樣就不必創建完整的list,從而節省大量的空間。在Python中,這種調用時才會生成相應數據的機制,稱為生成器:generator
要創建一個generator,有很多種方法,第一種方法很簡單,只要把一個列表生成式的[]
改成()
,就創建了一個生成器
#生成器 l = [ i*2 for i in range(10)] print(l) g = (i*2 for i in range(10)) print(g) ###########打印輸出########### #[0, 2, 4, 6, 8, 10, 12, 14, 16, 18] #<generator object <genexpr> at 0x0064AAE0>
print(g) 打印出來的信息顯示g是一個生成器,創建l
和g
的區別僅在於最外層的[]
和()
,l
是一個list,而g
是一個generator;我們可以直接打印出list的每一個元素,但我們怎么打印出generator的每一個元素呢?如果要一個一個打印出來,可以通過next()
函數獲得generator的下一個返回值
#生成器next打印 print(next(g)) #......... 不斷next 打印10次 #.......... print(next(g)) ###########打印輸出########### #0 #........ #18 #Traceback (most recent call last): # File "<stdin>", line 1, in <module> #StopIteration
我們講過,generator保存的是算法,每次調用next(g)
,就計算出g
的下一個元素的值,直到計算到最后一個元素,沒有更多的元素時,拋出StopIteration
的錯誤。
上面這種不斷調用next(g)
實在是太變態了,正確的方法是使用for
循環,因為generator也是可迭代對象,所以,我們創建了一個generator后,基本上永遠不會調用next()
,而是通過for
循環來迭代它,並且不需要關心StopIteration
的錯誤
#生成器for調用 g = (i*2 for i in range(10)) #不用擔心出現StopIteration錯誤 for i in g: print(i) ###########打印輸出########### # 0 # 2 # 4 # 6 # 8 # 10 # 12 # 14 # 16 # 18
generator非常強大。如果推算的算法比較復雜,用列表生成式轉換的生成器無法去實現時,我們還可以用函數來實現。比如,著名的斐波拉契數列(Fibonacci)
#函數表示斐波拉契數列 def fib(max): n, a, b = 0, 0, 1 while n < max: print(b) a, b = b, a + b n += 1 return 'done' fib(5) ###########打印輸出########### # 1 # 1 # 2 # 3 # 5
仔細觀察,可以看出,fib
函數實際上是定義了斐波那契數列的推算規則,可以從第一個元素開始,推算出后續任意的元素,這種邏輯其實非常類似generator;也就是說,上面的函數和generator僅一步之遙,那我們能不能把上面的函數變成一個生成器呢?
#斐波拉契數列轉換為generator def fib(max): n, a, b = 0, 0, 1 while n < max: #print(b) yield b a, b = b, a + b n += 1 return 'done' print(type(fib(5))) #打印fib(5)的類型 for i in fib(5): #for循環去調用 print(i) ###########打印輸出########### # <class 'generator'> # 1 # 1 # 2 # 3 # 5
要把fib
函數變成generator,只需要把print(b)
改為yield b
就可以了,這就是定義generator的另一種方法。如果一個函數定義中包含yield
關鍵字,那么這個函數就不再是一個普通函數,而是一個generator
但是用for
循環調用generator時,會發現拿不到generator的return
語句的返回值,也就是return的值沒有打印出來,現在我們來看看怎么去打印generator的返回值
#!/usr/bin/env python # -*- coding:utf-8 -*- #-Author-Lian #獲取generator的返回值 def fib(max): n, a, b = 0, 0, 1 while n < max: #print(b) yield b a, b = b, a + b n += 1 return 'done' g = fib(5) while True: try: x = next(g) print( x) except StopIteration as e: print(e.value) break ###########打印輸出########### # 1 # 1 # 2 # 3 # 5 # done
如果想要拿到返回值,必須捕獲StopIteration
錯誤,返回值包含在StopIteration
的value
中,關於如何捕獲錯誤,后面的錯誤處理還會詳細講解。
還可通過yield實現在單線程的情況下實現並發運算的效果
#!/usr/bin/env python # -*- coding:utf-8 -*- #-Author-Lian import time def consumer(name): print("%s 准備吃包子啦!" %name) while True: baozi = yield print("包子[%s]來了,被[%s]吃了!" %(baozi,name)) def producer(name): c = consumer('A') c2 = consumer('B') c.__next__() #c.__next__()等同於next(c) c2.__next__() print("老子開始准備做包子啦!") for i in range(10): time.sleep(1) print("%s做了2個包子!"%(name)) c.send(i) c2.send(i) producer("lzl")
6、迭代器
我們已經知道,可以直接作用於for
循環的數據類型有以下2種:
-
集合數據類型,如
list
、tuple
、dict
、set
、str
等; -
生成器,包括generator和帶
yield
的generator function;
定義:這些可以直接作用於for
循環的對象統稱為可迭代對象:Iterable
我們可以使用isinstance()去
判斷一個對象是否是Iterable
對象
#可迭代對象 from collections import Iterable print(isinstance([], Iterable)) # True print(isinstance("abc", Iterable)) # True print(isinstance((x for x in range(10)), Iterable)) # True print(isinstance(100, Iterable)) # False
我們知道生成器不但可以作用於for
循環,還可以被next()
函數不斷調用並返回下一個值,直到最后拋出StopIteration
錯誤表示無法繼續返回下一個值了
重點來了....*可以被next()
函數調用並不斷返回下一個值的對象稱為迭代器:Iterator
可以使用isinstance()
判斷一個對象是否是Iterator
對象
#迭代器對象 from collections import Iterator print(isinstance([], Iterator)) # True print(isinstance("abc", Iterator)) # False print(isinstance((x for x in range(10)), Iterator)) # True print(isinstance(100, Iterator)) # False
由上面可知,生成器都是Iterator
對象,但list
、dict
、str
雖然是Iterable
,卻不是Iterator;把
list
、dict
、str
等Iterable
變成Iterator
可以使用iter()
函數
#可迭代對象轉迭代器對象 print(isinstance(iter([]), Iterator)) # True print(isinstance(iter("abc"), Iterator)) # True
你可能會問,為什么list
、dict
、str
等數據類型是Iterable但不是
Iterator
?
這是因為Python的Iterator
對象表示的是一個數據流,Iterator對象可以被next()
函數調用並不斷返回下一個數據,直到沒有數據時拋出StopIteration
錯誤。可以把這個數據流看做是一個有序序列,但我們卻不能提前知道序列的長度,只能不斷通過next()
函數實現按需計算下一個數據,所以Iterator
的計算是惰性的,只有在需要返回下一個數據時它才會計算。Iterator
甚至可以表示一個無限大的數據流,例如全體自然數。而使用list是永遠不可能存儲全體自然數的。
小結:
- 凡是可作用於
for
循環的對象都是Iterable
類型; - 凡是可作用於
next()
函數的對象都是Iterator
類型,它們表示一個惰性計算的序列; - 集合數據類型如
list
、dict
、str
等是Iterable
但不是Iterator
,不過可以通過iter()
函數獲得一個Iterator
對象;
Python的for
循環本質上就是通過不斷調用next()
函數實現的,例如:
for x in [1, 2, 3, 4, 5]: pass
實際上完全等價於:
# 首先獲得Iterator對象: it = iter([1, 2, 3, 4, 5]) # 循環: while True: try: # 獲得下一個值: x = next(it) except StopIteration: # 遇到StopIteration就退出循環 break