迭代器與裝飾器
阿新 • • 發佈:2018-12-17
一. iter&yield迭代器
1.1 iter
1 names = iter(['zeng', 'chun', 'yun']) 2 print(names) 3 print(names.__next__()) 4 print(names.__next__()) 5 print(names.__next__()) 6 print(names.__next__()) 7 8 9 #第一個輸出列印迭代器物件 10 #第二三四次next方法每次都去獲取迭代物件的值,每次往下取一個值,直到取完 11 #第五次輸出 print(names.__next__())12 #StopIteration,因為迭代器裡面的物件已經取完了,所以出現這個異常
1.2 yield
1 def fei(num): 2 x, y, z = 0, 0, 1 3 while x < num: 4 if x == 0: # 當第一次迴圈時,返回數列0 5 yield y # 生成器返回一個迭代器物件 6 else: # 否則返回z 7 yield z 8 y, z = z, y + z9 x += 1 10 11 print(fei(9).__next__()) # 只從迭代器裡面取一次值 12 # fei(9).__next__() 13 for i in fei(9): # 通過遍歷獲取迭代器內容 14 print(i)
1.3 生成器的非同步應用
import time def consumer(name): # 定義消費者函式 print("%s 準備吃包子啦!" % name) # 列印消費者名字 while True: baozi= yield # 當代碼執行到這時,返回一個迭代器物件,當物件第二次呼叫時,會接收值並賦值給baozi print("包子[%s]來了,被[%s]吃了!" % (baozi, name)) # 列印當前使用的迭代器物件,及接收的值 def producer(*name): # 定義一個生產者函式,並使用動態引數 if len(name) == 3: # 當傳入三個引數時,呼叫三次消費者函式方法 c = consumer(name[0]) c2 = consumer(name[1]) c3 = consumer(name[2]) c.__next__() # 通過函式內建方法獲取迭代器內部元素 c2.__next__() c3.__next__() print("老子開始準備做包子啦!") for i in range(1, 11): time.sleep(1) print("做了%s個包子!" % len(name)) c.send(i) # 通過迭代器方法的send屬性給生成器傳送值 c2.send(i) c3.send(i) producer("alex", 'zengchunyun', 'peiqi') # 建立一個生產者物件,並傳入三個消費者名字
二. 裝飾器
2.1 首先了解一下函式
def func(): print('hello') print(func) # 第一次輸出的是一個函式物件 print(type(func)) #第二次輸出的是一個型別為函式 func() # 第三次才是執行這個函式 print(func()) #第四次則是執行函式後,並列印函式的返回結果,函式沒有指定返回內容,所以使用預設返回值,None print(type(func())) # 第五次也是先執行函式,然後列印返回值物件的型別,所以可以看出,返回值物件型別是NoneType # 第一次 #<function func at 0x100756f28> # 第二次 #<class 'function'> # 第三次 #hello # 第四次 #hello #None # 第五次 #hello #<class 'NoneType'>
通過上述總結一句話,函式不加括號是沒有執行的,這時的這個函式名只是一個函式物件,而加括號後,函式將會執行函式體程式碼,
最終這個函式則是函式執行完後的返回的物件
2.2 裝飾器
有了上面這個基礎後,我們再來看看裝飾器
1 def wrapper(func): 2 print(func) # 列印引數 3 return func # 返回引數 4 5 6 @wrapper # 使用裝飾器 7 def index(): 8 print('welcome') 9 10 11 index() 12 13 # 執行順序是,直譯器從上往下讀取程式碼 14 # 遇到函式時,只加載定義的函式物件,並不執行函式體程式碼 15 # 然後遇到裝飾器@wrapper時 16 # 直譯器會跳到這個wrapper函式 17 # 然後執行這個wrapper函式內部程式碼 18 # 我們通過觀察得知,這個wrapper函式一定會傳入一個引數,因為測試發現,不傳入一個引數,程式執行會丟擲需要一個引數的異常錯誤. 19 # 通過分析這個引數,發現這個引數列印結果是一個函式物件 20 # 然後wrapper函式體程式碼執行完畢後,繼續往下執行,遇到函式index, 21 # 也是隻是載入這個函式物件,並不執行內部函式體程式碼 22 # 當遇到程式碼index()時,結合到我們之前積累的函式基礎知識, 23 # 這個寫法實際是開始執行一個函式,所以直譯器會跳到指定的index函式物件 24 # 然後開始執行這個函式體程式碼塊, 25 # 整個執行過程就結束了 26 27 28 # 以下為程式碼執行結果 29 # <function index at 0x1010ed0d0> 30 # welcome
可能你對上面這個過程還是不夠了解裝飾器,那麼我們再通過下面這個例子說明裝飾器的工作原理
1 def wrapper(func): 2 print(func) # 列印引數 3 # return func # 返回引數 ,現在註釋掉這個返回值 4 5 6 @wrapper # 使用裝飾器 7 def index(): 8 print('welcome') 9 10 print(type(index)) # 加上一句輸出型別程式碼的語句 11 index() 12 13 #我們在上面這個例子中,把函式wrapper中的返回值給註釋掉了,程式碼執行結果 14 15 # function index at 0x1011ed0d0> 16 # <class 'NoneType'> 17 # Traceback (most recent call last): 18 # File "/Users/zengchunyun/PycharmProjects/s12/day4/noteext.py", line 68, in <module> 19 # index() 20 # TypeError: 'NoneType' object is not callable 21 22 # 首先我們來分析一下這個結果 23 # 你會不會很驚訝?我們只是針對第一個例子的程式碼僅僅只是註釋掉一個返回值而已,程式碼就不能工作了 24 25 # 首先直譯器還是從上往下讀取程式碼 26 # 遇到函式時,只加載定義的函式物件,並不執行函式體程式碼 27 # 然後遇到裝飾器@wrapper時 28 # 直譯器會跳到這個wrapper函式 29 # 然後執行這個wrapper函式內部程式碼 30 # 通過分析這個引數,發現這個引數列印結果是一個函式物件 31 # 然後wrapper函式體程式碼執行完畢後,繼續往下執行,遇到函式index, 32 # 也是隻是載入這個函式物件,並不執行內部函式體程式碼 33 # 關鍵點來了 34 # 程式碼執行到列印物件型別的語句時,結果卻是一個NoneType的型別,根據我們之前對函式的基本介紹,這裡的型別應該是一個函式型別才對啊 35 # 怎麼回事呢?我們明明定義了index函式,怎麼列印的型別卻是NoneType型別? 36 # 我們之前也看到只有函式沒有返回值時,函式預設會返回一個None物件,故而這個物件的型別也就是NoneType型別了, 37 # 我們僅僅只是加了一個裝飾器程式碼@wrapper,其他都沒有變,為什麼會出現這個情況呢 38 # 我們上一個例子已經說明,這個裝飾器會攜帶一個引數,這個引數為一個函式物件, 39 # 實際上,這個時候這個裝飾器會對引用裝飾器的函式,也就是我們這裡的index函式進行重構, 40 # 所以如果我們如果不返回一個函式物件時,那麼這個時候的index實質是一個普通的物件,不是函式型別了 41 # 它已經被賦予None這個值了,而None不是一個函式物件,所以就沒有呼叫方法,就不能以括號方式執行 42 # 這時直譯器讀到index()這句程式碼,大家依據之前的理念,都能看出這個是去執行index這個函式內部程式碼塊的語句 43 # 但是這個時候,直譯器卻在這個時候丟擲異常了 44 # 返回型別錯誤,TypeError: 'NoneType' object is not callable 45 # 這個錯誤說我們的index執行後,是不能被呼叫的,只有物件型別為函式才有內建呼叫方法 46 # 因為這個index已經被重構,返回值已經變成了None,也就是說,index 物件目前僅僅是一個普通識別符號,不是函式
2.3 裝飾器的高階應用
我們通過上面的事咧可知,只要程式碼執行到裝飾器識別符號,都會去執行裝飾器函式體,但是這個不是我們想要的,我們希望的是
只有我們呼叫引用裝飾器的函式時,才去執行這個裝飾器函式體,那怎麼辦呢?我們知道,只有型別是函式物件時,程式碼是不會被執行,
只是載入到記憶體而已,那好吧,我們直接返回一個函式物件不就好了?
我們來看看程式碼實現
1 def wrapper(func): 2 def inner(): 3 print(func) # 輸出是一個函式物件 4 func() # 這裡實際是執行我們這個例子中原先定義的index函式物件的函式體 5 return inner 6 # print(func) # 列印引數 7 # return func # 返回引數 ,現在註釋掉這個返回值 8 9 10 @wrapper # 使用裝飾器 11 def index(): 12 print('welcome') 13 14 print(type(index)) # 加上一句輸出型別程式碼的語句 15 index() 16 17 # 首先看看這種情況 18 # 程式碼執行到index()時,啥也沒有,我們明明列印了一句welcome,為什麼沒有輸出資訊,這也就再次證明了, 19 # 實質是這個index已經等價於裝飾器的inner這個函數了,因為裝飾器返回的是inner這個函式物件 20 # 我們既想用裝飾器,又想執行呼叫裝飾器的內部程式碼時怎麼辦呢? 21 # 我們已經知道,裝飾器會攜帶一個引數,這個引數是引用裝飾器物件的一個函式物件 22 # 既然是一個函式物件,那我們是不是可以直接執行這個函式物件呢? 23 # 答案是肯定的,所以我們直接在這個裝飾器裡執行這個函式物件,等於就是執行我們原先那個函式的函式體 24 # 這個例子就滿足了我們的需求,當我們不呼叫index函式時,得到的僅僅是一個函式物件,並不會執行函式體程式碼
有引數的函式,或裝飾器實現傳參例項
1 # 無參裝飾器,有參函式 2 def wrapper(func): 3 def inner(name): # 這個引數最終會傳給這個函式體內部需要呼叫引數的物件 4 func(name) # 這個引數個數是由原來的函式,也就是我們這裡的index函式決定引數個數的 5 return inner 6 7 8 @wrapper # 使用裝飾器 9 def index(name): # 傳入一個引數 10 print('welcome %s' % name) 11 12 index('zengchunyun')無參裝飾器,有參函式
1 # 無參裝飾器,多參函式 2 def wrapper(func): 3 def inner(*args): # 使用動態引數 4 func(*args) 5 return inner 6 7 8 @wrapper # 使用裝飾器 9 def index(*args): # 傳入一個引數 10 print('welcome %s' % ' '.join(args)) 11 12 index('zengchunyun', 'goodbye')無參裝飾器,多參函式
1 # 無參裝飾器,多參函式2 2 def wrapper(func): 3 def inner(*args, **kwargs): # 使用動態引數 4 func(*args, **kwargs) 5 return inner 6 7 8 @wrapper # 使用裝飾器 9 def index(*args, **kwargs): # 傳入一個引數 10 print('welcome %s' % ' '.join(args)) 11 12 index('zengchunyun', 'goodbye')無參裝飾器,多參函式2
1 # 有參裝飾器,多參函式 2 def one(): 3 print('one') 4 5 6 def two(): 7 print('two') 8 9 10 def func(arg1, arg2): 11 def wrapper(oldfunc): 12 def inner(*args, **kwargs): # 使用動態引數 13 arg1() 14 arg2() 15 oldfunc(*args, **kwargs) 16 return inner 17 return wrapper 18 19 20 @func(one, two) # 使用裝飾器 21 def index(*args, **kwargs): # 傳入一個引數 22 print('welcome %s' % ' '.join(args)) 23 24 index('zengchunyun', 'goodbye') 25 26 # 直譯器遇到裝飾器,由於這個裝飾器是一個可執行函式 27 # 故而先執行函式,再次就成了我們所認知的普通裝飾器了# 有參裝飾器,多參函式