Python 装饰器

来源:互联网 发布:java获取访问者ip 编辑:程序博客网 时间:2024/06/18 15:03

原文链接:Python Decorator

Python有一个有趣的特征叫做装饰器,可以向已存在的代码添加功能。这也被称作元编程(metaprogramming, 编写操纵其它程序的程序,企图在运行时完成部分本应在编译时完成的工作)

Ref:metaprogramming

热身

为了理解装饰器,我们首先应该知道以下几点:

  • 一切皆对象
  • 我们所定义的名称仅仅是对象的标识
  • 函数也是对象
  • 不同的名称可以绑定到相同的对象

来看一个例子:

>>> first("Hello")Hello>>> second = first>>> second("Hello")Hello
上面的例子中,first和second都引用了同一个函数对象。

现在事情开始变得复杂了,函数能够被当做一个参数传递给另一个函数。如果你以前用过map,filter,reduce,那么你可能对此已经有所了解。这些函数将其它函数作为参数,也被称作高阶函数(Higher order functions)。

下面是这类函数的几个例子:

def inc(x):    '''Function to increase value by 1'''    return x + 1def dec(x):    '''Function to decrease value by 1'''    return x - 1def operate(funct, x):    '''A higher order function to increase or decrease'''    result = func(x)    return result
像下面这样调用:

>>> operate(inc, 3)4>>> operate(dec, 3)2
而且,一个函数还能返回另一个函数。

>>> def is_called():...     def is_returned():...             print('Hello')...     return is_returned...>>> new = is_called()>>> new()Hello>>>
这里,is_returned()是一个嵌套函数,每次我们调用is_called()的时候就返回这个函数。
函数或方法如果能够被调用,那么我们称之为可调用的(callable)。实际上,任何的对象只要实现了__call__()就被认为是可调用的。所以,大多数情况下,一个装饰器就是一个返回可调用对象的可调用对象(A decorator is a callable that returns a callable)。从根本上说,一个装饰器接收一个函数,添加一些功能然后返回,像下面这样:

>>> def is_called():...     def is_returned():...             print('Hello')...     return is_returned...>>> new = is_called()>>> new()Hello>>> def make_pretty(func):...     def inner():...             print('I got decorated')...             func()...     return inner...>>> def ordinary():...     print('I am ordinary')...>>> ordinary()I am ordinary>>> # 装饰ordinary函数>>> pretty = make_pretty(ordinary)>>> pretty()I got decoratedI am ordinary
在上面的例子中,make_pretty()就是一个装饰器。在赋值的这一步

pretty = make_pretty(ordinary)
函数ordinary()被装饰,返回的函数被赋予一个新的名字pretty。肯一看到,装饰器函数向原函数添加了一些新的功能。就像包装一个礼物一样,装饰器扮演了一个包装纸的角色。被包装对象(盒子里边的礼物)的本来的特性并没有改变,但是现在它变得更好看了(被装饰了)。

通常情况下,我们会装饰一个函数并这样赋值给它:

ordinary = make_pretty(ordinary)
这是普遍的一种做法,Python有特定的语法来简化这种操作。我们可以使用@符号后门见跟上装饰器的名字,把它放在需要装饰的函数的定义上面,像这样:

@make_pretty def ordinary():     print("I am ordinary") 
等价于:

def ordinary():     print("I am ordinary") ordinary = make_pretty(ordinary)
这仅仅是采用了语法糖来实现装饰器。

带参数的装饰函数

上面介绍的装饰器非常简单,它只适用于装饰不带参数的函数。要是我们想装饰下面这种函数该怎么办呢:

def divide(a, b):    return a/b
这个函数有两个参数,a和b。我们知道,如果我们传入b的值为0,函数将会抛出一个错误

>>> divide(2, 5)0.4>>> divide(2, 0)Traceback (most recent call last):  File "<stdin>", line 1, in <module>  File "<stdin>", line 2, in divideZeroDivisionError: division by zero
现在我们使用装饰器来检查这种情况:

def smart_divide(func):     def inner(a, b):         print("I am going to divide",a,"and",b)         if b == 0:             print("Whoops! cannot divide")             return         return func(a,b)     return inner @smart_divide def divide(a, b):     return a/b 
如果发生除0错误,新的实现将会返回None
>>> divide(2, 5)I am going to divide 2 and 50.4>>> divide(2, 0)I am going to divide 2 and 0Whoops! cannot divide
使用这种方式我们可以装饰带参数的函数。细心的读者可能注意到嵌套函数inner()的参数列表跟被装饰对象的参数列表是一样的。考虑到这种情况,我们设计一个装饰器能够装饰带任意数量参数的函数。在Python中,function(*args, **kwargs)实现了这种魔法,其中args是非关键字参数(位置有序参数,positional arguments)的元组, kwargs是关键字参数的字典。看下面的一个例子:

def works_for_all(func):    def inner(*args, **kwargs):        print("I can decorate any function")        return func(*args, **kwargs)    return inner
链式装饰器

在Python中可以链式的使用多个装饰器。即是说,一个函数可以被不同的函数装饰多次。仅仅把装饰器依次放在被装饰函数的上面就好了

def works_for_all(func):    def inner(*args, **kwargs):        print("I can decorate any function")        return func(*args, **kwargs)    return innerdef star(func):    def inner(*args, **kwargs):        print('*' * 30)        func(*args, **kwargs)        print('*' * 30)    return innerdef percent(func):    def inner(*args, **kwargs):        print('%' * 30)        func(*args, **kwargs)        print('%' * 30)    return inner@star@percentdef printer(msg):    print(msg)
>>> printer('Hello')******************************%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Hello%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%******************************
上面的语法,

@star@percentdef printer(msg):    print(msg)
等价于:

def printer(msg):    print(msg)printer = star(percent(printer))
在链式装饰中,装饰器的顺序是重要的,如果我们交换下装饰器的顺序:

@percent@stardef printer(msg):    print(msg)
结果会有所不同:

>>> printer('Hello')%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%******************************Hello******************************%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

使用装饰器的一个例子

使用装饰器来计算一个函数调用的时间消耗

from time import timedef timeit(func):    '''Use this decorator to measure execution time of a function.       eg.       @timeit       def yourFunction(args):            dosomething...    '''    def inner(*args, **kwargs):        start = time()        result = func(*args, **kwargs)        elapsed = time() - start        print('Function %s costs time : %10.6f seconds.' % (func.__name__, elapsed))        return result    return inner@timeitdef testFunc(n):    sum = 0    for i in range(n):        sum += i * iif __name__ == '__main__':    testFunc(100000000)

输出结果:

Function testFunc costs time : 18.431244 seconds.





0 0
原创粉丝点击