老生常談Python之裝飾器、迭代器和生成器
在學(xué)習(xí)python的時候,三大“名器”對沒有其他語言編程經(jīng)驗的人來說,應(yīng)該算是一個小難點,本次博客就博主自己對裝飾器、迭代器和生成器理解進行解釋。
為什么要使用裝飾器
什么是裝飾器?“裝飾”從字面意思來誰就是對特定的建筑物內(nèi)按照一定的思路和風(fēng)格進行美化的一種行為,所謂“器”就是工具,對于python來說裝飾器就是能夠在不修改原始的代碼情況下給其添加新的功能,比如一款軟件上線之后,我們需要在不修改源代碼和不修改被調(diào)用的方式的情況下還能為期添加新的功能,在python種就可以用裝飾器來實現(xiàn),同樣在寫代碼的時候也要考慮到后面的可擴展性,下面我們來看一步一步的看一下python的裝飾器。
一個簡單例子引入無參裝飾器
先來看簡單的幾行代碼,代碼的運行結(jié)果是先睡2秒,再打印"hello boy!":
import time
def foo():
"""打印"""
time.sleep(2)
print("Hello boy!")
foo()
我們現(xiàn)在我們需要為其添加一個程序計時功能,但是不能修改原始的代碼:
import time
def timmer(func):
def wrapper():
"""計時功能"""
time_start=time.time()
func()
time_end=time.time()
print("Run time is %f "%(time_end-time_start))
return wrapper
def foo():
"""打印"""
time.sleep(2)
print("Hello boy!")
foo=timmer(foo)
foo()
#運行結(jié)果
Hello boy!
Run time is 2.000446
看!我們沒有修改原來的代碼就實現(xiàn)了這個功能,因為函數(shù)也是對象,所以能夠?qū)⒑瘮?shù)foo當(dāng)做參數(shù)傳遞給了函數(shù)timmer。
在python中,有個更簡潔的方式來取代foo=timmer(foo),使用@timmer這種方式,這個在python中被稱為語法糖。
import time
def timmer(func):
def wrapper():
"""計時功能"""
time_start=time.time()
func()
time_end=time.time()
print("Run time is %f "%(time_end-time_start))
return wrapper
@timmer #等于 foo=timmer(foo)
def foo():
"""打印"""
time.sleep(2)
print("Hello boy!")
foo()
下面我們來一步一步的分析函數(shù)的執(zhí)行過程:
1.導(dǎo)入time模塊
import time
2.定義函數(shù)timmer,定義函數(shù)并不會執(zhí)行函數(shù)內(nèi)的代碼
def timmer(func):
3.調(diào)用裝飾器,相當(dāng)于foo=timer(foo),就是把函數(shù)foo作為參數(shù)穿給了函數(shù)timmer
@timmer
4.運行函數(shù)timmer,接受了參數(shù) func=foo
def timmer(func):
5.在函數(shù)timmer內(nèi),定義了函數(shù)wrapper,wrapper函數(shù)內(nèi)部代碼也不執(zhí)行,然后將函數(shù)wrapper作為返回值返回
return wrapper
6.將返回值賦值給了foo,在第3步中,foo=timmer(foo),還記吧
@timmer #等于 foo=timmer(foo)
7.運行函數(shù)foo(),但是這里的函數(shù)已經(jīng)不是原來的那個函數(shù)了,可以打印foo,對的,因為之前我們將wrapper作為返回值傳給了foo,所以在這里執(zhí)行foo就是在執(zhí)行wrapper了,為了再確定這一點你也可打印wrapper,它們的內(nèi)存地址相同,所以都是指向同一個地址空間:
<function timmer.<locals>.wrapper at 0x00000180E0A8A950> #打印foo的結(jié)果 <function timmer.<locals>.wrapper at 0x000001F10AD8A950> #打印wrapper的結(jié)果 foo()
8.運行函數(shù)wrapper,記錄開始時間,執(zhí)行函數(shù)func,在第4步的時候,func被foo賦值,運行func就是在運行原函數(shù)foo,睡2秒,打印字符串;
time_start=time.time()
time.sleep(2)
print("Hello boy!")
9.記錄結(jié)束時間,打印運行時間,程序結(jié)束。
Hello boy! Run time is 2.000161
有參裝飾器
在前面的例子中,原函數(shù)沒有參數(shù),下面的來看一個當(dāng)原函數(shù)有參數(shù),該怎么修改裝飾器函數(shù)呢?
import time
def timmer(func):
def wrapper(*args,**kwargs):
"""計時功能"""
start_time=time.time()
res=func(*args,**kwargs)
end_time=time.time()
print("Run time is %f"%(end_time-start_time))
return res
return wrapper
@timmer
def my_max(x,y):
"""返回兩個值的最大值"""
res=x if x > y else y
time.sleep(2)
return res
res=my_max(1,2)
print(res)
#運行結(jié)果
Run time is 2.000175
當(dāng)原函數(shù)有需要傳入?yún)?shù)的時候,在這個例子my_max有兩個位置形成需要傳入?yún)?shù),只需要在wrapper上添加兩個形參,本例子中使用了可變參數(shù)(*args,**kwargs)也是可以的,這是@timmer就等于my_max(1,2)=timmer(my_max)
下面我們來看一個帶有參數(shù)的裝飾器:
def auth(filetype):
def auth2(func):
def wrapper(*args,**kwargs):
if filetype == "file":
username=input("Please input your username:")
passwd=input("Please input your password:")
if passwd == '123456' and username == 'Frank':
print("Login successful")
func()
else:
print("login error!")
if filetype == 'SQL':
print("No SQL")
return wrapper
return auth2
@auth(filetype='file') #先先返回一個auth2 ==》@auth2 ==》 index=auth2(index) ==》 index=wrapper
def index():
print("Welcome to China")
index()
如果裝飾器本身有參數(shù),就需要多一層內(nèi)嵌函數(shù),下面我們一步一步分析執(zhí)行流程:
1.定義函數(shù)auth
def auth(filetype):
2.調(diào)用解釋器,首先要運行函數(shù)auth(filetype='file')
@auth(filetype='file')
3.運行函數(shù)auth,定義了一個函數(shù)auth2,并作為返回值返回,那么這個@auth(filetype='file')就等同于@auth2,等同于index=auth2(index)
def auth(filetype): def auth2(func): def wrapper(*args,**kwargs): return wrapper return auth2
4.auth2(index)執(zhí)行,func=index,定義函數(shù)wrapper,并返回之,這時候index其實就是等于wrapper了
def wrapper(*args,**kwargs): return wrapper
5.當(dāng)運行index,即運行wrapper,運行函數(shù)內(nèi)部代碼,filetype=="file",提示用戶輸出用戶名和密碼,判斷輸入是否正確,如果正確,則執(zhí)行函數(shù)func(),等于執(zhí)行原來的index,打印
if filetype == "file":
username=input("Please input your username:")
passwd=input("Please input your password:")
if passwd == '123456' and username == 'Frank':
print("Login successful")
func()
6.運行結(jié)果測試
Please input your username:Frank Please input your password:123456 Login successful Welcome to China
裝飾器也是可以被疊加的:
import time
#
def timmer(func):
def wrapper():
"""計時功能"""
time_start=time.time()
func()
time_end=time.time()
print("Run time is %f "%(time_end-time_start))
# print("---",wrapper)
return wrapper
def auth(filetype):
def auth2(func):
def wrapper(*args,**kwargs):
if filetype == "file":
username=input("Please input your username:")
passwd=input("Please input your password:")
if passwd == '123456' and username == 'Frank':
print("Login successful")
func()
else:
print("login error!")
if filetype == 'SQL':
print("No SQL")
return wrapper
return auth2
@timmer
@auth(filetype='file') #先先返回一個auth2 ==》@auth2 ==》 index=auth2() ==》 index=wrapper
def index():
print("Welcome to China")
index()
#測試結(jié)果
Please input your username:Frank
Please input your password:123456
Login successful
Welcome to China
Run time is 7.966267
注釋優(yōu)化
import time
def timmer(func):
def wrapper():
"""計算程序運行時間"""
start_time=time.time()
func()
end_time=time.time()
print("Run time is %s:"%(end_time-start_time))
return wrapper
@timmer
def my_index():
"""打印歡迎"""
time.sleep(1)
print("Welcome to China!")
my_index()
print(my_index.__doc__)
#運行結(jié)果
Welcome to China!
Run time is 1.0005640983581543:
計算程序運行時間
當(dāng)我們使用了裝飾器的時候,雖然沒有修改代碼本身,但是在運行的時候,比如上面這個例子,運行my_index其實在運行wrapper了,如果我們打印my_index的注釋信息,會打印wrapper()的注釋信息,那么該怎么優(yōu)化?
可以在模塊functools中導(dǎo)入wraps,具體見以下:
import time
from functools import wraps
def timmer(func):
@wraps(func)
def wrapper():
"""計算程序運行時間"""
start_time=time.time()
func()
end_time=time.time()
print("Run time is %s:"%(end_time-start_time))
return wrapper
@timmer
def my_index():
"""打印歡迎"""
time.sleep(1)
print("Welcome to China!")
my_index()
print(my_index.__doc__)
#運行結(jié)果
Welcome to China!
Run time is 1.0003223419189453:
打印歡迎
這樣,在表面看來,原函數(shù)沒有發(fā)生任何變化。
為什么要用迭代器
從字面意思,迭代就是重復(fù)反饋過程的活動,其目的通常是為了比較所需目標(biāo)或結(jié)果,在python中可以用迭代器來實現(xiàn),先來描述一下迭代器的優(yōu)缺點,如果看不懂可以先略過,等看完本博客再回頭看,相信你會理解其中的意思:
優(yōu)點:
迭代器在取值的時候是不依賴于索引的,這樣就可以遍歷那些沒有索引的對象,比如字典和文件
迭代器與列表相比,迭代器是惰性計算,更節(jié)省內(nèi)存
缺點:
無法獲取迭代器的長度,沒有列表靈活
只能往后取值,不能倒著取值
什么是迭代器
那么在python什么才算是迭代器呢?
只要對象有__iter__(),那么它就是可迭代的,迭代器可以使用函數(shù)next()來取值
下面我們來看一個簡單的迭代器:
my_list=[1,2,3] li=iter(my_list) #li=my_list.__iter__() print(li) print(next(li)) print(next(li)) print(next(li)) #運行結(jié)果 <list_iterator object at 0x000002591652C470> 2
可以看到,使用內(nèi)置函數(shù)iter可以將列表轉(zhuǎn)換成一個列表迭代器,使用next()獲取值,一次值取一個值,當(dāng)值取完了,再使用一次next()的時候,會報異常StopIteration,可以通過異常處理的方式來避免,try-except-else就是一個最常用的異常處理結(jié)構(gòu):
my_list=[1,2,3]
li=iter(my_list)
while True:
try:
print(next(li))
except StopIteration:
print("Over")
break
else:
print("get!")
#運行結(jié)果
get!
get!
get!
Over
查看可迭代對象和迭代器對象
使用Iterable模塊可以判斷對象是否是可迭代的:
from collections import Iterable
s="hello" #定義字符串
l=[1,2,3,4] #定義列表
t=(1,2,3) #定義元組
d={'a':1} #定義字典
set1={1,2,3,4} #定義集合
f=open("a.txt") #定義文本
# 查看是否都是可迭代的
print(isinstance(s,Iterable))
print(isinstance(l,Iterable))
print(isinstance(t,Iterable))
print(isinstance(d,Iterable))
print(isinstance(set1,Iterable))
print(isinstance(f,Iterable))
#運行結(jié)果
True
True
True
True
True
True
通過判斷,可以確定我們所知道的常用的數(shù)據(jù)類型都是可以被迭代的。
使用Iterator模塊可以判斷對象是否是迭代器:
from collections import Iterable,Iterator
s="hello"
l=[1,2,3,4]
t=(1,2,3)
d={'a':1}
set1={1,2,3,4}
f=open("a.txt")
# 查看是否都是可迭代的
print(isinstance(s,Iterator))
print(isinstance(l,Iterator))
print(isinstance(t,Iterator))
print(isinstance(d,Iterator))
print(isinstance(set1,Iterator))
print(isinstance(f,Iterator))
#運行結(jié)果
False
False
False
False
False
True
可知只有文件是迭代器,所以可以直接使用next(),而不需要轉(zhuǎn)換成迭代器。
什么是生成器
生產(chǎn)器就是一個是帶有yield的函數(shù)
下面來看一個簡單的生成器
def my_yield():
print('first')
yield 1
g=my_yield()
print(g)
#運行結(jié)果
<generator object my_yield at 0x0000024366D7E258>
生成器也是一個迭代器
from collections import Iterator
def my_yield():
print('first')
yield 1
g=my_yield()
print(isinstance(g,Iterator))
#運行結(jié)果
True
那就可以用next()來取值了
print(next(g)) #運行結(jié)果 first 1
生成器的執(zhí)行過程
我們來看以下下面這個例子,了解生產(chǎn)的執(zhí)行流程
def my_yield():
print('first')
yield 1
print('second')
yield 2
print('Third')
yield 3
g=my_yield()
next(g)
next(g)
next(g)
#運行結(jié)果
first
second
Third
1.定義生成器my_yield,并將其賦值給了g
def my_yield(): g=my_yield()
2.開始第一次執(zhí)行next(),開始執(zhí)行生產(chǎn)器函數(shù) ,打印第一語句,遇到y(tǒng)ileld的時候暫停,并返回一個1,如果你想打印返回值的話,這里會顯示1
print('first')
yield 1
3.再執(zhí)行2次,打印字符串(每執(zhí)行一次都會暫停一下)
print('second')
yield 2
print('Third')
yield 3
4.如果再加一次next()就會報出StopIteration異常了
生成器在每次暫停的時候,函數(shù)的狀態(tài)將被保存下來,來看下面的例子:
def foo(): i=0 while True: yield i i+=1 g=foo() for num in g: if num < 10: print(num) else: break #運行結(jié)果
for循環(huán)中隱含next(),每next一次,暫停一次,if語句判斷一次,然后執(zhí)行下一次next,可以看到我們的while循環(huán)并沒有無限循環(huán)下去,而是狀態(tài)被保存下來了。
協(xié)程函數(shù)
我們來看下面這個生成器和執(zhí)行結(jié)果
def eater(name):
print('%s start to eat food'%name)
while True:
food=yield
print('%s get %s ,to start eat'%(name,food))
print('done')
e=eater('Frank')
next(e)
e.send('egg') #給yield送一個值,并繼續(xù)執(zhí)行代碼
e.send('tomato')
#運行結(jié)果
Frank start to eat food
Frank get egg ,to start eat
Frank get tomato ,to start eat
send可直接以向yield傳值,含有yield表達(dá)式的函數(shù)我們也稱為協(xié)程函數(shù),
這運行程序的時候,不可以直接send,必須先使用next()初始化生成器。
如果存在多個這樣的函數(shù),那么我們每次執(zhí)行的時候都要去next()一下,為了防止忘記這一步操作,可以使用裝飾器初始化:
def init(func):
def wrapper(*args):
res = func(*args)
next(res) # 在這里執(zhí)行next
return res
return wrapper
@init
def eater(name):
print('%s start to eat food'%name)
while True:
food=yield
print('%s get %s ,to start eat'%(name,food))
print('done')
e=eater('Frank')
e.send('egg')
e.send('tomato')
所以在程序中有更多的生成器需要初始化的時候,直接調(diào)用這個裝飾器就可以了。
以上這篇老生常談Python之裝飾器、迭代器和生成器就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
分析PyTorch?Dataloader報錯ValueError:num_samples的另一種可能原因
這篇文章主要介紹了分析PyTorch?Dataloader報錯ValueError:num_samples的另一種可能原因,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02
python將十六進制值轉(zhuǎn)換為字符串的三種方法
這篇文章主要給大家介紹了關(guān)于python將十六進制值轉(zhuǎn)換為字符串的三種方法,工作內(nèi)容的需要需求,經(jīng)常需要使用到字符同16進制,以及各個進制之間的轉(zhuǎn)換,需要的朋友可以參考下2023-07-07
Python使用concurrent.futures模塊實現(xiàn)多進程多線程編程
Python的concurrent.futures模塊可以很方便的實現(xiàn)多進程、多線程運行,減少了多進程帶來的的同步和共享數(shù)據(jù)問題,下面就跟隨小編一起了解一下concurrent.futures模塊的具體使用吧2023-12-12

