溫馨提示×

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

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

Python實(shí)戰(zhàn)教程:修飾器精講?。?019下半年篇)

發(fā)布時(shí)間:2020-08-10 13:43:18 來源:ITPUB博客 閱讀:179 作者:千鋒Python唐小強(qiáng) 欄目:編程語言

是不是很多伙伴都認(rèn)為Python的語法簡單,作為入門語言學(xué)起來非常簡單?

很多伙伴說Python寫出來的代碼只要符合邏輯,不需要太多的學(xué)習(xí)即可,即可從一門其他語言跳來用Python寫(當(dāng)然這樣是好事,誰都希望入門簡單)。

于是我便記錄一下,如果要學(xué)Python的話,到底有什么好學(xué)的。記錄一下Python有什么值得學(xué)的,對(duì)比其他語言有什么特別的地方,有什么樣的代碼寫出來更Pythonic。一路回味,一路學(xué)習(xí)。

什么是修飾器,為什么叫修飾器

修飾器英文是Decorator,

我們假設(shè)這樣一種場景:古老的代碼中有幾個(gè)很是復(fù)雜的函數(shù)F1、F2、F3…,復(fù)雜到看都不想看,反正我們就是不想改這些函數(shù),但是我們需要改造加功能,在這個(gè)函數(shù)的前后加功能,這個(gè)時(shí)候我們很容易就實(shí)現(xiàn)這個(gè)需求:

def hi():
 """hi func,假裝是很復(fù)雜的函數(shù)"""
 return 'hi'
def aop(func):
 """aop func"""
 print('before func')
 print(func())
 print('after func')
 
if __name__ == '__main__':
 aop(hi)
Python實(shí)戰(zhàn)教程:修飾器精講?。?019下半年篇)

以上是很是簡單的實(shí)現(xiàn),利用Python參數(shù)可以傳函數(shù)引用的特性,就可以實(shí)現(xiàn)了這種類似AOP的效果。

這段代碼目前沒有什么問題,接下來煎魚加需求:需求為幾十個(gè)函數(shù)都加上這樣的前后的功能,而所有調(diào)用這些函數(shù)地方也要相應(yīng)地升級(jí)。

看起來這個(gè)需求比較扯,偏偏這個(gè)需求卻是較為廣泛:在調(diào)用函數(shù)的前后加上log輸出、在調(diào)用函數(shù)的前后計(jì)算調(diào)用時(shí)間、在調(diào)用函數(shù)的前后占用和釋放資源等等。

一種比較笨的方法就是,為這幾十個(gè)函數(shù)逐一添加一個(gè)入口函數(shù),針對(duì)a函數(shù)添加一個(gè)a_aop函數(shù),針對(duì)b函數(shù)添加一個(gè)b_aop函數(shù)…如此這樣。 問題也很明顯:

  1. 工作量大
  2. 代碼變得臃腫復(fù)雜
  3. 原代碼有多處調(diào)用了這些函數(shù),可以會(huì)升級(jí)不完全

于是接下來有請(qǐng)修飾器出場,修飾器可以統(tǒng)一地給這些函數(shù)加這樣的功能:

def aop(func):
 """aop func"""
 def wrapper():
 """wrapper func"""
 print('before func')
 func()
 print('after func')
 return wrapper
@aop
def hi():
 """hi func"""
 print('hi')
 
@aop
def hello():
 """hello func"""
 print('hello')
if __name__ == '__main__':
 hi()
 hello()
Python實(shí)戰(zhàn)教程:修飾器精講!(2019下半年篇)

以上aop函數(shù)就是修飾器的函數(shù),使用該修飾器時(shí)只要在待加函數(shù)上一行加@修飾器函數(shù)名即可,如實(shí)例代碼中就是@aop。

加上了@aop后,調(diào)用新功能的hi函數(shù)就喝原來的調(diào)用一樣:就是hi()而不是aop(hi),也意味著所有調(diào)用這些函數(shù)的地方不需要修改就可以升級(jí)。

簡單地來說,大概修飾器就是以上的這樣子。

@是個(gè)什么

對(duì)于新手來說,上面例子中, @就是一樣奇怪的東西:為什么這樣子用就可以實(shí)現(xiàn)需求的功能了。

其實(shí)我們還可以不用@,這里換一種寫法:

def hi():
 """hi func"""
 print('hi')
def aop(func):
 """aop func"""
 def wrapper():
 """wrapper func"""
 print('before func')
 func()
 print('after func')
 return wrapper
if __name__ == '__main__':
 hi()
 print('')
 hi = aop(hi)
 hi()
Python實(shí)戰(zhàn)教程:修飾器精講?。?019下半年篇)

上面的例子中的aop函數(shù)就是之前說過的修飾器函數(shù)。

如例子main函數(shù)中第一次調(diào)用hi函數(shù)時(shí),由于hi函數(shù)沒叫修飾器,因此我們可以從輸出結(jié)果中看到程序只輸出了一個(gè)hi而沒有前后功能。

然后加了一個(gè)hi = aop(hi)后再調(diào)用hi函數(shù),得到的輸出結(jié)果和加修飾器的一樣,換言之:

@aop 等效于hi = aop(hi)

因此,我們對(duì)于@,可以理解是,它通過閉包的方式把新函數(shù)的引用賦值給了原來函數(shù)的引用。

有點(diǎn)拗口。aop(hi)是新函數(shù)的引用,至于返回了引用的原因是aop函數(shù)中運(yùn)用閉包返回了函數(shù)引用。而hi這個(gè)函數(shù)的引用,本來是指向舊函數(shù)的,通過hi = aop(hi)賦值后,就指向新函數(shù)了。

