python的函數修飾符(裝飾器)


首先明確函數也是對象(python萬物皆對象),而且函數對象可以被賦值給變量,所以,通過變量也能調用該函數。

例如

>>> def now():
...     print ("2013-12-25")
...
>>> f = now   #函數名可以賦給一個變量,根據變量+()就可以調用這個函數 >>> f()
2013-12-25

函數對象有一個__name__屬性,可以獲取函數的名字:

>>> now.__name__
'now'
>>> f.__name__
'now'

現在,假設我們要擴展now()函數的功能,比如,在函數調用前后自動打印日志,但又不希望修改now()函數的定義,這種在代碼運行期間動態增加功能的方式,稱之為“裝飾器”(Decorator)。本質上,decorator就是一個返回函數的高階函數

 

創建函數修飾符的規則:

(1)修飾符是一個函數
(2)修飾符取被修飾函數為參數
(3)修飾符返回一個新函數
(4)修飾符維護被維護函數的簽名

def log(func):
    def wrapper(*args, **kw):
        print ( "call %s()" % func.__name__)
        return func(*args, **kw) return wrapper

@log #相當於執行了語句 now = log(now)@修飾符的功能就是把now當做參數傳遞給log,然后把log的返回值(wrapper)賦給now變量def now():
    print  ( "2013-12-25 ")

>>> now()
call now()
2013-12-25

 

這里的log()是一個decorator,是返回一個函數的高階函數;原來的now()函數仍然存在,只是現在同名的now變量指向了新的函數,於是調用now()將執行新函數,即wrapper()函數。

wrapper()函數的參數定義是(*args, **kw),因此,wrapper()函數可以接受任意參數的調用(如果被修飾函數now不接受參數,wrapper可以無參)wrapper()函數內,首先打印日志,再緊接着調用原始函數。

此時經過decorator裝飾之后的函數,它們的__name__已經從原來的'now'變成了'wrapper'

 

>>> now.__name__
'wrapper'

 

 

如果decorator本身需要傳入參數,那就需要編寫一個返回decorator的高階函數,多一層包裝。比如,要自定義log的文本:

 2         def decorator(func):
 3                 def wrapper(*args , **kw):
 4                         print("%s %s()" % (text , func.__name__))
 5                         return func(*args , **kw)
 6                 return wrapper
 7         return decorator
 8 
 9 @log('execute') #等價於now = log('execute')(now)  log()返回一個函數名
10 def now():
11     print("2013-12-25 ")
12 
13 
14 >>> now()
15 execute now()
16 2013-12-25 

首先執行log('execute'),返回的是decorator函數,再調用返回的函數,參數是now函數,返回值最終是wrapper函數

以上兩種decorator的定義都沒有問題,但還差最后一步。因為函數也是對象,它有__name__等屬性,但你去看經過decorator裝飾之后的函數,它們的__name__已經從原來的'now'變成了'wrapper'

>>> now.__name__
'wrapper'

所以,需要把原始函數的__name__等屬性復制到wrapper()函數中,否則,有些依賴函數簽名的代碼執行就會出錯。

 

不需要編寫wrapper.__name__ = func.__name__這樣的代碼,Python內置的functools.wraps是干這個事的,所以,一個完整的decorator的寫法如下:

import functools  #導入functools模塊def log(func):
    @functools.wraps(func)  #py內置的functools.wraps 的修改函數屬性的方法wrapsdef wrapper(*args, **kw):
        print ('call %s()' % func.__name__)
        return func(*args, **kw)
    return wrapper

或者帶參數的decorator

import functools

def log(text):
    def decorator(func):
       @functools.wraps(func) def wrapper(*args, **kw):
            print '%s %s():' % (text, func.__name__)
            return func(*args, **kw)
        return wrapper
    return decorator

》》只需記住在定義wrapper()的前面加上@functools.wraps(func)即可。

 

functools模塊提供的修改函數屬性的方法wraps 進一步理解

def log(func):
    def wrapper():
        print('log開始 ...')
        func()
        print('log結束 ...')
    return wrapper
    
@log
def test1():
    print('test1 ..')

def test2():
    print('test2 ..')

print(test1.__name__)
print(test2.__name__)

運行結果:

wrapper    #test1.__name__改變了
test2

test1的函數名稱變了,如果某些代碼用到就會出問題,可以使用functools模塊提供的修改函數屬性的方法wraps,例如

from functools import wraps  #可以寫成import functools 后面改@functools.wraps(func) 發揮命名空間的意義

def log(func):
 @wraps(func) def wrapper():
        print('log開始 ...')
        func()
        print('log結束 ...')
    return wrapper
    
@log
def test1():
    print('test1 ..')

def test2():
    print('test2 ..')

print(test1.__name__)
print(test2.__name__)

運行結果:

test1
test2

 

 

更多請看  https://www.cnblogs.com/gdjlc/p/11182441.html  條理更加清晰!!!

 

》》總結:

--Pythondecorator可以用函數實現,也可以用類實現,decorator可以增強函數的功能,定義起來雖然有點復雜,但使用起來非常靈活和方便。

--函數修飾符@的作用是為現有函數增加額外的功能,常用於插入日志、性能測試、事務處理等等

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM