溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Python中的迭代器、生成器和裝飾器的功能

發(fā)布時(shí)間:2021-09-04 18:40:07 來(lái)源:億速云 閱讀:155 作者:chen 欄目:編程語(yǔ)言

這篇文章主要講解了“Python中的迭代器、生成器和裝飾器的功能”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Python中的迭代器、生成器和裝飾器的功能”吧!

迭代器

1. 可迭代對(duì)象

1) 可迭代對(duì)象定義

對(duì)于迭代器來(lái)說(shuō),我們更熟悉的應(yīng)該是可迭代對(duì)象,之前無(wú)論是源碼還是講課中或多或少我們提到過(guò)可迭代對(duì)象這個(gè)詞。之前為了便于大家理解可迭代對(duì)象,可能解釋的不是很正確,所以今天我們正式的聊一聊什么是可迭代對(duì)象。從字面意思來(lái)說(shuō),我們先對(duì)其進(jìn)行拆解:什么是對(duì)象?Python中一切皆對(duì)象,之前我們講過(guò)的一個(gè)變量,一個(gè)列表,一個(gè)字符串,文件句柄,函數(shù)名等等都可稱作一個(gè)對(duì)象,其實(shí)一個(gè)對(duì)象就是一個(gè)實(shí)例,就是一個(gè)實(shí)實(shí)在在的東西。那么什么叫迭代?其實(shí)我們?cè)谌粘I钪薪?jīng)常遇到迭代這個(gè)詞兒,更新迭代等等,迭代就是一個(gè)重復(fù)的過(guò)程,但是不能是單純的重復(fù)(如果只是單純的重復(fù)那么他與循環(huán)沒(méi)有什么區(qū)別)每次重復(fù)都是基于上一次的結(jié)果而來(lái)。比如你爹生你,你生你爹,哦不對(duì),你生你兒子,你兒子生你孫子等等,每一代都是不一樣的;還有你使用過(guò)得app,微信,抖音等,隔一段時(shí)間就會(huì)基于上一次做一些更新,那么這就是迭代??傻鷮?duì)象從字面意思來(lái)說(shuō)就是一個(gè)可以重復(fù)取值的實(shí)實(shí)在在的東西。

那么剛才我們是從字面意思分析的什么是可迭代對(duì)象,到目前為止我們接觸到的可迭代對(duì)象有哪些呢?

str  list   tuple  dic  set  range 文件句柄等,那么int,bool這些為什么不能稱為可迭代對(duì)象呢?雖然在字面意思這些看著不符合,但是我們要有一定的判斷標(biāo)準(zhǔn)或者規(guī)則去判斷該對(duì)象是不是可迭代對(duì)象。

在python中,但凡內(nèi)部含有iter方法的對(duì)象,都是可迭代對(duì)象。

2) 查看對(duì)象內(nèi)部方法

該對(duì)象內(nèi)部含有什么方法除了看源碼還有什么其他的解決方式么?當(dāng)然有了, 可以通過(guò)dir() 去判斷一個(gè)對(duì)象具有什么方法

s1 = 'alex'
print(dir(s1))

dir()會(huì)返回一個(gè)列表,這個(gè)列表中含有該對(duì)象的以字符串的形式所有方法名。這樣我們就可以判斷python中的一個(gè)對(duì)象是不是可迭代對(duì)象了:

s1 = 'alex'
i = 100
print('__iter__' in dir(i))  # False
print('__iter__' in dir(s1))  # True
3)小結(jié):

從字面意思來(lái)說(shuō):可迭代對(duì)象就是一個(gè)可以重復(fù)取值的實(shí)實(shí)在在的東西。

從專業(yè)角度來(lái)說(shuō):但凡內(nèi)部含有iter方法的對(duì)象,都是可迭代對(duì)象。

可迭代對(duì)象可以通過(guò)判斷該對(duì)象是否有’iter’方法來(lái)判斷。

可迭代對(duì)象的優(yōu)點(diǎn):

可以直觀的查看里面的數(shù)據(jù)。

可迭代對(duì)象的缺點(diǎn):

1. 占用內(nèi)存。

2. 可迭代對(duì)象不能迭代取值(除去索引,key以外)。

那么這個(gè)缺點(diǎn)有人就提出質(zhì)疑了,即使拋去索引,key以外,這些我可以通過(guò)for循環(huán)進(jìn)行取值呀!對(duì),他們都可以通過(guò)for循環(huán)進(jìn)行取值,其實(shí)for循環(huán)在底層做了一個(gè)小小的轉(zhuǎn)化,就是先將可迭代對(duì)象轉(zhuǎn)化成迭代器,然后在進(jìn)行取值的。那么接下來(lái),我們就看看迭代器是個(gè)什么鬼。

2. 迭代器

1) 迭代器的定義

從字面意思來(lái)說(shuō)迭代器,是一個(gè)可以迭代取值的工具,器:在這里當(dāng)做工具比較合適。

從專業(yè)角度來(lái)說(shuō):迭代器是這樣的對(duì)象:實(shí)現(xiàn)了無(wú)參數(shù)的next方法,返回序列中的下一個(gè)元素,如果沒(méi)有元素了,那么拋出StopIteration異常.python中的迭代器還實(shí)現(xiàn)了iter方法,因此迭代器也可以迭代。 出自《流暢的python》

那么對(duì)于上面的解釋有一些超前,和難以理解,不用過(guò)于糾結(jié),我們簡(jiǎn)單來(lái)說(shuō):在python中,內(nèi)部含有'Iter'方法并且含有'next'方法的對(duì)象就是迭代器。

2) 如何判斷該對(duì)象是否是迭代器

ok,那么我們有了這個(gè)定義,我們就可以判斷一些對(duì)象是不是迭代器或者可迭代對(duì)象了了,請(qǐng)判斷這些對(duì)象:str list tuple dict set range 文件句柄 哪個(gè)是迭代器,哪個(gè)是可迭代對(duì)象:

