詳解python三大器——迭代器、生成器、裝飾器

一、迭代器

聊迭代器前我們要先清楚迭代的概念:通常來講從一個物件中依次取出資料,這個過程叫做遍歷,這個手段稱為迭代(重複執行某一段程式碼塊,並將每一次迭代得到的結果作為下一次迭代的初始值)。

可迭代物件(iterable):是指該物件可以被用於for…in…迴圈,例如:集合,列表,元祖,字典,字串,迭代器等。

在python中如果一個物件實現了 __iter__方法,我們就稱之為可迭代物件,可以檢視set\list\tuple…等原始碼內部均實現了__iter__方法

如果一個物件未實現__iter__方法,但是對其使用for…in則會丟擲TypeError: ‘xxx’ object is not iterable

可以透過isinstance(obj,Iterable)來判斷物件是否為可迭代物件。如

from collections。abc import Iterablea: int = 1print(isinstance(a, Iterable)) # Falseb: str = “lalalalala” print(isinstance(b, Iterable)) # Truec: set = set([1, 2])print(isinstance(c, Iterable)) # True

我們也可以自己實現__iter__來將一個類例項物件變為可迭代物件:

class MyIterable: def __iter__(self): passprint(isinstance(MyIterable(), Iterable)) # True

迭代器:對可迭代物件進行迭代的方式或容器,並且需要記錄當前迭代進行到的位置。

在python中如果一個物件同時實現了__iter__和__next__(獲取下一個值)方法,那麼它就是一個迭代器物件。

可以透過內建函式next(iterator),來獲取當前迭代的值

迭代器一定是可迭代物件,可迭代物件不一定是迭代器。

如果可迭代物件遍歷完後繼續呼叫next(),則會丟擲:StopIteration異常。

自己實現一個迭代器物件:

from collections。abc import Iterator, Iterableclass MyIterator: def __init__(self, array_list): self。array_list = array_list self。index = 0 def __iter__(self): return self def __next__(self): if self。index < len(self。array_list): val = self。array_list[self。index] self。index += 1 return val else: raise StopIteration# 父類如果是迭代器,子類也將是迭代器class MySubIterator(MyIterator): def __init__(self): passmyIterator = MyIterator([1, 2, 3, 4])# 判斷是否為可迭代物件print(isinstance(myIterator, Iterable)) # True# 判斷是否為迭代器print(isinstance(myIterator, Iterator)) # True# 子類例項化mySubIterator = MySubIterator()print(isinstance(mySubIterator, Iterator)) # True# 進行迭代print(next(myIterator)) # 1print(next(myIterator)) # 2print(next(myIterator)) # 3print(next(myIterator)) # 4print(next(myIterator)) # raise StopIteration

迭代器優缺點:

- 優點:迭代器物件表示的是一個數據流,可以在需要時才去呼叫next來獲取一個值;因而本身在記憶體中始終只保留一個值,對於記憶體佔用小可以存放無限資料流。優於其他容器需要一次將所有元素都存放進記憶體,如:列表、集合、字典。。。等

- 缺點:1。無法獲取存放的元素長度,除非取完計數。2。只能向後取值, next()永遠返回的是下一個值。取值不靈活,無法取出指定值(無法像字典的key,或列表的下標),而且迭代器的生命週期是一次性的元素被迭代完則生命週期結束。

生成器

定義:在Python中,一邊迴圈一邊計算的機制,稱為生成器:generator;同時生成器物件也是迭代器物件,所以他有迭代器的特性;例如支援for迴圈、next()方法…等

作用:物件中的元素是按照某種演算法推算出來的,在迴圈的過程中不斷推算出後續的元素,這樣就不必建立完整的list,從而節省大量的空間。

簡單生成器:透過將列表生成式[]改成()即可得到一個生成器物件

# 列表生成式_list = [i for i in range(10)]print(type(_list)) # print(_list) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]# 生成器_generator = (i for i in range(10))print(type(_generator)) # print(_generator) # at 0x7fbcd92c9ba0># 生成器物件取值print(_generator。__next__()) # 0print(next(_generator)) # 1# 注意從第三個元素開始了!for x in _generator: print(x) # 2,3,4,5,6,7,8,9

因為生成器物件也有迭代器的特性,所以元素迭代完後繼續呼叫next()方法則會引發StopIteration。

函式物件生成器:帶yield語句的函式物件的返回值則是個生成器物件。

def gen_generator(): yield 1def generator(): return 1print(gen_generator(), type(gen_generator())) # print(generator(), type(generator())) # 1

他與普通函式返回值有所不同,普通函式執行到return語句則直接返回程式碼不再執行;而生成器物件會執行到yield後返回,再下次呼叫時從yield語句後繼續執行。如:

詳解python三大器——迭代器、生成器、裝飾器

注意:

yield 一次只會返回一個元素,即使返回的元素是個可迭代物件,也是一次性返回

def gen_generator2(): yield [1, 2, 3]s = gen_generator2()print(next(s)) # [1, 2, 3]

yield生成器高階應用:

send()方法,傳遞值給yield返回(會立即返回!);如果傳None,則等同於next(generator)。

詳解python三大器——迭代器、生成器、裝飾器

藉助send我們可以實現一個簡單的生產者-消費者模式如:

def consumer(): r = ‘’ while True: n = yield r if not n: return print(f‘[CONSUMER] Consuming get params。。 ({n})’) if n == 3: r = ‘500 Error’ else: r = ‘200 OK’def produce(c): c。send(None) # 啟動生成器 n = 0 while n < 5: n = n + 1 print(f‘[PRODUCER] Producing with params。。 ({n})’) r = c。send(n) # 一旦n有值,則切換到consumer執行 print(f‘[PRODUCER] Consumer return : [{r}]’) if not r。startswith(‘200’): print(“消費者返回服務異常,則結束生產,並關閉消費者”) c。close() # 關閉生成器 breakconsume = consumer()produce(consume)# [PRODUCER] Producing with params。。 (1)# [CONSUMER] Consuming get params。。 (1)# [PRODUCER] Consumer return : [200 OK]# [PRODUCER] Producing with params。。 (2)# [CONSUMER] Consuming get params。。 (2)# [PRODUCER] Consumer return : [200 OK]# [PRODUCER] Producing with params。。 (3)# [CONSUMER] Consuming get params。。 (3)# [PRODUCER] Consumer return : [500 Error]# 消費者返回服務異常,則結束生產,並關閉消費者

yield from iterable 語法,基本作用為:返回一個生成器物件,提供一個“資料傳輸的管道”,yield from iterable 是 for item in iterable: yield item的縮寫;並且內部幫我們實現了很多異常處理,簡化了編碼複雜度。

yield 無法獲取生成器return的返回值:

def my_generator(n, end_case): for i in range(n): if i == end_case: return f‘當 i==`{i}`時,中斷程式。’ else: yield ig = my_generator(5, 2) # 呼叫for _i in g: # for迴圈不會顯式觸發異常,故而無法獲取到return的值 print(_i)# 輸出:# 0# 1

從上面的例子可以看出,for迭代語句不會顯式觸發異常,故而無法獲取到return的值,迭代到2的時候遇到return語句,隱式的觸發了StopIteration異常,就終止迭代了,但是在程式中不會顯示出來。

可以透過next()顯示的觸發StopIteration異常來獲取返回值:

def my_generator2(n, end_case): for i in range(n): if i == end_case: return f‘當 i==`{i}`時,中斷程式。’ else: yield ig = my_generator2(5, 2) # 呼叫try: print(next(g)) # 0 print(next(g)) # 1 print(next(g)) # 此處要觸發end_case了except StopIteration as exc: print(exc。value) # 當 i==`2`時,中斷程式。

使用yield from 可以簡化成:

def my_generator3(n, end_case): for i in range(n): if i == end_case: return f‘當 i==`{i}`時,中斷程式。’ else: yield idef wrap_my_generator(generator): # 將my_generator的返回值包裝成一個生成器 result = yield from generator yield resultg = my_generator3(5, 2) # 呼叫for _ in wrap_my_generator(g): print(_)# 輸出:# 0# 1# 當 i==`2`時,中斷程式。

yield from 有以下幾個概念名詞:

1、呼叫方:呼叫委派生成器的客戶端(呼叫方)程式碼(上文中的wrap_my_generator(g))

2、委託生成器:包含yield from表示式的生成器函式(包裝),作用就是提供一個數據傳輸的管道(上文中的wrap_my_generator)

3、子生成器:yield from後面加的生成器函式(上文中的my_generator)

詳解python三大器——迭代器、生成器、裝飾器

有興趣的同學可以結合圖和下方一起理解:

迭代器(即可指子生成器)產生的值直接返還給呼叫者

任何使用send()方法發給委派生產器(即外部生產器)的值被直接傳遞給迭代器。如果send值是None,則呼叫迭代器next()方法;如果不為None,則呼叫迭代器的send()方法。如果對迭代器的呼叫產生StopIteration異常,委派生產器恢復繼續執行yield from後面的語句;若迭代器產生其他任何異常,則都傳遞給委派生產器。

子生成器可能只是一個迭代器,並不是一個作為協程的生成器,所以它不支援。throw()和。close()方法,即可能會產生AttributeError 異常。

除了GeneratorExit 異常外的其他拋給委派生產器的異常,將會被傳遞到迭代器的throw()方法。如果迭代器throw()呼叫產生了StopIteration異常,委派生產器恢復並繼續執行,其他異常則傳遞給委派生產器。

如果GeneratorExit異常被拋給委派生產器,或者委派生產器的close()方法被呼叫,如果迭代器有close()的話也將被呼叫。如果close()呼叫產生異常,異常將傳遞給委派生產器。否則,委派生產器將丟擲GeneratorExit 異常。

當迭代器結束並丟擲異常時,yield from表示式的值是其StopIteration 異常中的第一個引數。

一個生成器中的return expr語句將會從生成器退出並丟擲 StopIteration(expr)異常。

三、裝飾器(非常實用!)

講裝飾器之前要先了解兩個概念:

物件引用 :物件名僅僅只是個繫結記憶體地址的變數

def func(): # 函式名僅僅只是個繫結記憶體地址的變數 print(“i`m running”) # 這是呼叫 func() # i`m running# 這是物件引用,引用的是記憶體地址 func2 = func print(func2 is func) # True# 透過引用進行呼叫 func2() # i`m running

閉包:定義一個函式A,然後在該函式內部再定義一個函式B,並且B函式用到了外邊A函式的變數

def out_func(): out_a = 10 def inner_func(inner_x): return out_a + inner_x return inner_funcout = out_func()print(out) # 。inner_func at 0x7ff378af5c10> out_func返回的是inner_func的記憶體地址print(out(inner_x=2)) # 12

裝飾器和閉包不同點在於:裝飾器的入參是

函式物件

,閉包入參是普通資料物件

def decorator_get_function_name(func): “”“ 獲取正在執行函式名 :return: ”“” def wrapper(*arg): “”“ wrapper :param arg: :return: ”“” print(f“當前執行方法名:{func。__name__} with params: {arg}”) return func(*arg) return wrapper@decorator_get_function_namedef test_func_add(x, y): print(x + y)@decorator_get_function_namedef test_func_sub(x, y): print(x - y)test_func_add(1, 2)# 當前執行方法名:test_func_add with params: (1, 2)# 3test_func_sub(3, 5)# 當前執行方法名:test_func_sub with params: (3, 5)# -2

常用於如鑑權校驗,例如筆者會用於登陸校驗:

def login_check(func): def wrapper(request, *args, **kwargs): if not request。session。get(‘login_status’): return HttpResponseRedirect(‘/api/login/’) return func(request, *args, **kwargs) return wrapper@login_checkdef edit_config(): pass

裝飾器內部的執行邏輯:

