Python 裝飾器使用指南

裝飾器是可調用的對象,其參數是另外一個函數(被裝飾的函數)。html

裝飾器基礎知識

首先看一下這段代碼python

def deco(fn):
    print "I am %s!" % fn.__name__

@deco
def func():
    pass

# output
I am func!

# 沒有執行func 函數 可是 deco 被執行了複製代碼

HUGOMORE42git

在用某個@decorator來修飾某個函數func時github

@decorator
def func():
    pass複製代碼

其解釋器會解釋成下面這樣的語句:shell

func = decorator(func)編程

其實就是把一個函數當參數傳到另外一個函數中,而後再回調,可是值得注意的是裝飾器必須返回一個函數給funcflask

裝飾器的一大特性是,能把被裝飾的函數替換成其餘函數。第二大特性是,裝飾器在加載模塊時當即執行。設計模式

裝飾器什麼時候執行

裝飾器的一個關鍵特性是,它們在被裝飾的函數定義後當即運行。這一般在導入是(python 加載模塊時)。數組

看下下面的示例:緩存

registry = []  # registry 保存被@register 裝飾的函數的引用

def register(func):  # register 的參數是一個函數
    print('running register(%s)' % func)  # 打印被裝飾的函數
    registry.append(func)  # 把 func 存入 `registery`
    return func  # 返回 func:必須返回函數,這裏返回的函數與經過參數傳入的同樣

@register # `f1` 和 `f2`被 `@register` 裝飾
def f1():
    print('running f1()')

@register
def f2():
    print('running f2()')

def f3():  # <7>
    print('running f3()')

def main():  # main 打印 `registry`,而後調用 f1()、f2()和 f3()
    print('running main()')
    print('registry ->', registry)
    f1()
    f2()
    f3()

if __name__=='__main__':
    main()  # <9>複製代碼

運行代碼結果以下:

running register(<function f1 at 0x1023fb378>)
running register(<function f2 at 0x1023fb400>)
running main()
registry -> [<function f1 at 0x1023fb378>, <function f2 at 0x1023fb400>]
running f1()
running f2()
running f3()複製代碼

從結果能夠發現register 在模塊中其餘函數以前運行了兩次。調用 register 時,傳給它的參數是被裝飾的函數(例如 )。

看完上邊的示例咱們知道,函數被裝飾器裝飾後會變成裝飾器函數的一個參數,那這時就不得不說變量的做用域了。

變量做用域

先看下下邊這段代碼:

def f1(a):
    print(locals())
    print(a)
    print(b)