o1 = 'alex'
o2 = [1, 2, 3]
o3 = (1, 2, 3)
o4 = {'name': '太白','age': 18}
o5 = {1, 2, 3}
f = open('file',encoding='utf-8', mode='w')
print('__iter__' in dir(o1))  # True
print('__iter__' in dir(o2))  # True
print('__iter__' in dir(o3))  # True
print('__iter__' in dir(o4))  # True
print('__iter__' in dir(o5))  # True
print('__iter__' in dir(f))  # True
# hsagn
print('__next__' in dir(o1))  # False
print('__next__' in dir(o2))  # False
print('__next__' in dir(o3))  # False
print('__next__' in dir(o4))  # False
print('__next__' in dir(o5))  # False
print('__next__' in dir(f))  # True
f.close()

通過(guò)以上代碼可以驗(yàn)證,之前我們學(xué)過(guò)的這些對(duì)象,只有文件句柄是迭代器,剩下的那些數(shù)據(jù)類型都是可迭代對(duì)象。

3) 可迭代對(duì)象如何轉(zhuǎn)化成迭代器:
l1 = [1, 2, 3, 4, 5, 6]
obj = l1.__iter__() 
# 或者 iter(l1)print(obj) 
# <list_iterator object at 0x000002057FE1A3C8>
4) 迭代器取值:

可迭代對(duì)象是不可以一直迭代取值的(除去用索引,切片以及Key),但是轉(zhuǎn)化成迭代器就可以了,迭代器是利用next()進(jìn)行取值:

l1 = [1, 2, 3,]
obj = l1.__iter__()  # 或者 iter(l1)
# print(obj)  # <list_iterator object at 0x000002057FE1A3C8>
ret = obj.__next__()
print(ret)
ret = obj.__next__()
print(ret)
ret = obj.__next__()
print(ret)
ret = obj.__next__()  # StopIteration
print(ret)
# 迭代器利用next取值:一個(gè)next取對(duì)應(yīng)的一個(gè)值,如果迭代器里面的值取完了,還要next,
# 那么就報(bào)StopIteration的錯(cuò)誤。
5) while模擬for的內(nèi)部循環(huán)機(jī)制:

剛才我們提到了,for循環(huán)的循環(huán)對(duì)象一定要是可迭代對(duì)象,但是這不意味著可迭代對(duì)象就可以取值,因?yàn)閒or循環(huán)的內(nèi)部機(jī)制是:將可迭代對(duì)象轉(zhuǎn)換成迭代器,然后利用next進(jìn)行取值,最后利用異常處理處理StopIteration拋出的異常。

l1 = [1, 2, 3, 4, 5, 6]
# 1 將可迭代對(duì)象轉(zhuǎn)化成迭代器
obj = iter(l1)
# 2,利用while循環(huán),next進(jìn)行取值
while 1:
    # 3,利用異常處理終止循環(huán)
    try:
        print(next(obj))
    except StopIteration:
        break
6)小結(jié):

從字面意思來(lái)說(shuō):迭代器就是可以迭代取值的工具。

從專業(yè)角度來(lái)說(shuō):在python中,內(nèi)部含有'Iter'方法并且含有'next'方法的對(duì)象就是迭代器。

迭代器的優(yōu)點(diǎn):

節(jié)省內(nèi)存。
              迭代器在內(nèi)存中相當(dāng)于只占一個(gè)數(shù)據(jù)的空間:因?yàn)槊看稳≈刀忌弦粭l數(shù)據(jù)會(huì)在內(nèi)存釋放,加載當(dāng)前的此條數(shù)據(jù)。

惰性機(jī)制。
                 next一次,取一個(gè)值,絕不過(guò)多取值。

有一個(gè)迭代器模式可以很好的解釋上面這兩條:迭代是數(shù)據(jù)處理的基石。掃描內(nèi)存中放不下的數(shù)據(jù)集時(shí),我們要找到一種惰性獲取數(shù)據(jù)項(xiàng)的方式,即按需一次獲取一個(gè)數(shù)據(jù)項(xiàng)。這就是迭代器模式。

迭代器的缺點(diǎn):

不能直觀的查看里面的數(shù)據(jù)。

取值時(shí)不走回頭路,只能一直向下取值。

l1 = [1, 2, 3, 4, 5, 6]
obj = iter(l1)

for i in range(2):
    print(next(obj))

for i in range(2):
    print(next(obj))

3. 可迭代對(duì)象與迭代器對(duì)比

我們今天比較深入的了解了可迭代對(duì)象與迭代器,接下來(lái)我們說(shuō)一下這兩者之間比較與應(yīng)用:

可迭代對(duì)象:

是一個(gè)私有的方法比較多,操作靈活(比如列表,字典的增刪改查,字符串的常用操作方法等),比較直觀,但是占用內(nèi)存,而且不能直接通過(guò)循環(huán)迭代取值的這么一個(gè)數(shù)據(jù)集。

應(yīng)用:當(dāng)你側(cè)重于對(duì)于數(shù)據(jù)可以靈活處理,并且內(nèi)存空間足夠,將數(shù)據(jù)集設(shè)置為可迭代對(duì)象是明確的選擇。

迭代器:

是一個(gè)非常節(jié)省內(nèi)存,可以記錄取值位置,可以直接通過(guò)循環(huán)+next方法取值,但是不直觀,操作方法比較單一的數(shù)據(jù)集。

應(yīng)用:當(dāng)你的數(shù)據(jù)量過(guò)大,大到足以撐爆你的內(nèi)存或者你以節(jié)省內(nèi)存為首選因素時(shí),將數(shù)據(jù)集設(shè)置為迭代器是一個(gè)不錯(cuò)的選擇。(可參考為什么python把文件句柄設(shè)置成迭代器)。

生成器

1.1 初識(shí)生成器