被調(diào)函數(shù)加參數(shù)

以上的例子中,我們都假設(shè)被調(diào)函數(shù)是無參的,如hi、hello函數(shù)都是無參的,我們?cè)倏匆谎奂弭~剛才的寫的修飾器函數(shù):

def aop(func):
 """aop func"""
 def wrapper():
 """wrapper func"""
 print('before func')
 func()
 print('after func')
 return wrapper

很明顯,閉包函數(shù)wrapper中,調(diào)用被調(diào)函數(shù)用的是func(),是無參的。同時(shí)就意味著,如果func是一個(gè)帶參數(shù)的函數(shù),再用這個(gè)修飾器就會(huì)報(bào)錯(cuò)。

@aop
def hi_with_deco(a):
 """hi func"""
 print('hi' + str(a))
if __name__ == '__main__':
 # hi()
 hi_with_deco(1)
Python實(shí)戰(zhàn)教程:修飾器精講?。?019下半年篇)

就是參數(shù)的問題。這個(gè)時(shí)候,我們把修飾器函數(shù)改得通用一點(diǎn)即可,其中import了一個(gè)函數(shù)(也是修飾器函數(shù)):

from functools import wraps
def aop(func):
 """aop func"""
 @wraps(func)
 def wrap(*args, **kwargs):
 print('before')
 func(*args, **kwargs)
 print('after')
 return wrap
@aop
def hi(a, b, c):
 """hi func"""
 print('test hi: %s, %s, %s' % (a, b, c))
@aop
def hello(a, b):
 """hello func"""
 print('test hello: %s, %s' % (a, b))
if __name__ == '__main__':
 hi(1, 2, 3)
 hello('a', 'b')
Python實(shí)戰(zhàn)教程:修飾器精講!(2019下半年篇)

這是一種很奇妙的東西,就是在寫修飾器函數(shù)的時(shí)候,還用了別的修飾器函數(shù)。那也沒什么,畢竟修飾器函數(shù)也是函數(shù)啊,有什么所謂。

帶參數(shù)的修飾器

思路到了這里,煎魚不禁思考一個(gè)問題:修飾器函數(shù)也是函數(shù),那函數(shù)也是應(yīng)該能傳參的。函數(shù)傳參的話,不同的參數(shù)可以輸出不同的結(jié)果,那么,修飾器函數(shù)傳參的話,不同的參數(shù)會(huì)怎么樣呢?

其實(shí)很簡單,修飾器函數(shù)不同的參數(shù),能生成不同的修飾器啊。

如,我這次用這個(gè)修飾器是把時(shí)間日志打到test.log,而下次用修飾器的時(shí)候煎魚希望是能打到test2.log。這樣的需求,除了寫兩個(gè)修飾器函數(shù)外,還可以給修飾器加參數(shù)選項(xiàng):

from functools import wraps
def aop_with_param(aop_test_str):
 def aop(func):
 """aop func"""
 @wraps(func)
 def wrap(*args, **kwargs):
 print('before ' + str(aop_test_str))
 func(*args, **kwargs)
 print('after ' + str(aop_test_str))
 return wrap
 return aop
@aop_with_param('abc')
def hi(a, b, c):
 """hi func"""
 print('test hi: %s, %s, %s' % (a, b, c))
@aop_with_param('pppppp')
def hi2(a, b, c):
 """hi func"""
 print('test hi: %s, %s, %s' % (a, b, c))
if __name__ == '__main__':
 hi(1, 2, 3)
 print('')
 hi2(2, 3, 4)
Python實(shí)戰(zhàn)教程:修飾器精講?。?019下半年篇)

同樣的,可以加一個(gè)參數(shù),也可以加多個(gè)參數(shù),這里就不說了。

修飾器類

大道同歸,邏輯復(fù)雜了之后,人們都喜歡將函數(shù)的思維層面抽象上升到對(duì)象的層面。原因往往是對(duì)象能擁有多個(gè)函數(shù),對(duì)象往往能管理更復(fù)雜的業(yè)務(wù)邏輯。

顯然,修飾器函數(shù)也有對(duì)應(yīng)的修飾器類。寫起來也沒什么難度,和之前的生成器一樣簡單:

from functools import wraps
class aop(object):
 def __init__(self, aop_test_str):
 self.aop_test_str = aop_test_str
 def __call__(self, func):
 @wraps(func)
 def wrapper(*args, **kwargs):
 print('before ' + self.aop_test_str)
 func()
 print('after ' + self.aop_test_str)
 return wrapper
 
@aop('pppppp')
def hi():
 print('hi')

看得出來,這個(gè)修飾器類也不過是多了個(gè)__call__函數(shù),而這個(gè)__call__函數(shù)的內(nèi)容和之前寫的修飾器函數(shù)一個(gè)樣!而使用這個(gè)修飾器的方法,和之前也一樣,一樣的如例子中的@aop('pppppp')。

過于無聊,還試了一下繼承的修飾器類:

class sub_aop(aop):
 def __init__(self, sub_aop_str, *args, **kwargs):
 self.sub_aop_str = sub_aop_str
 super(sub_aop, self).__init__(*args, **kwargs)
 def __call__(self, func):
 @wraps(func)
 def wrapper(*args, **kwargs):
 print('before ' + self.sub_aop_str)
 super(sub_aop, self).__call__(func)()
 print('after ' + self.sub_aop_str)
 return wrapper
 
@sub_aop('ssssss', 'pppppp')
def hello():
 print('hello')
 
if __name__ == '__main__':
 hello()

大家可以大膽猜測一下結(jié)果會(huì)怎樣。。。

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

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

AI