python裝飾器

裝飾器簡述

要理解裝飾器需要知道Python高階函數python閉包,Python高階函數可以接受函數作為參數,也可以返回函數,閉包的內部函數可以訪問外部函數的局部變量。Python的裝飾器正式基于高階函數和閉包。

來看一個簡單的裝飾器:

def dec_func(fun):
    def wrapper():
        print('In Wapper!')
        fun()
        print('After fun')
    return wrapper
@dec_func
def foo():
    print('foo')
foo()
In Wapper!
foo
After fun

可以看到裝飾器不影響foo()函數的正常功能,它接受一個函數作為參數,然后對這個函數進行裝飾后返回給原來的函數名。

裝飾器的原理類似下面的函數:

def bar():
    print('Bar')
bar = dec_func(bar)
bar()
In Wapper!
Bar
After fun

裝飾器的運行首先是將函數帶入裝飾器,原本的函數名接受裝飾器“裝飾”后的返回函數,再調用這個返回函數。

裝飾器就是對函數進行裝飾,“裝飾”以為這并不會對函數的正常運行造成影響,僅僅是對函數的一些功能進行額外的補充。

裝飾器是一個函數,接受一個函數(或者類)作為參數,返回值也是也是一個函數(或者類)

無參裝飾器

無參裝飾器就是不接受參數的裝飾器,無參裝飾器嵌套了兩層函數,一個是外部函數接受參數,一個是內部的返回函數。

import time
def timeit(func):
    def wrapper():  # wrapper是返回函數,被裝飾的函數的函數名接受這個函數,相當于f = wrapper()
        start = time.clock()
        func()  # func就是被傳入裝飾器的函數,func()在wrapper()內,在調用wrapper時,會調用func()
        end = time.clock()
        print('Used {}'.format(end - start))
    return wrapper
@timeit
def f():
    print('In f()')
f()  # 此時f = wrapper
In f()
Used 0.00031399999999992545

1.首先,把foo函數當做參數傳入timeit
2.foo接受返回的函數wrapper,此時的foo指向wrapper,執行foo其實執行wrapper
3.調用foo其實調用了wrapper,在調用wrapper時又重新調用了原本的foo函數,在調用wrapper其實調用foo

多裝飾器

def deco1(func):
    def wrapper():
        print('In Deco1')
        func()
    return wrapper
def deco2(func):
    def wrapper():
        print('In Deco2')
        func()
    return wrapper
@deco2
@deco1
def foo():
    print('In foo()')
foo()
In Deco2
In Deco1
In foo()

等價與:

foo = deco2(deco1(foo))

在多裝飾器中,緊挨著被裝飾函數的裝飾器屬于最內層,最先調用,最外層的裝飾器最后調用。

有參裝飾器

def make_header(level):  #接受參數
    print('Create decorator')
    def decorator(func):  #接受函數
        print('Initialize...')
        def wrapper():
            print('Call')
            return '<h{0}>{1}</h{0}>'.format(level, func())
        return wrapper
    return decorator
@make_header(2)
def get_content():
    return 'hello world'
Create decorator
Initialize...
get_content()
Call





'<h2>hello world</h2>'

等價于:

get_content = make_header(2)
get_content = decorator(get_content)

def log(prefix):  # 接受裝飾器的參數
    def log_decorator(f):  # 內部定義的wapper負責輸出log的參數+被裝飾函數名稱, 接受被裝飾函數
        def wrapper(*args, **kw):   # 接受被裝飾函數的參數
                # 將log函數的參數引用
            print('[%s] in decorate wrapper s%s()...' % (prefix, f.__name__))
            f(*args, **kw)
        return wrapper  # 返回內部函數給裝飾器
    return log_decorator  # 返回裝飾器給log函數
@log('DEBUG')
def test():
    print('out decorate run test()')


print(test())
[DEBUG] in decorate wrapper stest()...
out decorate run test()
None

有參裝飾器嵌套了三層函數,最外層的函數接受裝飾器的參數,中間的函數接受被裝飾函數,最內層的函數接受被裝飾函數的參數。

import time
from functools import reduce

def performance(unit):
    def perf_decorator(f):
        def wrapper(*args, **kargs):
            start_time = time.time()
            run_func = f(*args, **kargs)
            end_time = time.time()
            run_time = (end_time - start_time) * 1000 if unit == 'ms' else (end_time - start_time)
            print('call %s() in %f %s' % (f.__name__, run_time, unit))
            return run_func
        return wrapper
    return perf_decorator
@performance('ms')
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))
print(factorial(10))
call factorial() in 0.010252 ms
3628800

裝飾器的影響

def log(f):
    def wrapper(*args, **kw):
        print('call...')
        return f(*args, **kw)
    return wrapper
@log
def f2(x):
    pass
print(f2.__name__)
wrapper

由于decorator返回的新函數函數名已經不是'f2',而是@log內部定義的'wrapper'。 這對于那些依賴函數名的代碼就會失效。decorator還改變了函數的__doc__等其它屬性。

改善:

import functools
def log(f):
    @functools.wraps(f)
    def wrapper(*args, **kw):
        print('call...')
        return f(*args, **kw)
    return wrapper

最后需要指出,由于我們把原函數簽名改成了(*args, **kw),因此,無法獲得原函數的原始參數信息。 即便我們采用固定參數來裝飾只有一個參數的函數

def log(f):
    @functools.wraps(f)
    def wrapper(x):
        print('call...')
        return f(x)
    return wrapper

也可能改變原函數的參數名,因為新函數的參數名始終是 'x',原函數定義的參數名不一定叫 'x'

import time
import functools

def performance(unit):
    def perf_decorator(f):
        @functools.wraps(f)
        def wrapper(*args, **kw):
            t1 = time.time()
            r = f(*args, **kw)
            t2 = time.time()
            t = (t2 - t1) * 1000 if unit == 'ms' else (t2 - t1)
            print('call %s() in %f %s' % (f.__name__, t, unit))
            return r
        return wrapper
    return perf_decorator
@performance('ms')
def factorial(n):
    return reduce(lambda x, y: x * y, range(1, n + 1))
print(factorial(10))
call factorial() in 0.008821 ms
3628800
print(factorial.__name__)
factorial

基于類的裝飾器

可以定義基于類的裝飾器

class Bold(object):
    def __init__(self, func):
        self.func = func
 
    def __call__(self, *args, **kwargs):
        return '<b>' + self.func(*args, **kwargs) + '</b>'
@Bold
def hello(name):
    return 'hello %s' % name
hello('world')
'<b>hello world</b>'
  • __init__():它接收一個函數作為參數,也就是被裝飾的函數
  • __call__():讓類對象可調用,就像函數調用一樣,在調用被裝飾函數時被調用

閱讀

Python Decorator 基礎
Python: 會打扮的裝飾器

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容