什么是生成器?這個(gè)概念比較模糊,各種文獻(xiàn)都有不同的理解,但是核心基本相同。生成器的本質(zhì)就是迭代器,在python社區(qū)中,大多數(shù)時(shí)候都把迭代器和生成器是做同一個(gè)概念。不是相同么?為什么還要?jiǎng)?chuàng)建生成器?生成器和迭代器也有不同,唯一的不同就是:迭代器都是Python給你提供的已經(jīng)寫(xiě)好的工具或者通過(guò)數(shù)據(jù)轉(zhuǎn)化得來(lái)的,(比如文件句柄,iter([1,2,3])。生成器是需要我們自己用python代碼構(gòu)建的工具。最大的區(qū)別也就如此了。

1.2 生成器的構(gòu)建方式

在python中有三種方式來(lái)創(chuàng)建生成器:

  1. 通過(guò)生成器函數(shù)

  2. 通過(guò)生成器推導(dǎo)式

  3. python內(nèi)置函數(shù)或者模塊提供(其實(shí)1,3兩種本質(zhì)上差不多,都是通過(guò)函數(shù)的形式生成,只不過(guò)1是自己寫(xiě)的生成器函數(shù),3是python提供的生成器函數(shù)而已)

1.3 生成器函數(shù)

我們先來(lái)研究通過(guò)生成器函數(shù)構(gòu)建生成器。

首先,我們先看一個(gè)很簡(jiǎn)單的函數(shù):

def func():

    print(11)

    return 22

ret = func()

print(ret)

# 運(yùn)行結(jié)果:

11

22

將函數(shù)中的return換成yield,這樣func就不是函數(shù)了,而是一個(gè)生成器函數(shù)

def func():
    print(11)
    yield 22

我們這樣寫(xiě)沒(méi)有任何的變化,這是為什么呢? 我們來(lái)看看函數(shù)名加括號(hào)獲取到的是什么?

def func():

    print(11)

    yield 22

ret = func()

print(ret)

# 運(yùn)行結(jié)果:

<generator object func at 0x000001A575163888>

運(yùn)行的結(jié)果和最上面的不一樣,為什么呢?? 由于函數(shù)中存在yield,那么這個(gè)函數(shù)就是一個(gè)生成器函數(shù).

我們?cè)趫?zhí)行這個(gè)函數(shù)的時(shí)候.就不再是函數(shù)的執(zhí)行了.而是獲取這個(gè)生成器對(duì)象,那么生成器對(duì)象如何取值呢?

之前我們說(shuō)了,生成器的本質(zhì)就是迭代器.迭代器如何取值,生成器就如何取值。所以我們可以直接執(zhí)行next()來(lái)執(zhí)行以下生成器

def func():

     print("111")

     yield 222

gener = func() # 這個(gè)時(shí)候函數(shù)不會(huì)執(zhí)?. ?是獲取到?成器

ret = gener.__next__() # 這個(gè)時(shí)候函數(shù)才會(huì)執(zhí)?

print(ret)  # 并且yield會(huì)將func生產(chǎn)出來(lái)的數(shù)據(jù) 222 給了 ret。  

結(jié)果:

111

222

并且我的生成器函數(shù)中可以寫(xiě)多個(gè)yield。

def func():

    print("111")

    yield 222

    print("333")

    yield 444

gener = func()

ret = gener.__next__()

print(ret)

ret2 = gener.__next__()

print(ret2)

ret3 = gener.__next__()

# 最后?個(gè)yield執(zhí)?完畢. 再次__next__()程序報(bào)錯(cuò)

print(ret3)

結(jié)果:

111

222

333

444

當(dāng)程序運(yùn)行完最后一個(gè)yield,那么后面繼續(xù)運(yùn)行next()程序會(huì)報(bào)錯(cuò),一個(gè)yield對(duì)應(yīng)一個(gè)next,next超過(guò)yield數(shù)量,就會(huì)報(bào)錯(cuò),與迭代器一樣。

yield與return的區(qū)別:

return一般在函數(shù)中只設(shè)置一個(gè),他的作用是終止函數(shù),并且給函數(shù)的執(zhí)行者返回值。

yield在生成器函數(shù)中可設(shè)置多個(gè),他并不會(huì)終止函數(shù),next會(huì)獲取對(duì)應(yīng)yield生成的元素。

舉例:

我們來(lái)看一下這個(gè)需求:老男孩向樓下賣(mài)包子的老板訂購(gòu)了10000個(gè)包子.包子鋪老板非常實(shí)在,一下就全部都做出來(lái)了  

def eat():

    lst = []

    for i in range(1,10000):

        lst.append('包子'+str(i))

    return lst

e = eat()

print(e)

這樣做沒(méi)有問(wèn)題,但是我們由于學(xué)生沒(méi)有那么多,只吃了2000個(gè)左右,剩下的8000個(gè),就只能占著一定的空間,放在一邊了。如果包子鋪老板效率夠高,我吃一個(gè)包子,你做一個(gè)包子,那么這就不會(huì)占用太多空間存儲(chǔ)了,完美。

def eat():

    for i in range(1,10000):

        yield '包子'+str(i)

e = eat()

for i in range(200):
    next(e)

這兩者的區(qū)別:

第一種是直接把包子全部做出來(lái),占用內(nèi)存。

第二種是吃一個(gè)生產(chǎn)一個(gè),非常的節(jié)省內(nèi)存,而且還可以保留上次的位置。

def eat():

    for i in range(1,10000):

        yield '包子'+str(i)

e = eat()

for i in range(200):
    next(e)

for i in range(300):
    next(e)
# 多次next包子的號(hào)碼是按照順序記錄的。

1.4 send 方法

·接下來(lái)我們?cè)賮?lái)認(rèn)識(shí)一個(gè)新的東西,send方法