“”“> 1。 def login_check(func): ==>將login_check函式載入到記憶體> 。。。。> @login_check ==>此處已經在記憶體中將login_check這個函式執行了!;並不需要等edit_config()例項化呼叫> 2。 上例@login_check內部會執行以下操作:> 2。1 執行login_check函式,並將 @login_check 下面的 函式(edit_config) 作為login_check函式的引數,即:@login_check 等價於 login_check(edit_config)> 2。2 內部就會去執行: def wrapper(*args): # 校驗session。。。 return func(request, *args, **kwargs) # func是引數,此時 func 等於 edit_config,此處相當於edit_config(request, *args, **kwargs) return wrapper # 返回的 wrapper,wrapper代表的是函式物件,非函式例項化物件 2。3 其實就是將原來的 edit_config 函式塞進另外一個函式中,另一個函式當中可以做一些操作;再執行edit_config 2。4 將執行完的 login_check 函式返回值(也就是 wrapper物件)將此返回值再重新賦值給新 edit_config,即: 2。5 新edit_config = def wrapper: # 校驗session。。。 return 原來edit_config(request, *args, **kwargs) > 3。 也就是新edit_config()=login_check(edit_config):wrapper(request, *args, **kwargs):return edit_config(request, *args, **kwargs) 有點繞,大家看步驟細細理解。”“”

同樣一個函式也可以使用多個裝飾器進行裝飾,執行順序從上到下

from functools import wrapsdef w1(func): @wraps(func) def wrapper(*args, **kwargs): print(“這裡是第一個校驗”) return func(*args, **kwargs) return wrapperdef w2(func): @wraps(func) def wrapper(*args, **kwargs): print(“這裡是第二個校驗”) return func(*args, **kwargs) return wrapperdef w3(func): def wrapper(*args, **kwargs): print(“這裡是第三個校驗”) return func(*args, **kwargs) return wrapper@w2 # 這裡其實是w2(w1(f1))@w1 # 這裡是w1(f1)def f1(): print(f“i`m f1, at {f1}”)@w3def f2(): print(f“i`m f2, at {f2}”)# ====================== 例項化階段 =====================f1()# 這裡是第二個校驗# 這裡是第一個校驗# i`m f1, at f2()# 這裡是第三個校驗# i`m f2, at 。inner at 0x7febc52f5f70>

有同學可能要好奇 為什麼f1物件列印的是“”,f2物件列印的是“”(也就是步驟2。5造成的,賦的值是wrapper物件),這就跟w1和w2 內部wrapper使用的wraps裝飾器有關係了。

wraps的作用是:被修飾的函式(也就是裡面的func)的一些屬性值賦值給修飾器函式(wrapper)包括元資訊和“函式物件”等。

同時裝飾器也可以接受引數

def decorator_get_function_duration(enable): “”“ :param enable: 是否需要統計函式執行耗時 :return: ”“” print(“this is decorator_get_function_duration”) def inner(func): print(‘this is inner in decorator_get_function_duration’) @wraps(func) def wrapper(*args, **kwargs): print(‘this is a wrapper in decorator_get_function_duration。inner’) if enable: start = time。time() print(f“函式執行前:{start}”) result = func(*args, **kwargs) print(‘[%s]`s enable was %s it`s duration : %。3f s ’ % (func。__name__, enable, time。time() - start)) else: result = func(*args, **kwargs) return result return wrapper return innerdef decorator_1(func): print(‘this is decorator_1’) @wraps(func) def wrapper(*args, **kwargs): print(‘this is a wrapper in decorator_1’) return func(*args, **kwargs) return wrapperdef decorator_2(func): print(‘this is decorator_2’) @wraps(func) def wrapper(*args, **kwargs): print(‘this is a wrapper in decorator_2’) return func(*args, **kwargs) return wrapper@decorator_1 # 此處相當:decorator_1(decorator_2(decorator_get_function_duration(enable=True)(fun)))@decorator_2 # = decorator_2(decorator_get_function_duration(enable=True)(fun))@decorator_get_function_duration(enable=True) # = decorator_get_function_duration(enable=True)(fun)def fun(): time。sleep(2) print(“fun 執行完了~”)fun()# ======== enable=False ============“”“this is decorator_get_function_durationthis is inner in decorator_get_function_durationthis is decorator_2this is decorator_1this is a wrapper in decorator_1this is a wrapper in decorator_2this is a wrapper in decorator_get_function_duration。innerfun 執行完了~”“”# ======== enable=True ============“”“this is decorator_get_function_durationthis is inner in decorator_get_function_durationthis is decorator_2this is decorator_1this is a wrapper in decorator_1this is a wrapper in decorator_2this is a wrapper in decorator_get_function_duration。inner函式執行前:1634635708。648994fun 執行完了~[fun]`s enable was True it`s duration : 2。002 s ”“”