Python 装饰器

原文链接: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 + 1
def dec(x):
    '''Function to decrease value by 1'''
    return x - 1
def 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 decorated
I 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 divide
ZeroDivisionError: 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 5
0.4
>>> divide(2, 0)
I am going to divide 2 and 0
Whoops! 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 inner

def star(func):
    def inner(*args, **kwargs):
        print('*' * 30)
        func(*args, **kwargs)
        print('*' * 30)
    return inner

def percent(func):
    def inner(*args, **kwargs):
        print('%' * 30)
        func(*args, **kwargs)
        print('%' * 30)
    return inner

@star
@percent
def printer(msg):
    print(msg)
>>> printer('Hello')
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
上面的语法,

@star
@percent
def printer(msg):
    print(msg)
等价于:

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

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

>>> printer('Hello')

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
Hello
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

使用装饰器的一个例子

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

from time import time

def 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

@timeit
def testFunc(n):
    sum = 0
    for i in range(n):
        sum += i * i

if __name__ == '__main__':
    testFunc(100000000)

输出结果:

Function testFunc costs time : 18.431244 seconds.





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值