# next只能獲取yield生成的值,但是不能傳遞值。
def gen(name):
    print(f'{name} ready to eat')
    while 1:
        food = yield
        print(f'{name} start to eat {food}')

dog = gen('alex')
next(dog)
next(dog)
next(dog)

# 而使用send這個(gè)方法是可以的。
def gen(name):
    print(f'{name} ready to eat')
    while 1:
        food = yield 222
        print(f'{name} start to eat {food}')

dog = gen('alex')
next(dog)  # 第一次必須用next讓指針停留在第一個(gè)yield后面
# 與next一樣,可以獲取到y(tǒng)ield的值
ret = dog.send('骨頭')
print(ret)

def gen(name):
    print(f'{name} ready to eat')
    while 1:
        food = yield
        print(f'{name} start to eat {food}')

dog = gen('alex')
next(dog)
# 還可以給上一個(gè)yield發(fā)送值
dog.send('骨頭')
dog.send('狗糧')
dog.send('香腸')

send和next()區(qū)別:

相同點(diǎn):

send 和 next()都可以讓生成器對(duì)應(yīng)的yield向下執(zhí)行一次。

都可以獲取到y(tǒng)ield生成的值。

不同點(diǎn):

第一次獲取yield值只能用next不能用send(可以用send(None))。

send可以給上一個(gè)yield置傳遞值。

1.4 yield from

在python3中提供一種可以直接把可迭代對(duì)象中的每一個(gè)數(shù)據(jù)作為生成器的結(jié)果進(jìn)行返回

# 對(duì)比yield 與 yield from 
def func():
    lst = ['衛(wèi)龍','老冰棍','北冰洋','牛羊配']
    yield lst
g = func()
print(g)
print(next(g))  # 只是返回一個(gè)列表

def func():
    lst = ['衛(wèi)龍','老冰棍','北冰洋','牛羊配']
    yield from lst
g = func()
print(g)
# 他會(huì)將這個(gè)可迭代對(duì)象(列表)的每個(gè)元素當(dāng)成迭代器的每個(gè)結(jié)果進(jìn)行返回。
print(next(g))
print(next(g))
print(next(g))
print(next(g))
'''
yield from ['衛(wèi)龍','老冰棍','北冰洋','牛羊配'] 
等同于:
    yield '衛(wèi)龍'
    yield '老冰棍'
    yield '北冰洋'
    yield '牛羊配'
'''

有個(gè)小坑,yield from 是將列表中的每一個(gè)元素返回,所以 如果寫(xiě)兩個(gè)yield from 并不會(huì)產(chǎn)生交替的效果

def func():
    lst1 = ['衛(wèi)龍','老冰棍','北冰洋','牛羊配']
    lst2 = ['饅頭','花卷','豆包','大餅']
    yield from lst1
    yield from lst2

g = func()
for i in g:
    print(i)

裝飾器

1. 開(kāi)放封閉原則

什么是開(kāi)放封閉原則?有的同學(xué)問(wèn)開(kāi)放,封閉這是兩個(gè)反義詞這還能組成一個(gè)原則么?這不前后矛盾么?其實(shí)不矛盾。開(kāi)放封閉原則是分情況討論的。

我們的軟件一旦上線之后(比如你的軟件主要是多個(gè)函數(shù)組成的),那么這個(gè)軟件對(duì)功能的擴(kuò)展應(yīng)該是開(kāi)放的,比如你的游戲一直在迭代更新,推出新的玩法,新功能。但是對(duì)于源代碼的修改是封閉的。你就拿函數(shù)舉例,如果你的游戲源代碼中有一個(gè)函數(shù)是閃躲的功能,那么你這個(gè)函數(shù)肯定是被多個(gè)地方調(diào)用的,比如對(duì)方扔雷,對(duì)方開(kāi)槍,對(duì)方用刀,你都會(huì)調(diào)用你的閃躲功能,那么如果你的閃躲功能源碼改變了,或者調(diào)用方式改變了,當(dāng)對(duì)方發(fā)起相應(yīng)的動(dòng)作,你在調(diào)用你的閃躲功能,就會(huì)發(fā)生問(wèn)題。所以,開(kāi)放封閉原則具體具體定義是這樣:

1.對(duì)擴(kuò)展是開(kāi)放的

我們說(shuō),任何一個(gè)程序,不可能在設(shè)計(jì)之初就已經(jīng)想好了所有的功能并且未來(lái)不做任何更新和修改。所以我們必須允許代碼擴(kuò)展、添加新功能。

2.對(duì)修改是封閉的

就像我們剛剛提到的,因?yàn)槲覀儗?xiě)的一個(gè)函數(shù),很有可能已經(jīng)交付給其他人使用了,如果這個(gè)時(shí)候我們對(duì)函數(shù)內(nèi)部進(jìn)行修改,或者修改了函數(shù)的調(diào)用方式,很有可能影響其他已經(jīng)在使用該函數(shù)的用戶。OK,理解了開(kāi)封封閉原則之后,我們聊聊裝飾器。

什么是裝飾器?從字面意思來(lái)分析,先說(shuō)裝飾,什么是裝飾? 裝飾就是添加新的,比如你家剛買(mǎi)的房子,下一步就是按照自己的喜歡的方式設(shè)計(jì),進(jìn)行裝修,裝飾,地板,墻面,家電等等。什么是器?器就是工具,也是功能,那裝飾器就好理解了:就是添加新的功能。

比如我現(xiàn)在不會(huì)飛,怎么才能讓我會(huì)飛?給我加一個(gè)翅膀,我就能飛了。那么你給我加一個(gè)翅膀,它會(huì)改變我原來(lái)的行為么?我之前的吃喝拉撒睡等生活方式都不會(huì)改變。它就是在我原來(lái)的基礎(chǔ)上,添加了一個(gè)新的功能。

今天我們講的裝飾器(裝修,翅膀)是以功能為導(dǎo)向的,就是一個(gè)函數(shù)。

