詳解python中的生成器、迭代器、閉包、裝飾器
迭代是訪問集合元素的一種方式。迭代器是一個可以記住遍歷的位置的對象。迭代器對象從集合的第一個元素開始訪問,直到所有的元素被訪問完結(jié)束。迭代器只能往前不會后退。
1|1可迭代對象
以直接作用于 for 循環(huán)的數(shù)據(jù)類型有以下幾種:
- 一類是集合數(shù)據(jù)類型,如 list 、 tuple 、 dict 、 set 、 str 等;
- 一類是 generator ,包括生成器和帶 yield 的generator function。
這些可以直接作用于 for 循環(huán)的對象統(tǒng)稱為可迭代對象: Iterable 。
1|2判斷是否可以迭代
可以使用 isinstance() 判斷一個對象是否是 Iterable 對象:
from collections import Iterable isinstance([],Iterable) # True isinstance({},Iterable) # True isinstance(123,Iterable) # False isinstance((x for x in range(10)),Iterable) # True
1|3什么是迭代器
可以被next()函數(shù)調(diào)用并不斷返回下一個值的對象稱為迭代器:Iterator。
可以使用 isinstance()
判斷一個對象是否是 Iterator 對象:
from collections import Iterator isinstance([],Iterator) False isinstance({},Iterator) False isinstance((x for x in range(10)),Iterator) # True
生成器都是迭代器。
1|4iter()函數(shù)
雖然list 、 tuple 、 dict 、 set 、 str 等是可迭代對象,但他們不是迭代器??梢酝ㄟ^iter()函數(shù)把可迭代對象編程迭代器。
isinstance(iter([]),Iterator) # True isinstance(iter({}),Iterator) # True isinstance(iter("asdf"),Iterator) # True
1|5總結(jié):
- 凡是可作用于 for 循環(huán)的對象都是 Iterable 類型。
- 凡是可作用于 next() 函數(shù)的對象都是 Iterator 類型。
- 集合數(shù)據(jù)類型如 list 、 dict 、 str 等是 Iterable 但不是 Iterator ,不過可以通過 iter() 函數(shù)獲得一個 Iterator 對象。
2|0生成器
2|1什么是生成器
我們可以通過列表生成式來創(chuàng)建一個列表,但是收到內(nèi)存的限制,列表的容量肯定是有限的。而且,創(chuàng)建一個包含100萬個元素的列表,不僅占用很大的存儲空間,如果我們僅僅需要訪問前面幾個元素,那后面絕大多數(shù)元素占用的空間都白白浪費了。所以,如果列表元素可以按照某種算法推算出來,那我們是否可以在循環(huán)的過程中不斷推算出后續(xù)的元素呢?這樣就不必創(chuàng)建完整的list,從而節(jié)省大量的空間。在Python中,這種一邊循環(huán)一邊計算的機制,稱為生成器:generator。
1|1修改列表推導(dǎo)式創(chuàng)建生成器的方法
最簡單的方法是把列表生成式中的 [ ] 改成 ( ) 就好了。
a = [x for x in range(10)] print(a) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] b = (x for x in range(10)) print(b) # <generator object <genexpr> at 0x03387DB0>
如何遍歷生成器
我們發(fā)現(xiàn)生成器不是能直接打印出來的,我們可以通過next()函數(shù)來獲得生成器的下一個返回值。
生成器保存的是算法,每次調(diào)用 next(G) ,就計算出 G 的下一個元素的值,直到計算到最后一個元素,沒有更多的元素時,拋出 StopIteration 的異常。
**使用next() 或者__next __():** print(next(b)) # 0 print(next(b)) # 1 print(next(b)) # 2 print(next(b)) # 3 print(next(b)) # 4 print(next(b)) # 5 print(b.__next__()) # 6 print(b.__next__()) # 7 print(b.__next__()) # 8 print(b.__next__()) # 9 print(b.__next__()) # Traceback (most recent call last): # File "<input>", line 2, in <module> # StopIteration
那么有什么簡單的方法呢?因為生成器是可迭代對象,也可以使用for循環(huán)來遍歷它,并且不需要關(guān)心 StopIteration 異常。
b = (x for x in range(10)) for x in b: print(x) # 0 # 1 # 2 # 3 #...
1|2函數(shù)中使用yield創(chuàng)建生成器的方法
如果如果生成器推算的算法比較復(fù)雜,用類似列表生成式的 for 循環(huán)無法實現(xiàn)的時候,還可以用函數(shù)來實現(xiàn)。把你要返回的值前面加yield 即可。
使用函數(shù)實現(xiàn)上面代碼:
def fn(): for x in range(3): yield x # 遍歷函數(shù)實現(xiàn)的生成器 f = fn() print(next(f)) # 0 print(next(f)) # 1 print(next(f)) # 2 print(next(f)) # Traceback (most recent call last): # File "<input>", line 1, in <module> # StopIteration
使用生成器實現(xiàn)斐波拉契數(shù)列:
def fib(count): n = 0 a,b = 0,1 while n < count: yield b a,b = b,a+b n += 1 return "done" f = fib(5) print(next(f)) # 1 print(next(f)) # 1 print(next(f)) # 2 print(next(f)) # 3 print(next(f)) # 5 print(next(f)) # Traceback (most recent call last): # File "<input>", line 1, in <module> # StopIteration: done
yield執(zhí)行流程
- 當(dāng)執(zhí)行next(f)時,函數(shù)開始執(zhí)行到y(tǒng)ield,yield 右邊的變量x作為next()的返回值被返回,此時函數(shù)保存當(dāng)前的運行狀態(tài),并暫停執(zhí)行。
- 再次調(diào)用next(f)時,函數(shù)從上次暫停的位置開始繼續(xù)執(zhí)行,再次遇到y(tǒng)ield時重復(fù)上面的操作
- 直到生成器遍歷結(jié)束
我們在循環(huán)過程中不斷調(diào)用 yield ,就會不斷中斷。當(dāng)然要給循環(huán)設(shè)置一個條件來退出循環(huán),不然就會產(chǎn)生一個無限數(shù)列出來。同樣的,把函數(shù)改成generator后,我們基本上從來不會用 next() 來獲取下一個返回值,而是直接使用 for 循環(huán)來迭代:
for x in fib(5): print(x) # 1 # 1 # 2 # 3 # 5
但是用for循環(huán)調(diào)用generator時,發(fā)現(xiàn)拿不到generator的return語句的返回值。如果想要拿到返回值,必須捕獲StopIteration錯誤,返回值包含在StopIteration的value中:
f = fib(5) while True: try: print(next(f)) except StopIteration as e: print("生成器返回值:%s"%e.value) break # 1 # 1 # 2 # 3 # 5 # 生成器返回值:done
1|3send方法
def gen(): i = 0 while i<3: temp = yield i print(temp) i+=1 g = gen() print(g.__next__()) # 0 print(g.send(None)) # None # 1 print(next(g)) # None # 2 print(g.send("哈哈") # 哈哈 # Traceback (most recent call last): # File "<input>", line 1, in <module> # StopIteration
上面代碼可以看出next()、next ()、send(None)是等價的并沒有什么區(qū)別。
- send()其實是比他們更高級的,在之前的代碼中yield i是沒有返回值的即輸出為None。
- 如果修改send()的形參,那么yield i 的返回值就是括號中的形參,在上面的代碼中g(shù).send("哈哈")相當(dāng)于temp = "哈哈",并且g.send("哈哈")的返回值就是變量i。
- 使用send時要注意,第一次調(diào)用生成器對象時,send()不能傳參數(shù)否則會報錯,第一次必須是send(None),或者第一次調(diào)
- 用next()、next ()也可以。
3|0閉包
3|1什么是閉包
在函數(shù)內(nèi)部再定義一個函數(shù),并且這個函數(shù)用到了外邊函數(shù)的變量,那么將這個函數(shù)以及用到的一些變量稱之為閉包
def test(number): def test_in(number_in): print("test_in函數(shù) 的number_in=%s"%number_in) return number_in+number # 返回test_in函數(shù)的引用 return test_in ret = test(20) print(ret(100)) # 相當(dāng)于直接調(diào)用test_in函數(shù),并給它傳值100 # test_in函數(shù) 的number_in=100 # 120 print(ret(200)) # test_in函數(shù) 的number_in=200 # 220
3|2閉包的一個例子
在數(shù)學(xué)中,一次函數(shù):y=kx+b,在一條確定的直線中,它的k、b是不變的。求y時,根據(jù)確定的k、b、x來求出。
def line_conf(k, b): def line(x): return k*x + b return line line1 = line_conf(1, 1) line2 = line_conf(4, 5) print(line1(5)) print(line2(5)) # 6 # 25
如果沒有閉包,我們需要每次創(chuàng)建直線函數(shù)的時候同時說明a,b,x。這樣,我們就需要更多的參數(shù)傳遞,也減少了代碼的可移植性。
4|0裝飾器
4|1什么是裝飾器
裝飾器就是對一個函數(shù)進行裝飾,給這個函數(shù)增加額外的功能。
def logging(func): def wrap(): print("正在打印日志!") func() return wrap
@logging # 該裝飾器為函數(shù)增加了打印日志的額外功能,并且之前函數(shù)內(nèi)部代碼不會改變。
def login(): print("張三正在登陸。") login() # 正在打印日志! # 張三正在登陸。 4|2兩個裝飾器 def makeBold(fn1): def wrapped(): print("----1----") return "<b>"+fn1()+"</b>" return wrapped def makeItalic(fn2): def wrapped(): print("----2----") return "<i>"+fn2()+"</i>" return wrapped @makeBold @makeItalic def f1(): print("----3----") return "hello world" ret = f1() # 此時f1并不是f1函數(shù),它是makeBold裝飾器返回的wrapped函數(shù)的引用。 print(ret) """ 輸出結(jié)果: ----1---- ----2---- ----3---- <b><i>hello world</i></b> """
調(diào)用流程:
- 把函數(shù)f1的引用傳入裝飾器makeItalic中的變量fn2,此時fn2指向f1函數(shù)。
- 把裝飾器makeItalic中wrapped函數(shù)的引用傳入裝飾器makeBold的變量fn1,此時fn1指向裝飾器makeItalic中的wrapped函數(shù)。
- ret = f1()表是執(zhí)行f1所指向的函數(shù),并返回給ret。
4|3裝飾器帶參數(shù)
一般情況下裝飾器內(nèi)部函數(shù)的參數(shù)都是不定長參數(shù),保證通用性,確保裝飾任何函數(shù)時都不會出錯。
def logging(func): def wrap(*args,**kwargs): print("正在打印日志!") func(*args,**kwargs) return wrap
@logging # 該裝飾器為函數(shù)增加了打印日志的額外功能,并且之前函數(shù)內(nèi)部代碼不會改變。
def login(name,dic): print("%s正在登陸。"%name) print(dic) login("李四",{"sex":"男"}) # 正在打印日志! # 李四正在登陸。 # {'sex': '男'} 4|4類裝飾器 class Test(object): def __init__(self, func): print("---初始化---") print("func name is %s"%func.__name__) self.__func = func def __call__(self): print("---裝飾器中的功能---") self.__func() @Test def test(): print("----test---") test() """
輸出結(jié)果:
---初始化---
func name is test
---裝飾器中的功能---
----test---
"""
說明:
- 當(dāng)用Test來裝作裝飾器對test函數(shù)進行裝飾的時候,首先會創(chuàng)建Test的實例對象并且會把test這個函數(shù)名當(dāng)做參數(shù)傳遞到__init__方法中即在
__init__
方法中的func變量指向了test函數(shù)體。
- test函數(shù)相當(dāng)于指向了用Test創(chuàng)建出來的實例對象。
- 當(dāng)在使用test()進行調(diào)用時,就相當(dāng)于讓這個對象(),因此會調(diào)用這個對象的__call__方法。
- 為了能夠在__call__方法中調(diào)用原來test指向的函數(shù)體,所以在__init __方法中就需要一個實例屬性來保存這個函數(shù)體的引用所以才有了
self.__func = func
這句代碼,從而在調(diào)用__call __方法中能夠調(diào)用到test之前的函數(shù)體。
總結(jié)
以上所述是小編給大家介紹的python中的生成器、迭代器、閉包、裝飾器,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
如果你覺得本文對你有幫助,歡迎轉(zhuǎn)載,煩請注明出處,謝謝!
相關(guān)文章
Python利用selenium建立代理ip池訪問網(wǎng)站的全過程
selenium控制瀏覽器也是可以使用代理ip的,下面這篇文章主要給大家介紹了關(guān)于Python利用selenium建立代理ip池訪問網(wǎng)站的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-03-03YOLOv5車牌識別實戰(zhàn)教程(五)字符分割與識別
這篇文章主要介紹了YOLOv5車牌識別實戰(zhàn)教程(五)字符分割與識別,在這個教程中,我們將一步步教你如何使用YOLOv5進行車牌識別,幫助你快速掌握YOLOv5車牌識別技能,需要的朋友可以參考下2023-04-04Python實現(xiàn)檢測文件的MD5值來查找重復(fù)文件案例
這篇文章主要介紹了Python實現(xiàn)檢測文件的MD5值來查找重復(fù)文件案例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03Python 結(jié)巴分詞實現(xiàn)關(guān)鍵詞抽取分析
這篇文章主要介紹了Python 結(jié)巴分詞實現(xiàn)關(guān)鍵詞抽取分析,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10