f1(3)
# output
{'a': 3}
3
Traceback(most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 3, in f1
NameError: global name 'b' is not defined複製代碼

這裏的錯誤是由於全局變量 b 沒有定義,若是咱們先在函數外部給 b 賦值,再調用這個方法就不會報錯了。

函數運行時會建立一個新的做用域(命名空間)。函數的命名空間隨着函數調用開始而開始,結束而銷燬。
這個例子中 f1 的命名空間中只有 {'a': 3},因此 b 會被認爲是全局變量。

再看一個例子:

b = 6
def f2(a):
    print(a)
    print(globals())
    print(locals())
    print(b)
    b = 9

f2(3)
# output
3
{
    '__name__': '__main__',
    '__doc__': None, 
    '__package__': None, 
    '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x10c7f2dd8>, 
    '__spec__': None, 
    '__annotations__': {}, 
    '__builtins__': <module 'builtins' (built-in)>, 
    '__file__': '~/var_local.py', 
    '__cached__': None, 
    'b': 6, 
    'f2': <function f2 at 0x10c7e7598>
}
{'a': 3}
3
Traceback(most recent call last):
    File "<stdin>", line 1, in <module>
    File "<stdin>", line 3, in f1
UnboundLocalError: local variable 'b' referenced before assignment複製代碼

這個例子和上一個例子不一樣是,我如今函數外部定義了全局變量b,可是執行f2 這個方法並無打印6,這是爲何呢?
這是由於執行函數時 Python 會嘗試從局部變量中獲取 b,函數對於已經引用但未賦值的變量並不會自動聲明爲局部變量,因此解釋器發現後邊的賦值以前有引用就會拋出 UnboundLocalError 錯誤。

Python 不要求聲明變量,可是假定在函數定義體中賦值的變量是局部變量。

若是要讓解釋器把b當作全局變量,要使用global聲明:

b = 6
def f3(a):
    global b
    print(a)
    print(b)
    b = 9

f2(3)
# output
3
6複製代碼

閉包

閉包是一種函數,它會保留定義函數時存在的自由變量的綁定,這樣調用函數時,雖然定義做用域不可用,但仍能使用那些綁定。

介紹閉包前先要說明一下 Python 的函數參數

函數的兩種參數

函數有兩種參數

  1. 位置參數
  2. 命名參數
def foo(x, y=0):
    return x - y複製代碼

python 中一切都是對象

函數和python中其餘同樣都是對象

In [7]: class A(object):
   ...:     pass


In [8]: A
Out[8]: __main__.A
In [9]: type(A)
Out[9]: type
In [10]: def foo():
   ....:     pass


In [11]: type(foo)
Out[11]: function

In [12]: A.__class__
Out[12]: type

In [13]: foo.__class__
Out[13]: function

In [14]: a = 1
In [15]: a.__class__
Out[15]: int

# 類 是對象
In [16]: issubclass(A.__class__, object)
Out[16]: True

# 變量 是對象
In [17]: issubclass(a.__class__, object)
Out[17]: True

# 函數 是對象
In [18]: issubclass(foo.__class__, object)
Out[18]: True複製代碼

因此函數也能夠做爲參數傳遞給其它函數,也能夠被當作返回值返回

def add(x, y):
    return x + y

def apply(func):
    return func

>> a = apply(add)
>> type(a)
<type 'function'>

>> a(1, 2)
>> 3複製代碼

閉包的使用

先來看一個示例:假設有個名爲 avg 的函數,它的做用是計算不斷增長的系列值的均值;
它是這麼使用的:

>>> avg(10)
10
>>> avg(11)
10.5
>>> avg(12)
11複製代碼

那麼咱們考慮下,avg 從何而來,它又在哪裏保存歷史值呢,這個用閉包如何實現呢?
下邊的代碼是閉包的實現:

def make_averager():
    series = []

    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)

    return averager複製代碼

調用 make_averager 時,返回一個 averager 函數對象。每次調用 averager 時,它都會把參數添加到系列值中,而後計算當前平均值。

avg = make_averager()

>>> avg(10)
10
>>> avg(11)
10.5
>>> avg(12)
11複製代碼

seriesmake_averager 函數的局部變量,由於那個函數的定義體中初始化了series: series=[]。但在averager 函數中,series 是自由變量(指未在本地做用域中綁定的變量)。

變量示意圖
變量示意圖

averager 的閉包延伸到那個函數的做用域以外,包含自由變量series的綁定。

  • avg 就是一個閉包
  • 也能夠說 make_averager 指向一個閉包
  • 或者說 make_averager 是閉包的工廠函數

閉包能夠認爲是一個內層函數(averager),由一個變量指代,而這個變量相對於外層包含它的函數而言,是本地變量
嵌套定義在非全局做用域裏面的函數可以記住它在被定義的時候它所處的封閉命名空間

閉包 只是在形式和表現上像函數,但實際上不是函數。函數是一些可執行的代碼,這些代碼在函數被定義後就肯定了,不會在執行時發生變化,因此一個函數只有一個實例。閉包在運行時能夠有多個實例,不一樣的引用環境和相同的函數組合能夠產生不一樣的實例。

裝飾器

實現一個簡單的裝飾器

對一個已有的模塊作一些「修飾工做」,所謂修飾工做就是想給現有的模塊加上一些小裝飾(一些小功能,這些小功能可能好多模塊都會用到),但又不讓這個小裝飾(小功能)侵入到原有的模塊中的代碼裏去

def my_decorator(func):
    def wrapper():
        print "Before the function runs"
        func() # 這行代碼可用,是由於 wrapper 的閉包中包含自由變量 func
        print "After the function runs"
    return wrapper

def my_func():
    print "I am a stand alone function"


>> my_func()

# output
I am a stand alone function

# 而後,咱們在這裏裝飾這個函數
# 將函數傳遞給裝飾器,裝飾器將動態地將其包裝在任何想執行的代碼中,而後返回一個新的函數

>> my_func = my_decorator(my_func)
>> my_func()
#output
Before the function runs
I am a stand alone function
After the function runs

# 也能夠這麼寫

@ my_decorator
def my_func():
    print "I am a stand alone function"

>> my_func()
#output
Before the function runs
I am a stand alone function
After the function runs複製代碼

裝飾器是設計模式中裝飾器模式英文版)的python實現。

多個裝飾器

裝飾器能夠嵌套使用

def bread(func):
    def wrapper():
        print "</''''''\>"
        func()
        print "<\______/>"
    return wrapper

def ingredients(func):
    def wrapper():
        print "#tomatoes#"
        func()
        print "~salad~"
    return wrapper

def sandwich(food="--ham--"):
    print food

#### outputs:複製代碼

嵌套兩個裝飾器

>> sandwich = bread(ingredients(sandwich))
>> sandwich()

#### outputs
</''''''\>
 #tomatoes#
 --ham--
 ~salad~
<\______/>複製代碼

更簡單的寫法

@bread
@ingredients
def sandwich(food="--ham--"):
    print food複製代碼

裝飾器的順序是很重要的

若是咱們換下順序就會發現,三明治變成了披薩。。

@ingredients
@bread
def sandwich(food="--ham--"):
    print food

# outputs:

 #tomatoes#
 </' ' ' ' ' '\>
   --ham--
 <\______/>
 ~salad~複製代碼

Decorator 的工做原理

首先看一下這段代碼

def deco(fn):
    print "I am %s!" % fn.__name__

@deco
def func():
    pass

# output
I am func!

# 沒有執行func 函數 可是 deco 被執行了複製代碼

在用某個@decorator來修飾某個函數func時

@decorator
def func():
    pass複製代碼

其解釋器會解釋成下面這樣的語句:

func = decorator(func)

其實就是把一個函數當參數傳到另外一個函數中,而後再回調
可是值得注意的是裝飾器必須返回一個函數給func

回到剛纔的例子

def my_decorator(func):
    def wrapper():
        print "Before the function runs"
        func()
        print "After the function runs"
    return wrapper

def my_func():
    print "I am a stand alone function"

>> my_func = my_decorator(my_func)
>> my_func()
#output
Before the function runs
I am a stand alone function
After the function runs複製代碼

my_decorator(my_func)返回了wrapper()函數,因此,my_func其實變成了wrapper的一個變量,然後面的my_func()執行其實變成了wrapper()

好比:多個decorator

@decorator_one
@decorator_two
def func():
    pass複製代碼

至關於:

func = decorator_one(decorator_two(func))複製代碼

好比:帶參數的decorator:

@decorator(arg1, arg2)
def func():
    pass
# 至關於:

func = decorator(arg1,arg2)(func)複製代碼

帶參數的裝飾器

首先看一下, 若是被裝飾的方法有參數

def a_decorator(method_to_decorate):
    def wrapper(self, x):
        x -= 3
        print 'x is %s' % x
        method_to_decorate(self, x)
    return wrapper


class A(object):

    def __init__(self):
        self.b = 42

 @a_decorator
    def number(self, x):
        print "b is %s" % (self.b + x)


a = A()
a.number(-3)

# output
x is -6
b is 36複製代碼

一般咱們都使用更加通用的裝飾器,能夠做用在任何函數或對象方法上,而沒必要關心其參數使用

def a_decorator(method_to_decorate):
    def wrapper(*args, **kwargs):
        print '****** args ******'
        print args
        print kwargs
        method_to_decorate(*args, **kwargs)
    return wrapper

@a_decorator
def func():
    pass

func()
#output
****** args ******
()
{}

@a_decorator
def func_with_args(a, b=0):
    pass
    return a + b

func_with_args(1, b=2)

#output
****** args ******
(1,)
{'b': 2}複製代碼