被裝飾的對(duì)象:比如毛坯房,我本人,其實(shí)也是一個(gè)函數(shù)。0

所以裝飾器最終最完美的定義就是:在不改變?cè)谎b飾的函數(shù)的源代碼以及調(diào)用方式下,為其添加額外的功能。

2. 初識(shí)裝飾器

接下來(lái),我們通過(guò)一個(gè)例子來(lái)為大家講解這個(gè)裝飾器:

需求介紹:你現(xiàn)在xx科技有限公司的開(kāi)發(fā)部分任職,領(lǐng)導(dǎo)給你一個(gè)業(yè)務(wù)需求讓你完成:讓你寫(xiě)代碼測(cè)試小ming寫(xiě)的函數(shù)的執(zhí)行效率。

def index():
    print('歡迎訪問(wèn)博客園主頁(yè)')

版本1:

需求分析:你要想測(cè)試此函數(shù)的執(zhí)行效率,你應(yīng)該怎么做?應(yīng)該在此函數(shù)執(zhí)行前記錄一個(gè)時(shí)間, 執(zhí)行完畢之后記錄一個(gè)時(shí)間,這個(gè)時(shí)間差就是具體此函數(shù)的執(zhí)行效率。那么執(zhí)行時(shí)間如何獲取呢? 可以利用time模塊,有一個(gè)time.time()功能。

import time
print(time.time())

此方法返回的是格林尼治時(shí)間,是此時(shí)此刻距離1970年1月1日0點(diǎn)0分0秒的時(shí)間秒數(shù)。也叫時(shí)間戳,他是一直變化的。所以要是計(jì)算shopping_car的執(zhí)行效率就是在執(zhí)行前后計(jì)算這個(gè)時(shí)間戳的時(shí)間,然后求差值即可。

import time
def index():
    print('歡迎訪問(wèn)博客園主頁(yè)')

start_time = time.time()
index()
end_time = time.time()
print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')

由于index函數(shù)只有一行代碼,執(zhí)行效率太快了,所以我們利用time模塊的一個(gè)sleep模擬一下

import time
def index():
    time.sleep(2)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print('歡迎訪問(wèn)博客園主頁(yè)')

start_time = time.time()
index()
end_time = time.time()
print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')

版本1分析:你現(xiàn)在已經(jīng)完成了這個(gè)需求,但是有什么問(wèn)題沒(méi)有? 雖然你只寫(xiě)了四行代碼,但是你完成的是一個(gè)測(cè)試其他函數(shù)的執(zhí)行效率的功能,如果讓你測(cè)試一下,小張,小李,小劉的函數(shù)效率呢? 你是不是全得復(fù)制:

import time
def index():
    time.sleep(2)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print('歡迎訪問(wèn)博客園首頁(yè)')

def home(name):
    time.sleep(3)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print(f'歡迎訪問(wèn){name}主頁(yè)')

start_time = time.time()
index()
end_time = time.time()
print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')

start_time = time.time()
home('太白')
end_time = time.time()
print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')

......

重復(fù)代碼太多了,所以要想解決重復(fù)代碼的問(wèn)題,怎么做?我們是不是學(xué)過(guò)函數(shù),函數(shù)就是以功能為導(dǎo)向,減少重復(fù)代碼,好我們繼續(xù)整改。

版本2:

import time

def index():
    time.sleep(2)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print('歡迎訪問(wèn)博客園主頁(yè)')

def inner():
    start_time = time.time()
    index()
    end_time = time.time()
    print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')

inner()

但是你這樣寫(xiě)也是有問(wèn)題的,你雖然將測(cè)試功能的代碼封裝成了一個(gè)函數(shù),但是這樣,你只能測(cè)試小ming同學(xué)的的函數(shù)index,你要是測(cè)試其他同事的函數(shù)呢?你怎么做?

import time
def index():
    time.sleep(2)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print('歡迎訪問(wèn)博客園主頁(yè)')

def home(name):
    time.sleep(3)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print(f'歡迎訪問(wèn){name}主頁(yè)')

def inner():
    start_time = time.time()
    index()
    home('太白')
    end_time = time.time()
    print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')

timer()

你要是像上面那么做,每次測(cè)試其他同事的代碼還需要手動(dòng)改,這樣是不是太low了?所以如何變成動(dòng)態(tài)測(cè)試其他函數(shù)?我們是不是學(xué)過(guò)函數(shù)的傳參?能否將被裝飾函數(shù)的函數(shù)名作為函數(shù)的參數(shù)傳遞進(jìn)去呢?

版本3:

import time
def index():
    time.sleep(2)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print('歡迎訪問(wèn)博客園主頁(yè)')

def home(name):
    time.sleep(3)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print(f'歡迎訪問(wèn){name}主頁(yè)')

def timmer(func):  # func == index 函數(shù)
    start_time = time.time()
    func()  # index()
    end_time = time.time()
    print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')

timmer(index)

這樣我將index函數(shù)的函數(shù)名作為參數(shù)傳遞給timmer函數(shù),然后在timmer函數(shù)里面執(zhí)行index函數(shù),這樣就變成動(dòng)態(tài)傳參了。好,你們現(xiàn)在將版本3的代碼快速練一遍。 大家練習(xí)完了之后,發(fā)現(xiàn)有什么問(wèn)題么? 對(duì)比著開(kāi)放封閉原則說(shuō): 首先,index函數(shù)除了完成了自己之前的功能,還增加了一個(gè)測(cè)試執(zhí)行效率的功能,對(duì)不?所以也符合開(kāi)放原則。 其次,index函數(shù)源碼改變了么?沒(méi)有,但是執(zhí)行方式改變了,所以不符合封閉原則。 原來(lái)如何執(zhí)行? index() 現(xiàn)在如何執(zhí)行? inner(index),這樣會(huì)造成什么問(wèn)題? 假如index在你的項(xiàng)目中被100處調(diào)用,那么這相應(yīng)的100處調(diào)用我都得改成inner(index)。 非常麻煩,也不符合開(kāi)放封閉原則。

