Python decorator

一、编写无参数的decoratorhtml

Python的 decorator 本质上就是一个高阶函数,它接收一个函数做为参数,而后,返回一个新函数。python

使用 decorator 用Python提供的 @ 语法,这样能够避免手动编写 f = decorate(f) 这样的代码。ubuntu

def log(f):
    def fn(x):
        print 'call ' + f.__name__ + '()...'
        return f(x)
    return fn

对于阶乘函数,@log工做得很好:ruby

@log
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))
print factorial(10)

结果:app

call factorial()...
3628800

可是,对于参数不是一个的函数,调用将报错:函数

@log
def add(x, y):
    return x + y
print add(1, 2)

结果:post

Traceback (most recent call last):
  File "test.py", line 15, in <module>
    print add(1,2)
TypeError: fn() takes exactly 1 argument (2 given)

由于 add() 函数须要传入两个参数,可是 @log 写死了只含一个参数的返回函数。url

要让 @log 自适应任何参数定义的函数,能够利用Python的 *args 和 **kw,保证任意个数的参数老是能正常调用:spa

def log(f):
    def fn(*args, **kw):
        print 'call ' + f.__name__ + '()...'
        return f(*args, **kw)
    return fn

如今,对于任意函数,@log 都能正常工做。.net

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 编写一个@performance,它能够打印出函数调用的时间
import time
 
def performance(f):
     def fn( * args, * * kw):
         t1 = time.time()
         r = f( * args, * * kw)
         t2 = time.time()
         print 'call %s() in %fs' % (f.__name__, (t2 - t1))
         return r
     return fn
 
@performance
def factorial(n):
     return reduce ( lambda x,y: x * y, range ( 1 , n + 1 ))
print factorial( 10 )

补充:python中的魔法参数:*args和**kwargs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def foo(*args, **kwargs):
    print 'args = ' , args
    print 'kwargs = ' , kwargs
    print '---------------------------------------'
if __name__ == '__main__' :
    foo( 1 , 2 , 3 , 4 )
    foo(a= 1 ,b= 2 ,c= 3 )
    foo( 1 , 2 , 3 , 4 , a= 1 ,b= 2 ,c= 3 )
    foo( 'a' , 1 , None, a= 1 , b= '2' , c= 3 )
     
 
输出结果以下:
args = ( 1 , 2 , 3 , 4 )
kwargs = {}
—————————————
args = ()
kwargs = {‘a’: 1 , ‘c’: 3 , ‘b’: 2 }
—————————————
args = ( 1 , 2 , 3 , 4 )
kwargs = {‘a’: 1 , ‘c’: 3 , ‘b’: 2 }
—————————————
args = (‘a’, 1 , None)
kwargs = {‘a’: 1 , ‘c’: 3 , ‘b’: ’ 2 ′}
—————————————
 
能够看到,这两个是python中的可变参数。*args表示任何多个无名参数,它是一个tuple;**kwargs表示关键字参数,它是一个 dict。而且同时使用*args和**kwargs时,必须*args参数列要在**kwargs前,像foo(a= 1 , b=’ 2 ′, c= 3 , a’, 1 , None, )这样调用的话,会提示语法错误“SyntaxError: non-keyword arg after keyword arg”。
 
还有一个很漂亮的用法,就是建立字典:
 
def kw_dict(**kwargs):
return kwargs
print kw_dict(a= 1 ,b= 2 ,c= 3 ) == { 'a' : 1 , 'b' : 2 , 'c' : 3 }
 
其实python中就带有dict类,使用dict(a= 1 ,b= 2 ,c= 3 )便可建立一个字典了。

二、编写带参数的decorator

考察上一节的 @log 装饰器:

def log(f):
    def fn(x):
        print 'call ' + f.__name__ + '()...'
        return f(x)
    return fn

发现对于被装饰的函数,log打印的语句是不能变的(除了函数名)。

若是有的函数很是重要,但愿打印出'[INFO] call xxx()...',有的函数不过重要,但愿打印出'[DEBUG] call xxx()...',这时,log函数自己就须要传入'INFO'或'DEBUG'这样的参数,相似这样:

@log('DEBUG')
def my_func():
    pass

把上面的定义翻译成高阶函数的调用,就是:

my_func = log('DEBUG')(my_func)

上面的语句看上去仍是比较绕,再展开一下:

log_decorator = log('DEBUG')
my_func = log_decorator(my_func)

上面的语句又至关于:

log_decorator = log('DEBUG')
@log_decorator
def my_func():
    pass

因此,带参数的log函数首先返回一个decorator函数,再让这个decorator函数接收my_func并返回新函数:

def log(prefix):
    def log_decorator(f):
        def wrapper(*args, **kw):
            print '[%s] %s()...' % (prefix, f.__name__)
            return f(*args, **kw)
        return wrapper
    return log_decorator

@log('DEBUG')
def test():
    pass
print test()

执行结果:

[DEBUG] test()...
None

 Python Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import time

def performance(unit):
     def perf_decorator(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'# factorial = performance('ms')(factorial)
def factorial(n):
     return  reduce( lambda x,y: x*y,  range( 1, n+ 1))

print factorial( 10)

# 总结(代码不能运行)
@performance  # factorial = performance(factorial)
@performance( 'ms'# factorial = performance('ms')(factorial)
def factorial(n):
     pass

三、完善decorator

@decorator能够动态实现函数功能的增长,可是,通过@decorator“改造”后的函数,和原函数相比,除了功能多一点外,有没有其它不一样的地方?

在没有decorator的状况下,打印函数名:

def f1(x):
    pass
print f1.__name__

输出: f1

有decorator的状况下,再打印函数名:

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__等其它属性。若是要让调用者看不出一个函数通过了@decorator的“改造”,就须要把原函数的一些属性复制到新函数中:

def log(f):
    def wrapper(*args, **kw):
        print 'call...'
        return f(*args, **kw)
    wrapper.__name__ = f.__name__
    wrapper.__doc__ = f.__doc__
    return wrapper

这样写decorator很不方便,由于咱们也很难把原函数的全部必要属性都一个一个复制到新函数上,因此Python内置的functools能够用来自动化完成这个“复制”的任务:

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'。

 Python Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 带参数的@decorator,@functools.wraps的放置
import time, functools

def performance(unit):
     def perf_decorator(f):
        @functools.wraps(f)
         def wrapper(*args, **kw):
             return f(*args, **kw)
         return wrapper
     return perf_decorator

@performance( 'ms')
def factorial(n):
     return  reduce( lambda x,y: x*y,  range( 1, n+ 1))

print factorial.__name__

四、偏函数

当一个函数有不少参数时,调用者就须要提供多个参数。若是减小参数个数,就能够简化调用者的负担。

好比,int()函数能够把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:

>>> int('12345')
12345

但int()函数还提供额外的base参数,默认值为10。若是传入base参数,就能够作 进制的转换:

>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565

假设要转换大量的二进制字符串,每次都传入int(x, base=2)很是麻烦,因而,咱们想到,能够定义一个int2()的函数,默认把base=2传进去:

def int2(x, base=2):
    return int(x, base)

这样,咱们转换二进制就很是方便了:

>>> int2('1000000')
64
>>> int2('1010101')
85

functools.partial就是帮助咱们建立一个偏函数的,不须要咱们本身定义int2(),能够直接使用下面的代码建立一个新的函数int2:

>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85

因此,functools.partial能够把一个参数多的函数变成一个参数少的新函数,少的参数须要在建立时指定默认值,这样,新函数调用的难度就下降了。


摘自:慕课网



相关文章
相关标签/搜索