上邊的示例是帶參數的被裝飾函數

如今咱們看一下向裝飾器自己傳遞參數

向裝飾器自己傳遞參數

裝飾器必須使用函數做爲參數,你不能直接傳遞參數給裝飾器自己
若是想傳遞參數給裝飾器,能夠 聲明一個用於建立裝飾器的函數

# 我是一個建立裝飾器的函數
def decorator_maker():
    print "I make decorators!"

    def my_decorator(func):
        print "I am a decorator!"

        def wrapped():
            print "I am the wrapper around the decorated function. "
            return func()

        print "As the decorator, I return the wrapped function."
        return wrapped

    print "As a decorator maker, I return a decorator"
    return my_decorator

# decorator_maker()返回的是一個裝飾器
new_deco = decorator_maker()

#outputs
I make decorators!
As a decorator maker, I return a decorator

# 使用裝飾器
def decorated_function():
    print "I am the decorated function"

decorated_function = new_deco(decorated_function)   
decorated_function()

# outputs
I make decorators!
As a decorator maker, I return a decorator
I am a decorator!
As the decorator, I return the wrapped function.
I am the wrapper around the decorated function.
I am the decorated  function複製代碼

使用@修飾

decorated_function = new_deco(decorated_function)

# 等價於下面的方法

@new_deco
def func():
    print "I am the decorated function"


@decorator_maker()
def func():
    print "I am the decorated function"複製代碼

my_decorator(裝飾器函數)是decorator_maker(裝飾器生成函數)的內部函數
因此可使用把參數加在decorator_maker(裝飾器生成函數)的方法像裝飾器傳遞參數

# 我是一個建立帶參數裝飾器的函數
def decorator_maker_with_arguments(darg1, darg2):
    print "I make decorators! And I accept arguments:", darg1, darg2

    def my_decorator(func):
        print "I am a decorator! Somehow you passed me arguments:", darg1, darg2

        def wrapped(farg1, farg2):
            print "I am the wrapper around the decorated function."
            print "I can access all the variables", darg1, darg2, farg1, farg2
            return func(farg1, farg2)

        print "As the decorator, I return the wrapped function."
        return wrapped

    print "As a decorator maker, I return a decorator"
    return my_decorator

@decorator_maker_with_arguments("deco_arg1", "deco_arg2")
def decorated_function_with_arguments(function_arg1, function_arg2):
    print ("I am the decorated function and only knows about my arguments: {0}"
           " {1}".format(function_arg1, function_arg2))


decorated_function_with_arguments('farg1', 'farg2')

# outputs

I make decorators! And I accept arguments: deco_arg1 deco_arg2
As a decorator maker, I return a decorator
I am a decorator! Somehow you passed me arguments: deco_arg1 deco_arg2
As the decorator, I return the wrapped function.
I am the wrapper around the decorated function.
I can access all the variables deco_arg1 deco_arg2 farg1 farg2
I am the decorated function and only knows about my arguments: farg1 farg2複製代碼

這裏裝飾器生成函數內部傳遞參數是閉包的特性

使用裝飾器須要注意

  • 裝飾器是Python2.4的新特性
  • 裝飾器會下降代碼的性能
  • 裝飾器僅在Python代碼導入時被調用一次,以後你不能動態地改變參數.當你使用"import x",函數已經被裝飾

使用 functools.wraps

最後Python2.5解決了最後一個問題,它提供functools模塊,包含functools.wraps,這個函數會將被裝飾函數的名稱、模塊、文檔字符串拷貝給封裝函數

def foo():
    print "foo"

print foo.__name__
#outputs: foo

# 但當你使用裝飾器
def bar(func):
    def wrapper():
        print "bar"
        return func()
    return wrapper

@bar
def foo():
    print "foo"

print foo.__name__
#outputs: wrapper複製代碼

"functools" 能夠修正這個錯誤

import functools

def bar(func):
    # 咱們所說的 "wrapper", 封裝 "func"
 @functools.wraps(func)
    def wrapper():
        print "bar"
        return func()
    return wrapper

@bar
def foo():
    print "foo"

# 獲得的是原始的名稱, 而不是封裝器的名稱
print foo.__name__
#outputs: foo複製代碼

類裝飾器

class myDecorator(object):

    def __init__(self, func):
        print "inside myDecorator.__init__()"
        self.func = func

    def __call__(self):
        self.func()
        print "inside myDecorator.__call__()"

@myDecorator
def aFunction():
    print "inside aFunction()"

print "Finished decorating aFunction()"

aFunction()

# output:
# inside myDecorator.__init__()
# Finished decorating aFunction()
# inside aFunction()
# inside myDecorator.__call__()複製代碼

咱們能夠看到這個類中有兩個成員:

  1. 一個是init(),這個方法是在咱們給某個函數decorator時被調用,因此,須要有一個func的參數,也就是被decorator的函數。
  2. 一個是call(),這個方法是在咱們調用被decorator函數時被調用的

若是decorator有參數的話,init() 就不能傳入func了,而fn是在call的時候傳入

class myDecorator(object):

    def __init__(self, arg1, arg2):
        self.arg1 = arg2

    def __call__(self, func):
        def wrapped(*args, **kwargs):
            return self.func(*args, **kwargs)
        return wrapped複製代碼

裝飾器示例

Python 內置了三個用於裝飾方法的函數:property、classmethod和 staticmethod。
另外一個常見的裝飾器是 functools.wraps,它的做用是協助構建行爲良好的裝飾器。

functools.lru_cache

functools.lru_cache 實現了內存緩存功能,它能夠把耗時長的函數結果保存起來,避免傳入相同參數時重複計算。

咱們本身的實現代碼以下:

from functools import wraps
def memo(fn):
    cache = {}
    miss = object()

 @wraps(fn)
    def wrapper(*args):
        result = cache.get(args, miss)
        if result is miss:
            result = fn(*args)
            print "{0} has been used: {1}x".format(fn.__name__, wrapper.count)
            cache[args] = result
        return result

    return wrapper

@memo
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)複製代碼

統計函數執行次數的裝飾器

def counter(func):
    """ 記錄並打印一個函數的執行次數 """
    def wrapper(*args, **kwargs):
        wrapper.count = wrapper.count + 1
        res = func(*args, **kwargs)
        print "{0} has been used: {1}x".format(func.__name__, wrapper.count)
        return res
    wrapper.count = 0
    return wrapper複製代碼
  • 裝飾器作緩存

帶有過時時間的內存緩存

def cache_for(duration):
    def deco(func):
 @wraps(func)
        def fn(*args, **kwargs):
            key = pickle.dumps((args, kwargs))
            value, expire = func.func_dict.get(key, (None, None))
            now = int(time.time())
            if value is not None and expire > now:
                return value
            value = func(*args, **kwargs)
            func.func_dict[key] = (value, int(time.time()) + duration)
            return value
        return fn
    return deco複製代碼

統計代碼運行時間

def timeit(fn):

 @wraps(fn)
    def real_fn(*args, **kwargs):
        if config.common['ENVIRON'] == 'PRODUCTION':
            return fn(*args, **kwargs)

        _start = time.time()
        #app.logger.debug('Start timeit for %s' % fn.__name__)
        result = fn(*args, **kwargs)
        _end = time.time()
        _last = _end - _start
        app.logger.debug('End timeit for %s in %s seconds.' %
                         (fn.__name__, _last))
        return result

    return real_fn複製代碼

參考連接

  1. How can I make a chain of function decorators in Python?
  2. 理解PYTHON中的裝飾器
  3. Python修飾器的函數式編程
  4. Understanding Python Decorators in 12 Easy Steps!
  5. PEP 0318 -- Decorators for Functions and Methods
  6. PEP 3129 -- Class Decorators
  7. args and *kwargs? [duplicate]
  8. why-cant-i-set-a-global-variable-in-python
  9. 【flask route】
  10. PythonDecoratorLibrary
  11. 關於Python Decroator的各類提案

最後,感謝女友支持。

歡迎關注(April_Louisa) 請我喝芬達
歡迎關注
歡迎關注
請我喝芬達
請我喝芬達
相關文章
相關標籤/搜索