版本4:實(shí)現(xiàn)真正的開(kāi)放封閉原則:裝飾器。

這個(gè)也很簡(jiǎn)單,就是我們昨天講過(guò)的閉包,只要你把那個(gè)閉包的執(zhí)行過(guò)程整清楚,那么這個(gè)你想不會(huì)都難。

import time          
def index():
    time.sleep(2)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print('歡迎訪問(wèn)博客園主頁(yè)')

def home(name):
    time.sleep(3)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print(f'歡迎訪問(wèn){name}主頁(yè)')

你將上面的inner函數(shù)在套一層最外面的函數(shù)timer,然后將里面的inner函數(shù)名作為最外面的函數(shù)的返回值,這樣簡(jiǎn)單的裝飾器就寫(xiě)好了,一點(diǎn)新知識(shí)都沒(méi)有加,這個(gè)如果不會(huì)就得多抄幾遍,然后理解代碼。

def timer(func):  # func = index
    def inner():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')
    return inner

# f = timer(index)

# f()

我們分析一下,代碼,代碼執(zhí)行到這一行:f = timer(index) 先執(zhí)行誰(shuí)?看見(jiàn)一個(gè)等號(hào)先要執(zhí)行等號(hào)右邊, timer(index) 執(zhí)行timer函數(shù)將index函數(shù)名傳給了func形參。內(nèi)層函數(shù)inner執(zhí)行么?不執(zhí)行,inner函數(shù)返回 給f變量。所以我們執(zhí)行f() 就相當(dāng)于執(zhí)行inner閉包函數(shù)。 f(),這樣既測(cè)試效率又執(zhí)行了原函數(shù),有沒(méi)有問(wèn)題?當(dāng)然有啦??!版本4你要解決原函數(shù)執(zhí)行方式不改變的問(wèn)題,怎么做? 所以你可以把 f 換成 index變量就完美了! index = timer(index) index()帶著同學(xué)們將這個(gè)流程在執(zhí)行一遍,特別要注意 函數(shù)外面的index實(shí)際是inner函數(shù)的內(nèi)存地址而不是index函數(shù)。讓學(xué)生們抄一遍,理解一下,這個(gè)timer就是最簡(jiǎn)單版本裝飾器,在不改變?cè)璱ndex函數(shù)的源碼以及調(diào)用方式前提下,為其增加了額外的功能,測(cè)試執(zhí)行效率。

3. 帶返回值的裝飾器

你現(xiàn)在這個(gè)代碼,完成了最初版的裝飾器,但是還是不夠完善,因?yàn)槟惚谎b飾的函數(shù)index可能會(huì)有返回值,如果有返回值,你的裝飾器也應(yīng)該不影響,開(kāi)放封閉原則嘛。但是你現(xiàn)在設(shè)置一下試試:

import time
def index():
    time.sleep(2)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print('歡迎訪問(wèn)博客園主頁(yè)')
    return '訪問(wèn)成功'

def timer(func):  # func = index
    def inner():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')
    return inner

index = timer(index)
print(index())  # None

加上裝飾器之后,他的返回值為None,為什么?因?yàn)槟悻F(xiàn)在的index不是函數(shù)名index,這index實(shí)際是inner函數(shù)名。所以index() 等同于inner() 你的 '訪問(wèn)成功'返回值應(yīng)該返回給誰(shuí)?應(yīng)該返回給index,這樣才做到開(kāi)放封閉,實(shí)際返回給了誰(shuí)?實(shí)際返回給了func,所以你要更改一下你的裝飾器代碼,讓其返回給外面的index函數(shù)名。 所以:你應(yīng)該這么做:

def timer(func):  # func = index
    def inner():
        start_time = time.time()
        ret = func()
        end_time = time.time()
        print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')
        return ret
    return inner

index = timer(index)  # inner
print(index())  # print(inner())

借助于內(nèi)層函數(shù)inner,你將func的返回值,返回給了inner函數(shù)的調(diào)用者也就是函數(shù)外面的index,這樣就實(shí)現(xiàn)了開(kāi)放封閉原則,index返回值,確實(shí)返回給了'index'。

讓同學(xué)們;練習(xí)一下。

4 被裝飾函數(shù)帶參數(shù)的裝飾器

到目前為止,你的被裝飾函數(shù)還是沒(méi)有傳參呢?按照我們的開(kāi)放封閉原則,加不加裝飾器都不能影響你被裝飾函數(shù)的使用。所以我們看一下。

import time
def index():
    time.sleep(2)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print('歡迎訪問(wèn)博客園主頁(yè)')
    return '訪問(wèn)成功'

def home(name):
    time.sleep(3)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print(f'歡迎訪問(wèn){name}主頁(yè)')

def timer(func):  # func = index
    def inner():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')
    return inner

# 要想timer裝飾home函數(shù)怎么做?
home = timer(home)
home('太白')

上面那么做,顯然報(bào)錯(cuò)了,為什么? 你的home這個(gè)變量是誰(shuí)?是inner,home('太白')實(shí)際是inner('太白')但是你的'太白'這個(gè)實(shí)參應(yīng)該傳給誰(shuí)? 應(yīng)該傳給home函數(shù),實(shí)際傳給了誰(shuí)?實(shí)際傳給了inner,所以我們要通過(guò)更改裝飾器的代碼,讓其將實(shí)參'太白'傳給home.

import time
def index():
    time.sleep(2)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print('歡迎訪問(wèn)博客園主頁(yè)')
    return '訪問(wèn)成功'

def home(name):
    time.sleep(3)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print(f'歡迎訪問(wèn){name}主頁(yè)')

def timer(func):  # func = home
    def inner(name):
        start_time = time.time()
        func(name)  # home(name) == home('太白')
        end_time = time.time()
        print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')
    return inner

# 要想timer裝飾home函數(shù)怎么做?
home = timer(home)
home('太白')

這樣你就實(shí)現(xiàn)了,還有一個(gè)小小的問(wèn)題,現(xiàn)在被裝飾函數(shù)的形參只是有一個(gè)形參,如果要是多個(gè)怎么辦?有人說(shuō)多少個(gè)我就寫(xiě)多少個(gè)不就行了,那不行呀,你這個(gè)裝飾器可以裝飾N多個(gè)不同的函數(shù),這些函數(shù)的參數(shù)是不統(tǒng)一的。所以你要有一種可以接受不定數(shù)參數(shù)的形參接受他們。這樣,你就要想到*args,**kwargs。

import time
def index():
    time.sleep(2)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print('歡迎訪問(wèn)博客園主頁(yè)')
    return '訪問(wèn)成功'

def home(name,age):
    time.sleep(3)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print(name,age)
    print(f'歡迎訪問(wèn){name}主頁(yè)')

def timer(func):  # func = home
    def inner(*args,**kwargs):  # 函數(shù)定義時(shí),*代表聚合:所以你的args = ('太白',18)
        start_time = time.time()
        func(*args,**kwargs)  # 函數(shù)的執(zhí)行時(shí),*代表打散:所以*args --> *('太白',18)--> func('太白',18)
        end_time = time.time()
        print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')
    return inner

home = timer(home)
home('太白',18)

這樣利用*的打散與聚合的原理,將這些實(shí)參通過(guò)inner函數(shù)的中間完美的傳遞到給了相應(yīng)的形參。

好將上面的代碼在敲一遍。

5. 標(biāo)準(zhǔn)版裝飾器

代碼優(yōu)化:語(yǔ)法糖

根據(jù)我的學(xué)習(xí),我們知道了,如果想要各給一個(gè)函數(shù)加一個(gè)裝飾器應(yīng)該是這樣:

def home(name,age):
    time.sleep(3)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print(name,age)
    print(f'歡迎訪問(wèn){name}主頁(yè)')

def timer(func):  # func = home
    def inner(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)
        end_time = time.time()
        print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')
    return inner

home = timer(home)
home('太白',18)

如果你想給home加上裝飾器,每次執(zhí)行home之前你要寫(xiě)上一句:home = timer(home)這樣你在執(zhí)行home函數(shù) home('太白',18) 才是真生的添加了額外的功能。但是每次寫(xiě)這一句也是很麻煩。所以,Python給我們提供了一個(gè)簡(jiǎn)化機(jī)制,用一個(gè)很簡(jiǎn)單的符號(hào)去代替這一句話。

def timer(func):  # func = home
    def inner(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)
        end_time = time.time()
        print(f'此函數(shù)的執(zhí)行效率為{end_time-start_time}')
    return inner

@timer  # home = timer(home)
def home(name,age):
    time.sleep(3)  # 模擬一下網(wǎng)絡(luò)延遲以及代碼的效率
    print(name,age)
    print(f'歡迎訪問(wèn){name}主頁(yè)')

home('太白',18)

你看此時(shí)我調(diào)整了一下位置,你要是不把裝飾器放在上面,timer是找不到的。home函數(shù)如果想要加上裝飾器那么你就在home函數(shù)上面加上@home,就等同于那句話 home = timer(home)。這么做沒(méi)有什么特殊意義,就是讓其更簡(jiǎn)單化,比如你在影視片中見(jiàn)過(guò)野戰(zhàn)軍的作戰(zhàn)時(shí)由于不方便說(shuō)話,用一些簡(jiǎn)單的手勢(shì)代表一些話語(yǔ),就是這個(gè)意思。

至此標(biāo)準(zhǔn)版的裝飾器就是這個(gè)樣子:

def wrapper(func):
    def inner(*args,**kwargs):
        '''執(zhí)行被裝飾函數(shù)之前的操作'''
        ret = func
        '''執(zhí)行被裝飾函數(shù)之后的操作'''
        return ret
    return inner

這個(gè)就是標(biāo)準(zhǔn)的裝飾器,完全符合代碼開(kāi)放封閉原則。這幾行代碼一定要背過(guò),會(huì)用。

此時(shí)我們要利用這個(gè)裝飾器完成一個(gè)需求:簡(jiǎn)單版模擬博客園登錄。 此時(shí)帶著學(xué)生們看一下博客園,說(shuō)一下需求: 博客園登陸之后有幾個(gè)頁(yè)面,diary,comment,home,如果我要訪問(wèn)這幾個(gè)頁(yè)面,必須驗(yàn)證我是否已登錄。 如果已經(jīng)成功登錄,那么這幾個(gè)頁(yè)面我都可以無(wú)阻力訪問(wèn)。如果沒(méi)有登錄,任何一個(gè)頁(yè)面都不可以訪問(wèn),我必須先登錄,登錄成功之后,才可以訪問(wèn)這個(gè)頁(yè)面。我們用成功執(zhí)行函數(shù)模擬作為成功訪問(wèn)這個(gè)頁(yè)面,現(xiàn)在寫(xiě)三個(gè)函數(shù),寫(xiě)一個(gè)裝飾器,實(shí)現(xiàn)上述功能。

def auth():

 pass

def diary():
 print('歡迎訪問(wèn)日記頁(yè)面')

def comment():

 print('歡迎訪問(wèn)評(píng)論頁(yè)面')

def home():

 print('歡迎訪問(wèn)博客園主頁(yè)')
答案:

login_status = {
    'username': None,
    'status': False,
}

def auth(func):
    def inner(*args,**kwargs):
        if login_status['status']:
            ret = func()
            return ret
        username = input('請(qǐng)輸入用戶名:').strip()
        password = input('請(qǐng)輸入密碼:').strip()
        if username == '太白' and password == '123':
            login_status['status'] = True
            ret = func()
            return ret
    return inner

@auth
def diary():
    print('歡迎訪問(wèn)日記頁(yè)面')

@auth
def comment():
    print('歡迎訪問(wèn)評(píng)論頁(yè)面')

@auth
def home():
    print('歡迎訪問(wèn)博客園主頁(yè)')

diary()
comment()
home()

6. 帶參數(shù)的裝飾器

我們看,裝飾器其實(shí)就是一個(gè)閉包函數(shù),再說(shuō)簡(jiǎn)單點(diǎn)就是兩層的函數(shù)。那么是函數(shù),就應(yīng)該具有函數(shù)傳參功能。

login_status = {
    'username': None,
    'status': False,
}

def auth(func):
    def inner(*args,**kwargs):
        if login_status['status']:
            ret = func()
            return ret
        username = input('請(qǐng)輸入用戶名:').strip()
        password = input('請(qǐng)輸入密碼:').strip()
        if username == '太白' and password == '123':
            login_status['status'] = True
            ret = func()
            return ret
    return inner

你看我上面的裝飾器,不要打開(kāi),他可以不可在套一層:

def auth(x):
    def auth3(func):
        def inner(*args,**kwargs):
            if login_status['status']:
                ret = func()
                return ret
            username = input('請(qǐng)輸入用戶名:').strip()
            password = input('請(qǐng)輸入密碼:').strip()
            if username == '太白' and password == '123':
                login_status['status'] = True
                ret = func()
                return ret
        return inner
    return auth

舉例說(shuō)明:抖音:綁定的是微信賬號(hào)密碼。 皮皮蝦:綁定的是qq的賬號(hào)密碼。 你現(xiàn)在要完成的就是你的裝飾器要分情況去判斷賬號(hào)和密碼,不同的函數(shù)用的賬號(hào)和密碼來(lái)源不同。 但是你之前寫(xiě)的裝飾器只能接受一個(gè)參數(shù)就是函數(shù)名,所以你寫(xiě)一個(gè)可以接受參數(shù)的裝飾器。

def auth3(func):
    def inner(*args, **kwargs):
        if login_status['status']:
            ret = func()
            return ret
        if 微信:
            username = input('請(qǐng)輸入用戶名:').strip()
            password = input('請(qǐng)輸入密碼:').strip()
            if username == '太白' and password == '123':
                login_status['status'] = True
                ret = func()
                return ret
        elif 'qq':
            username = input('請(qǐng)輸入用戶名:').strip()
            password = input('請(qǐng)輸入密碼:').strip()
            if username == '太白' and password == '123':
                login_status['status'] = True
                ret = func()
                return ret
    return inner

@auth3
def jitter():
    print('記錄美好生活')

@auth3
def pipefish():
    print('期待你的內(nèi)涵神評(píng)論')

解決方式:

def auth(x):
    def auth3(func):
        def inner(*args, **kwargs):
            if login_status['status']:
                ret = func()
                return ret

            if x == 'wechat':
                username = input('請(qǐng)輸入用戶名:').strip()
                password = input('請(qǐng)輸入密碼:').strip()
                if username == '太白' and password == '123':
                    login_status['status'] = True
                    ret = func()
                    return ret
            elif x == 'qq':
                username = input('請(qǐng)輸入用戶名:').strip()
                password = input('請(qǐng)輸入密碼:').strip()
                if username == '太白' and password == '123':
                    login_status['status'] = True
                    ret = func()
                    return ret
        return inner
    return auth3

@auth('wechat')  
def jitter():
    print('記錄美好生活')

@auth('qq')
def pipefish():
    print('期待你的內(nèi)涵神評(píng)論')

帶參數(shù)的裝飾器

log_dic = {"flag":False}
def auth(argv):
    def wraaper(func):
        def inner(*args,**kwargs):
            if log_dic["flag"]:
                func(*args,**kwargs)
            elif    argv == "QQ":
                    print("歡迎登陸")
                    user = input("username:")
                    pwd = input("password:")
                    if user == "rsx" and pwd == "rsx123":
                        log_dic["flag"] = True
                        func(*args,**kwargs)
                    else:
                        print("用戶名密碼錯(cuò)誤")
            elif    argv == "wechat":
                    print("歡迎登陸")
                    user = input("username:")
                    pwd = input("password:")
                    if user == "rsx" and pwd == "rsx123":
                        log_dic["username"] = user
                        func(*args, **kwargs)
                    else:
                        print("用戶名密碼錯(cuò)誤")
            else:
                print("請(qǐng)選擇APP登陸")
        return inner
    return wraaper

# msg="""
#     QQ
#     wechat
#     請(qǐng)選擇:
# """
# chose = input(msg).upper()

"""
@auth("QQ")
def foo():
    print("騰訊,用心創(chuàng)造快樂(lè)")
foo()
"""

# wraaper = auth("QQ")
# foo = wraaper(foo)
# foo()
"""
@auth("wechat")
def foo():
    print("微信")
foo()
"""

# wraaper = auth("wechat")
# foo = wraaper(foo)
# foo()

多個(gè)裝飾器裝飾一個(gè)函數(shù)

def wrapper1(func):
    def inner1(*args,**kwargs):
        print(1)
        func(*args,**kwargs)
        print(11)
    return inner1

def wrapper2(func):
    def inner2(*args,**kwargs):
        print(2)
        func(*args, **kwargs)
        print(22)
    return inner2

def wrapper3(func):
    def inner3(*args,**kwargs):
        print(3)
        func(*args, **kwargs)
        print(33)
    return inner3

@wrapper3
@wrapper2
@wrapper1
def fo():
    print(8)
foo()

感謝各位的閱讀,以上就是“Python中的迭代器、生成器和裝飾器的功能”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Python中的迭代器、生成器和裝飾器的功能這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI