装饰器函数

 

Python装饰器学习(九步入门):http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html
浅谈Python装饰器:https://blog.csdn.net/mdl13412/article/details/22608283
Python装饰器与面向切面编程:http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html

 

 

 

Python装饰器学习(九步入门)

 

这是在Python学习小组上介绍的内容,现学现卖、多练习是好的学习方式。

第一步:最简单的函数,准备附加额外功能


 # -*- coding:gbk -*-
'''示例1: 最简单的函数,表示调用了两次'''


def deco(func):
    print("before myfunc() called.")
    func()
    print("after myfunc() called.")
    return func


def myfunc():
    print("     myfunc() called.")


print('*****************************')
dec_func = deco(myfunc)

print('*****************************')
dec_func()


"""
结果:
*****************************
before myfunc() called.
     myfunc() called.
after myfunc() called.
*****************************
     myfunc() called.
"""

第二步:使用装饰函数在函数执行前和执行后分别附加额外功能

# -*- coding:gbk -*-
'''示例2: 替换函数(装饰)
装饰函数的参数是被装饰的函数对象,返回原函数对象
装饰的实质语句: myfunc = deco(myfunc)'''


def deco(func):
    print("before myfunc() called.")
    func()
    print("after myfunc() called.")
    return func


def myfunc():
    print("    myfunc() called.")


dec_func = deco(myfunc)
print('****************************')
dec_func()
dec_func()

"""
结果:
before myfunc() called.
    myfunc() called.
after myfunc() called.
****************************
    myfunc() called.
    myfunc() called.
"""

第三步:使用语法糖@来装饰函数

本例中 deco 没有使用内嵌函数,可以看到第一次执行可以进入到装饰函数,但是第二次不会进入装饰函数

# -*- coding:gbk -*-
'''示例3: 使用语法糖@来装饰函数,相当于“myfunc = deco(myfunc)”
但发现新函数只在第一次被调用,且原函数多调用了一次'''


def deco(func):
    print("before myfunc() called.")
    func()
    print("after myfunc() called.")
    return func


@deco
def myfunc():
    print("    myfunc() called.")


# 第一次调用后,返回的是 deco 里面的 func 对象,
# 所以第二次调用myfunc() 只输出 myfunc() called.
myfunc()  # 第一次调用
print('************************')
myfunc()  # 第二次调用

"""
结果:
before myfunc() called.
    myfunc() called.
after myfunc() called.
    myfunc() called.
************************
    myfunc() called.
"""

第四步:使用内嵌包装函数来确保每次新函数都被调用

装饰函数 deco 返回内嵌包装函数对象 _deco。使用内嵌包装函数来确保每次新函数都被调用

# -*- coding:gbk -*-
'''示例4: 使用内嵌包装函数来确保每次新函数都被调用,
内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象'''


def deco(func):
    def _deco():
        print("before myfunc() called.")
        func()
        print("after myfunc() called.")
        # 不需要返回func,实际上应返回原函数的返回值
    return _deco


@deco
def myfunc():
    print("    myfunc() called.")
    return 'ok'


myfunc()
print('*************************')
myfunc()

"""
执行结果:
before myfunc() called.
    myfunc() called.
after myfunc() called.
*************************
before myfunc() called.
    myfunc() called.
after myfunc() called.
"""

第五步:对带参数的函数进行装饰

# -*- coding:gbk -*-
'''示例5: 对带参数的函数进行装饰,
内嵌包装函数的形参和返回值与原函数相同,装饰函数返回内嵌包装函数对象'''


def deco(func):
    def _deco(a, b):
        print("before myfunc() called.")
        ret = func(a, b)
        print("after myfunc() called. result: %s" % ret)
        return ret

    return _deco


@deco
def myfunc(a, b):
    print("    myfunc(%s,%s) called." % (a, b))
    return a + b


myfunc(1, 2)
print('*************************')
myfunc(3, 4)

"""
执行结果:
before myfunc() called.
    myfunc(1,2) called.
after myfunc() called. result: 3
*************************
before myfunc() called.
    myfunc(3,4) called.
after myfunc() called. result: 7
"""

第六步:对参数数量不确定的函数进行装饰

# -*- coding:gbk -*-
'''示例6: 对参数数量不确定的函数进行装饰,
参数用(*args, **kwargs),自动适应变参和命名参数'''


def deco(func):
    def _deco(*args, **kwargs):
        print("before %s called." % func.__name__)
        ret = func(*args, **kwargs)
        print("after %s called. result: %s" % (func.__name__, ret))
        return ret

    return _deco


@deco
def myfunc_1(a, b):
    print("    myfunc_1(%s,%s) called." % (a, b))
    return a + b


@deco
def myfunc_2(a, b, c):
    print("    myfunc_2(%s,%s,%s) called." % (a, b, c))
    return a + b + c


print('*' * 30)
myfunc_1(1, 2)
print('*' * 30)
myfunc_1(3, 4)
print('*' * 30)
myfunc_2(1, 2, 3)
print('*' * 30)
myfunc_2(3, 4, 5)

"""
执行结果:
******************************
before myfunc_1 called.
    myfunc_1(1,2) called.
after myfunc_1 called. result: 3
******************************
before myfunc_1 called.
    myfunc_1(3,4) called.
after myfunc_1 called. result: 7
******************************
before myfunc_2 called.
    myfunc_2(1,2,3) called.
after myfunc_2 called. result: 6
******************************
before myfunc_2 called.
    myfunc_2(3,4,5) called.
after myfunc_2 called. result: 12
"""

第七步:让装饰器带参数

# -*- coding:gbk -*-
'''示例7: 在示例4的基础上,让装饰器带参数,
和上一示例相比在外层多了一层包装。
装饰函数名实际上应更有意义些'''


def deco(arg):
    def _deco(func):
        def __deco():
            print("before %s called [%s]." % (func.__name__, arg))
            func()
            print("after %s called [%s]." % (func.__name__, arg))

        return __deco

    return _deco


@deco("mymodule")
def myfunc():
    print("    myfunc_1() called.")


@deco("module2")
def myfunc2():
    print("    myfunc_2() called.")


print('************************************')
myfunc()
print('************************************')
myfunc2()

"""
执行结果:
************************************
before myfunc called [mymodule].
    myfunc_1() called.
after myfunc called [mymodule].
************************************
before myfunc2 called [module2].
    myfunc_2() called.
after myfunc2 called [module2].
"""

第八步:让装饰器带 类 参数

# -*- coding:gbk -*-
'''示例8: 装饰器带类参数'''


class Locker:
    def __init__(self):
        print("locker.__init__() should be not called.")

    @staticmethod
    def acquire():
        print("locker.acquire() called.(这是静态方法)")

    @staticmethod
    def release():
        print("locker.release() called.(不需要对象实例)")


def deco(cls):
    '''cls 必须实现acquire和release静态方法'''

    def _deco(func):
        def __deco():
            print("before %s called [%s]." % (func.__name__, cls))
            cls.acquire()
            try:
                return func()
            finally:
                cls.release()

        return __deco

    return _deco


@deco(Locker)
def myfunc():
    print("    myfunc() called.")


print('*********************************************')
myfunc()
print('*********************************************')
myfunc()

"""
执行结果:
*********************************************
before myfunc called [].
locker.acquire() called.(这是静态方法)
    myfunc() called.
locker.release() called.(不需要对象实例)
*********************************************
before myfunc called [].
locker.acquire() called.(这是静态方法)
    myfunc() called.
locker.release() called.(不需要对象实例)
"""

第九步:装饰器带类参数,并分拆公共类到其他py文件中,同时演示了对一个函数应用多个装饰器

# -*- coding:gbk -*-
'''mylocker.py: 公共类 for 示例9.py'''


class MyLocker:
    def __init__(self):
        print("mylocker.__init__() called.")

    @staticmethod
    def acquire():
        print("mylocker.acquire() called.")

    @staticmethod
    def unlock():
        print("mylocker.unlock() called.")


class LockerEx(MyLocker):
    @staticmethod
    def acquire():
        print("lockerex.acquire() called.")

    @staticmethod
    def unlock():
        print("lockerex.unlock() called.")


def lock_helper(cls):
    """cls 必须实现acquire和release静态方法"""

    def _deco(func):
        def __deco(*args, **kwargs):
            print("before %s called." % func.__name__)
            cls.acquire()
            try:
                return func(*args, **kwargs)
            finally:
                cls.unlock()

        return __deco

    return _deco
# -*- coding:gbk -*-
'''示例9: 装饰器带类参数,并分拆公共类到其他py文件中
同时演示了对一个函数应用多个装饰器'''


class Example:
    @lock_helper(MyLocker)
    def func_1(self):
        print("    func_1() called.")

    @lock_helper(MyLocker)
    @lock_helper(LockerEx)
    def func_2(self, a, b):
        print("    func_2() called.")
        return a + b


if __name__ == "__main__":
    a = Example()
    a.func_1()
    print(a.func_1())
    print(a.func_2(1, 2))
    print(a.func_2(3, 4))


"""
执行结果:
before func_1 called.
mylocker.acquire() called.
    func_1() called.
mylocker.unlock() called.
before func_1 called.
mylocker.acquire() called.
    func_1() called.
mylocker.unlock() called.
None
before __deco called.
mylocker.acquire() called.
before func_2 called.
lockerex.acquire() called.
    func_2() called.
lockerex.unlock() called.
mylocker.unlock() called.
3
before __deco called.
mylocker.acquire() called.
before func_2 called.
lockerex.acquire() called.
    func_2() called.
lockerex.unlock() called.
mylocker.unlock() called.
7
"""

下面是参考资料,当初有不少地方没看明白,真正练习后才明白些:

1. Python装饰器学习 http://blog.csdn.net/thy38/article/details/4471421

2. Python装饰器与面向切面编程 http://www.cnblogs.com/huxi/archive/2011/03/01/1967600.html

3. Python装饰器的理解 http://apps.hi.baidu.com/share/detail/17572338

 

 

浅谈Python装饰器

 

前置知识

 

一级对象

        Python将一切视为 objec t的子类,即一切都是对象,因此函数可以像变量一样被指向和传递,我们来看下面的例子:

def foo():
    pass


print(issubclass(foo.__class__, object))  

# 执行结果:True

上述代码说明了Python 中的函数是 object 的子类,下面让我们看函数被当作参数传递时的效果:

def foo(func):
    func()


def bar():
    print("bar")


foo(bar)

# 执行结果:bar

 

 

Python 中的 namespace

 

Python中通过提供 namespace 来实现重名函数/方法、变量等信息的识别,其一共有三种 namespace,分别为:

  • local namespace: 作用范围为当前函数或者类方法
  • global namespace: 作用范围为当前模块
  • build-in namespace: 作用范围为所有模块

当函数/方法、变量等信息发生重名时,Python会按照 local namespace -> global namespace -> build-in namespace的顺序搜索用户所需元素,并且以第一个找到此元素的 namespace 为准。
下面以系统的 build-in 函数 str 为例进行说明:

[python] view plain copy print?

  1. def str(s):  
  2.     print "global str()"  
  3.   
  4. def foo():  
  5.     def str(s):  
  6.         print "closure str()"  
  7.     str("dummy")  
  8.   
  9. def bar():  
  10.     str("dummy")  
  11.   
  12. foo()  
  13. bar()  
首先定义三个  global namespace 的函数  strfoo 和 bar,然后在  foo 函数中定义一个内嵌的  local namespace 的函数 str,然后在函数  foo 和  bar 中分别调用  str("dummy"),其运行结果如下所示:

[python] view plain copy print?

  1. closure str()  
  2. global str()  
通过编码实验,我们可以看到:
  • foo 中调用 str 函数时,首先搜索 local namespace,并且成功找到了所需的函数,停止搜索,使用此namespace 中的定义
  • bar 中调用 str 函数时,首先搜索 local namespace,但是没有找到str 方法的定义,因此继续搜索 global namespace,并成功找到了 str 的定义,停止搜索,并使用此定义
下面我们使用Python内置的  `ocals() 和  globals() 函数查看不同  namespace 中的元素定义:

[python] view plain copy print?

  1. var = "var in global"  
  2.   
  3. def fun():  
  4.     var = "var in fun"  
  5.     print "fun: " + str(locals())  
  6.   
  7. print "globals: " + str(globals())  
  8. fun()  
运行结果如下:

[python] view plain copy print?

  1. globals: {'__builtins__''__builtin__' (built-in)>, '__file__''a.py''__package__': None, 'fun''var''var in global''__name__''__main__''__doc__': None}  
  2. fun: {'var''var in fun'}  
通过运行结果,我们看到了  fun 定义了  local namespace 的变量 var,在  global namespace 有一个全局的  var 变量,那么当在 global namespace 中直接访问  var 变量的时候,将会得到  var = "var in global" 的定义,而在 fun 函数的  local namespace 中访问  var 变量,则会得到 fun 私有的  var = "var in fun" 定义。

*args and **kwargs

  • *args: 把所有的参数按出现顺序打包成一个 list
  • **kwargs:把所有 key-value 形式的参数打包成一个 dict

下面给出一个 *args 的例子:

[python] view plain copy print?

  1. params_list = (1, 2)  
  2. params_tupple = (1, 2)  
  3.   
  4. def add(x, y):  
  5.     print x + y  
  6.   
  7. add(*params_list)  
  8. add(*params_tupple)  
其运行结果如下:

[python] view plain copy print?

  1. 3  
  2. 3  
**kwargs 的例子:

[python] view plain copy print?

  1. params = {  
  2.     'x': 1,  
  3.     'y': 2  
  4. }  
  5.   
  6. def add(x, y):  
  7.     print x + y  
  8.   
  9. add(**params)  
其运行结果如下:

[python] view plain copy print?

  1. 3  

 

闭包

javascript 闭包:https://www.runoob.com/js/js-function-closures.html

闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。
直观的说就是形成一个不销毁的栈环境。

[plain] view plain copy print?

  1. 闭包在维基百科上的定义如下: 在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。  
下面给出一个使用闭包实现的 logger factory的例子:
 

[python] view plain copy print?

  1. def logger_facroty(prefix="", with_prefix=True):  
  2.     if with_prefix:  
  3.         def logger(msg):  
  4.             print prefix + msg  
  5.         return logger  
  6.     else:  
  7.         def logger(msg):  
  8.             print msg  
  9.         return logger  
  10.   
  11. logger_with_prefix = logger_facroty("Prefix: ")  
  12. logger_without_prefix = logger_facroty(with_prefix=False)  
  13. logger_with_prefix("msg")  
  14. logger_without_prefix("msg")  
其运行结果如下:
 

[python] view plain copy print?

  1. Prefix: msg  
  2. msg  
在上面这个闭包的例子中, prefix 变量时所谓的自由变量,其在  return logger 执行完毕后,便脱离了创建它的环境 logger_factory,但因为其被 logger_factory 中定义的  logger 函数所引用,其生命周期将至少和  logger 函数相同。这样,在  logger 中就可以引用到 logger_factory 作用域内的变量  prefix
将闭包与 namespace 结合起来:

 

 

[python] view plain copy print?

  1. var = "var in global"  
  2.   
  3. def fun_outer():  
  4.     var = "var in fun_outer"  
  5.     unused_var = "this var is not used in fun_inner"  
  6.     print "fun_outer: " + var  
  7.     print "fun_outer: " + str(locals())  
  8.     print "fun_outer: " + str(id(var))  
  9.   
  10.     def fun_inner():  
  11.         print "fun_inner: " + var  
  12.         print "fun_inner: " + str(locals())  
  13.         print "fun_inner: " + str(id(var))  
  14.   
  15.     return fun_inner  
  16.   
  17. fun_outer()()  
其运行结果如下:
 

[python] view plain copy print?

  1. fun_outer: var in fun_outer  
  2. fun_outer: {'var''var in fun_outer''unused_var''this var is not used in fun_inner'}  
  3. fun_outer: 140228141915584  
  4. fun_inner: var in fun_outer  
  5. fun_inner: {'var''var in fun_outer'}  
  6. fun_inner: 140228141915584  

在这个例子中,当 fun_outer 被定义时,其内部的定义的 fun_inner 函数对 print "fun_inner: " + var中所引用的 var 变量进行搜索,发现第一个被搜索到的 var 定义在 fun_outer 的 local namespace 中,因此使用此定义,通过 print "fun_outer: " + str(id(var)) 和 print "fun_inner: " + str(id(var)),当var 超出 fun_outer 的作用域后,依然存活,而 fun_outer 中的unused_var 变量由于没有被 fun_inner 所引用,因此会被 GC

 

 

什么是闭包?

内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包。

简单说,闭包就是根据不同的配置信息得到不同的结果

再来看看专业的解释:闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。

python实例

看概念总是让人摸不着头脑,看几个python小例子就会了

例1

def make_adder(addend):
    def adder(augend):
        return augend + addend
    return adder

p = make_adder(23)
q = make_adder(44)

print p(100)
print q(100)
运行结果:
123
144

分析一下:

我们发现,make_adder是一个函数,包括一个参数addend,比较特殊的地方是这个函数里面又定义了一个新函数,这个新函数里面的一个变量正好是外部make_adder的参数.也就是说,外部传递过来的addend参数已经和adder函数绑定到一起了,形成了一个新函数,我们可以把addend看做新函数的一个配置信息,配置信息不同,函数的功能就不一样了,也就是能得到定制之后的函数.

再看看运行结果,我们发现,虽然p和q都是make_adder生成的,但是因为配置参数不同,后面再执行相同参数的函数后得到了不同的结果.这就是闭包.

例2

def hellocounter (name):
    count=[0] 
    def counter():
        count[0]+=1
        print 'Hello,',name,',',str(count[0])+' access!'
    return counter

hello = hellocounter('ma6174')
hello()
hello()
hello()  

执行结果
Hello, ysisl , 1 access!
Hello, ysisl , 2 access!
Hello, ysisl , 3 access!

分析一下

这个程序比较有趣,我们可以把这个程序看做统计一个函数调用次数的函数.count[0]可以看做一个计数器,没执行一次hello函数,count[0]的值就加1。也许你会有疑问:为什么不直接写count而用一个列表?这是python2的一个bug,如果不用列表的话,会报这样一个错误:

UnboundLocalError: local variable 'count' referenced before assignment.

什么意思?就是说conut这个变量你没有定义就直接引用了,我不知道这是个什么东西,程序就崩溃了.于是,再python3里面,引入了一个关键字:nonlocal,这个关键字是干什么的?就是告诉python程序,我的这个count变量是再外部定义的,你去外面找吧.然后python就去外层函数找,然后就找到了count=0这个定义和赋值,程序就能正常执行了.

python3 代码

 

def hellocounter (name):
    count=0 
    def counter():
        nonlocal count
        count+=1
        print 'Hello,',name,',',str(count[0])+' access!'
    return counter

hello = hellocounter('ma6174')
hello()
hello()
hello()  

关于这个问题的研究您可以参考http://linluxiang.iteye.com/blog/789946

例3

def makebold(fn):
    def wrapped():
        return "" + fn() + ""
    return wrapped

def makeitalic(fn):
    def wrapped():
        return "" + fn() + ""
    return wrapped

@makebold
@makeitalic
def hello():
    return "hello world"

print hello() 

执行结果
hello world

简单分析

怎么样?这个程序熟悉吗?这不是传说的的装饰器吗?对,这就是装饰器,其实,装饰器就是一种闭包,我们再回想一下装饰器的概念:对函数(参数,返回值等)进行加工处理,生成一个功能增强版的一个函数。再看看闭包的概念,这个增强版的函数不就是我们配置之后的函数吗?区别在于,装饰器的参数是一个函数或类,专门对类或函数进行加工处理。

python里面的好多高级功能,比如装饰器,生成器,列表推到,闭包,匿名函数等,开发中用一下,可能会达到事半功倍的效果!

探索装饰器

定义

[plain] view plain copy print?

  1. 点击打开装饰器在维基百科上的定义链接:http://en.wikipedia.org/wiki/Python_syntax_and_semantics#Decorators
  2. 如下: A decorator is any callable Python object that is used to modify a function, method or class definition.   

 

基本语法

语法糖

[python] view plain copy print?

  1. @bar  
  2. def foo():  
  3.     print "foo"  
其等价于:

[python] view plain copy print?

  1. def foo():  
  2.     print "foo"  
  3. foo = bar(foo)  

 

无参数装饰器

[python] view plain copy print?

  1. def foo(func):  
  2.     print 'decorator foo'  
  3.     return func  
  4.  
  5. @foo  
  6. def bar():  
  7.     print 'bar'  
  8.   
  9. bar()  
foo 函数被用作装饰器,其本身接收一个函数对象作为参数,然后做一些工作后,返回接收的参数,供外界调用。

注意: 时刻牢记 @foo 只是一个语法糖,其本质是 foo = bar(foo)

带参数装饰器

[python] view plain copy print?

  1. import time  
  2.   
  3. def function_performance_statistics(trace_this=True):  
  4.     if trace_this:  
  5.        def performace_statistics_delegate(func):  
  6.             def counter(*args, **kwargs):  
  7.                 start = time.clock()  
  8.                 func(*args, **kwargs)  
  9.                 end =time.clock()  
  10.                 print 'used time: %d' % (end - start, )  
  11.             return counter  
  12.     else:  
  13.        def performace_statistics_delegate(func):  
  14.             return func  
  15.     return performace_statistics_delegate  
  16.  
  17. @function_performance_statistics(True)  
  18. def add(x, y):  
  19.     time.sleep(3)  
  20.     print 'add result: %d' % (x + y,)  
  21.  
  22. @function_performance_statistics(False)  
  23. def mul(x, y=1):  
  24.     print 'mul result: %d' % (x * y,)  
  25.   
  26. add(1, 1)  
  27. mul(10)  
上述代码想要实现一个性能分析器,并接收一个参数,来控制性能分析器是否生效,其运行效果如下所示:

[python] view plain copy print?

  1. add result: 2  
  2. used time: 0  
  3. mul result: 10  
上述代码中装饰器的调用等价于:

[python] view plain copy print?

  1. add = function_performance_statistics(True)(add(1, 1))  
  2. mul = function_performance_statistics(False)(mul(10))  

 

类的装饰器

类的装饰器不常用,因此只简单介绍。

[python] view plain copy print?

  1. def bar(dummy):  
  2.     print 'bar'  
  3.   
  4. def inject(cls):  
  5.     cls.bar = bar  
  6.     return cls  
  7.  
  8. @inject  
  9. class Foo(object):  
  10.     pass  
  11.   
  12. foo = Foo()  
  13. foo.bar()  
上述代码的  inject 装饰器为类动态的添加一个  bar 方法,因为类在调用非静态方法的时候会传进一个 self 指针,因此  bar 的第一个参数我们简单的忽略即可,其运行结果如下:

[python] view plain copy print?

  1. bar  

类装饰器

类装饰器相比函数装饰器,具有灵活度大,高内聚、封装性等优点。其实现起来主要是靠类内部的 __call__ 方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法,下面时一个实例:

[python] view plain copy print?

  1. class Foo(object):  
  2.     def __init__(self, func):  
  3.         super(Foo, self).__init__()  
  4.         self._func = func  
  5.   
  6.     def __call__(self):  
  7.         print 'class decorator'  
  8.         self._func()  
  9.  
  10. @Foo  
  11. def bar():  
  12.     print 'bar'  
  13.   
  14. bar()  
其运行结果如下:

[python] view plain copy print?

  1. class decorator  
  2. bar  

 

内置装饰器

Python中内置的装饰器有三个: staticmethodclassmethod 和property

staticmethod 是类静态方法,其跟成员方法的区别是没有 self 指针,并且可以在类不进行实例化的情况下调用,下面是一个实例,对比静态方法和成员方法

[python] view plain copy print?

  1. class Foo(object):  
  2.     @staticmethod  
  3.     def statc_method(msg):  
  4.         print msg  
  5.   
  6.     def member_method(self, msg):  
  7.         print msg  
  8.   
  9. foo = Foo()  
  10. foo.member_method('some msg')  
  11. foo.statc_method('some msg')  
  12. Foo.statc_method('some msg')  
其运行结果如下:

[python] view plain copy print?

  1. some msg  
  2. some msg  
  3. some msg  
classmethod 与成员方法的区别在于所接收的第一个参数不是  self 类实例的指针,而是当前类的具体类型,下面是一个实例:

[python] view plain copy print?

  1. class Foo(object):  
  2.     @classmethod  
  3.     def class_method(cls):  
  4.         print repr(cls)  
  5.   
  6.     def member_method(self):  
  7.         print repr(self)  
  8.   
  9. foo = Foo()  
  10. foo.class_method()  
  11. foo.member_method()  
其运行结果如下:

[python] view plain copy print?

  1. <class '__main__.Foo'>  
  2. <__main__.Foo object at 0x10a611c50>  
property 是属性的意思,即可以通过通过类实例直接访问的信息,下面是具体的例子:

[python] view plain copy print?

  1. class Foo(object):  
  2.     def __init__(self, var):  
  3.         super(Foo, self).__init__()  
  4.         self._var = var  
  5.  
  6.     @property  
  7.     def var(self):  
  8.         return self._var  
  9.  
  10.     @var.setter  
  11.     def var(self, var):  
  12.         self._var = var  
  13.   
  14. foo = Foo('var 1')  
  15. print foo.var  
  16. foo.var = 'var 2'  
  17. print foo.var  
注意: 如果将上面的  @var.setter 装饰器所装饰的成员函数去掉,则 Foo.var 属性为只读属性,使用  foo.var = 'var 2' 进行赋值时会抛出异常,其运行结果如下:

[python] view plain copy print?

  1. var 1  
  2. var 2  
注意: 如果使用老式的Python类定义,所声明的属性不是  read only的,下面代码说明了这种情况:

[python] view plain copy print?

  1. class Foo:  
  2.     def __init__(self, var):  
  3.         self._var = var  
  4.  
  5.     @property  
  6.     def var(self):  
  7.         return self._var  
  8.   
  9. foo = Foo('var 1')  
  10. print foo.var  
  11. foo.var = 'var 2'  
  12. print foo.var  
其运行结果如下:

[python] view plain copy print?

  1. var 1  
  2. var 2  

 

调用顺序

装饰器的调用顺序与使用 @ 语法糖声明的顺序相反,如下所示:

[python] view plain copy print?

  1. def decorator_a(func):  
  2.     print "decorator_a"  
  3.     return func  
  4.   
  5. def decorator_b(func):  
  6.     print "decorator_b"  
  7.     return func  
  8.  
  9. @decorator_a  
  10. @decorator_b  
  11. def foo():  
  12.     print "foo"  
  13.       
  14. foo()  
其等价于:

[python] view plain copy print?

  1. def decorator_a(func):  
  2.     print "decorator_a"  
  3.     return func  
  4.   
  5. def decorator_b(func):  
  6.     print "decorator_b"  
  7.     return func  
  8.   
  9. def foo():  
  10.     print "foo"  
  11.   
  12. foo = decorator_a(decorator_b(foo))  
  13. foo()  
通过等价的调用形式我们可以看到,按照python的函数求值序列, decorator_b(fun) 会首先被求值,然后将其结果作为输入,传递给 decorator_a,因此其调用顺序与声明顺序相反。其运行结果如下所示:

[python] view plain copy print?

  1. decorator_b  
  2. decorator_a  
  3. foo  

 

调用时机

装饰器很好用,那么它什么时候被调用?性能开销怎么样?会不会有副作用?接下来我们就以几个实例来验证我们的猜想。
首先我们验证一下装饰器的性能开销,代码如下所示:

[python] view plain copy print?

  1. def decorator_a(func):  
  2.     print "decorator_a"  
  3.     print 'func id: ' + str(id(func))  
  4.     return func  
  5.   
  6. def decorator_b(func):  
  7.     print "decorator_b"  
  8.     print 'func id: ' + str(id(func))  
  9.     return func  
  10.   
  11. print 'Begin declare foo with decorators'  
  12. @decorator_a  
  13. @decorator_b  
  14. def foo():  
  15.     print "foo"  
  16. print 'End declare foo with decorators'  
  17.   
  18. print 'First call foo'  
  19. foo()  
  20. print 'Second call foo'  
  21. foo()  
  22. print 'Function infos'  
  23. print 'decorator_a id: ' + str(id(decorator_a))  
  24. print 'decorator_b id: ' + str(id(decorator_b))  
  25. print 'fooid : ' + str(id(foo))  
其运行结果如下:

[python] view plain copy print?

  1. Begin declare foo with decorators  
  2. decorator_b  
  3. func id: 140124961990488  
  4. decorator_a  
  5. func id: 140124961990488  
  6. End declare foo with decorators  
  7. First call foo  
  8. foo  
  9. Second call foo  
  10. foo  
  11. Function infos  
  12. decorator_a id: 140124961954464  
  13. decorator_b id: 140124961988808  
  14. fooid : 140124961990488  
在运行结果中的:

[python] view plain copy print?

  1. Begin declare foo with decorators  
  2. decorator_b  
  3. func id: 140124961990488  
  4. decorator_a  
  5. func id: 140124961990488  
  6. End declare foo with decorators  
证实了装饰器的调用时机为: 被装饰对象定义时
而运行结果中的:

[python] view plain copy print?

  1. First call foo  
  2. foo  
  3. Second call foo  
  4. foo  
证实了在相同  .py 文件中,装饰器对所装饰的函数只进行一次装饰,不会每次调用相应函数时都重新装饰,这个很容易理解,因为其本质等价于下面的函数签名重新绑定:

[python] view plain copy print?

  1. foo = decorator_a(decorator_b(foo))  
对于跨模块的调用,我们编写如下结构的测试代码:

[ruby] view plain copy print?

  1. .  
  2. ├── common  
  3. │   ├── decorator.py  
  4. │   ├── __init__.py  
  5. │   ├── mod_a  
  6. │   │   ├── fun_a.py  
  7. │   │   └── __init__.py  
  8. │   └── mod_b  
  9. │       ├── fun_b.py  
  10. │       └── __init__.py  
  11. └── test.py  
上述所有模块中的  __init__.py 文件均为:  # -*- coding: utf-8 -*-

[python] view plain copy print?

  1. # -*- coding: utf-8 -*-  
  2. # common/mod_a/fun_a.py  
  3.   
  4.   
  5. from common.decorator import foo  
  6.   
  7.   
  8. def fun_a():  
  9.     print 'in common.mod_a.fun_a.fun_a call foo'  
  10.     foo()  

 

 

[python] view plain copy print?

  1. # -*- coding: utf-8 -*-  
  2. # common/mod_b/fun_b.py  
  3.   
  4. from common.decorator import foo  
  5.   
  6. def fun_b():  
  7.     print 'in common.mod_b.fun_b.fun_b call foo'  
  8.     foo()  

[python] view plain copy print?

  1. # -*- coding: utf-8 -*-  
  2. # common/decorator.py  
  3.   
  4. def decorator_a(func):  
  5.     print 'init decorator_a'  
  6.     return func  
  7.  
  8. @decorator_a  
  9. def foo():  
  10.     print 'function foo'  

[python] view plain copy print?

  1. # -*- coding: utf-8 -*-  
  2. # test.py  
  3.   
  4. from common.mod_a.fun_a import fun_a  
  5. from common.mod_b.fun_b import fun_b  
  6.   
  7. fun_a()  
  8. fun_b()  
上述代码通过创建  common.mod_a 和  common.mod_b 两个子模块,并调用 common.decorator 中的  foo 函数,来测试跨模块时装饰器的工作情况,运行  test.py 的结果如下所示:

[python] view plain copy print?

  1. init decorator_a  
  2. in common.mod_a.fun_a.fun_a call foo  
  3. function foo  
  4. in common.mod_b.fun_b.fun_b call foo  
  5. function foo  
经过上面的验证,可以看出,对于跨模块的调用,装饰器也只会初始化一次,不过这要归功于  *.pyc,这与本文主题无关,故不详述。
关于装饰器副作用的话题比较大,这不仅仅是装饰器本身的问题,更多的时候是我们设计上的问题,下面给出一个初学装饰器时大家都会遇到的一个问题——丢失函数元信息:

[python] view plain copy print?

  1. def decorator_a(func):  
  2.     def inner(*args, **kwargs):  
  3.         res = func(*args, **kwargs)  
  4.         return res  
  5.     return inner  
  6.  
  7. @decorator_a  
  8. def foo():  
  9.     '''''foo doc'''  
  10.     return 'foo result'  
  11.   
  12. print 'foo.__module__: ' + str(foo.__module__)  
  13. print 'foo.__name__: ' + str(foo.__name__)  
  14. print 'foo.__doc__: ' + str(foo.__doc__)  
  15. print foo()  
其运行结果如下所示:

[python] view plain copy print?

  1. foo.__module__: __main__  
  2. foo.__name__: inner  
  3. foo.__doc__: None  
  4. foo result  
我们可以看到,在使用 decorator_a 对 foo 函数进行装饰后,foo 的元信息会丢失,解决方案参见:  functools.wraps

 

多个装饰器运行期行为

前面已经讲解过装饰器的调用顺序和调用时机,但是被多个装饰器装饰的函数,其运行期行为还是有一些细节需要说明的,而且很可能其行为会让你感到惊讶,下面时一个实例:

[python] view plain copy print?

  1. def tracer(msg):  
  2.     print "[TRACE] %s" % msg  
  3.   
  4. def logger(func):  
  5.     tracer("logger")  
  6.     def inner(username, password):  
  7.         tracer("inner")  
  8.         print "call %s" % func.__name__  
  9.         return func(username, password)  
  10.     return inner  
  11.   
  12. def login_debug_helper(show_debug_info=False):  
  13.     tracer("login_debug_helper")  
  14.     def proxy_fun(func):  
  15.         tracer("proxy_fun")  
  16.         def delegate_fun(username, password):  
  17.             tracer("delegate_fun")  
  18.             if show_debug_info:  
  19.                 print "username: %s\npassword: %s" % (username, password)  
  20.             return func(username, password)  
  21.         return delegate_fun  
  22.     return proxy_fun  
  23.   
  24. print 'Declaring login_a'  
  25.  
  26. @logger  
  27. @login_debug_helper(show_debug_info=True)  
  28. def login_a(username, password):  
  29.     tracer("login_a")  
  30.     print "do some login authentication"  
  31.     return True  
  32.   
  33. print 'Call login_a'  
  34. login_a("mdl""pwd")  
大家先来看一下运行结果,看看是不是跟自己想象中的一致:

[python] view plain copy print?

  1. Declaring login_a  
  2. [TRACE] login_debug_helper  
  3. [TRACE] proxy_fun  
  4. [TRACE] logger  
  5. Call login_a  
  6. [TRACE] inner  
  7. call delegate_fun  
  8. [TRACE] delegate_fun  
  9. username: mdl  
  10. password: pwd  
  11. [TRACE] login_a  
  12. do some login authentication  
首先,装饰器初始化时的调用顺序与我们前面讲解的一致,如下:

[python] view plain copy print?

  1. Declaring login_a  
  2. [TRACE] login_debug_helper  
  3. [TRACE] proxy_fun  
  4. [TRACE] logger  
然而,接下来,来自  logger 装饰器中的  inner 函数首先被执行,然后才是 login_debug_helper 返回的  proxy_fun 中的  delegate_fun 函数。各位读者发现了吗,运行期执行 login_a 函数的时候,装饰器中返回的函数的执行顺序是相反的,难道是我们前面讲解的例子有错误吗?其实,如果大家的认为运行期调用顺序应该与装饰器初始化阶段的顺序一致的话,那说明大家没有看透这段代码的调用流程,下面我来为大家分析一下。

[python] view plain copy print?

  1. def login_debug_helper(show_debug_info=False):  
  2.     tracer("login_debug_helper")  
  3.     def proxy_fun(func):  
  4.         tracer("proxy_fun")  
  5.         def delegate_fun(username, password):  
  6.             tracer("delegate_fun")  
  7.             if show_debug_info:  
  8.                 print "username: %s\npassword: %s" % (username, password)  
  9.             return func(username, password)  
  10.         return delegate_fun  
  11.     return proxy_fun  
当装饰器  login_debug_helper 被调用时,其等价于:

[python] view plain copy print?

  1. login_debug_helper(show_debug_info=True)(login_a)('mdl''pwd')  
对于只有  login_debug_helper 的情况,现在就应该是执行玩 login_a输出结果的时刻了,但是如果现在在加上 logger 装饰器的话,那么这个 login_debug_helper(show_debug_info=True)(login_a)('mdl', 'pwd')就被延迟执行,而将  login_debug_helper(show_debug_info=True)(login_a) 作为参数传递给  logger,我们令  login_tmp = login_debug_helper(show_debug_info=True)(login_a),则调用过程等价于:

[python] view plain copy print?

  1. login_tmp = login_debug_helper(show_debug_info=True)(login_a)  
  2. login_a = logger(login_tmp)  
  3. login_a('mdl''pwd')  
相信大家看过上面的等价变换后,已经明白问题出在哪里了,如果你还没有明白,我强烈建议你把这个例子自己敲一遍,并尝试用自己的方式进行化简,逐步得出结论。
 

 

一些实例参考

本文主要讲解原理性的东西,具体的实例可以参考下面的链接:
Python装饰器实例:调用参数合法性验证

Python装饰器与面向切面编程

Python装饰器小结

Python tips: 超时装饰器, @timeout decorator

python中判断一个运行时间过长的函数

python利用装饰器和threading实现异步调用

python输出指定函数运行时间的装饰器

python通过装饰器和线程限制函数的执行时间

python装饰器的一个妙用

通过 Python 装饰器实现DRY(不重复代码)原则

参考资料

Understanding Python Decorators in 12 Easy Steps

Decorators and Functional Python

Python Wiki: PythonDecorators

Meta-matters: Using decorators for better Python programming

Python装饰器入门(译)

Python装饰器与面向切面编程

Python 的闭包和装饰器

Python装饰器学习(九步入门)

python 装饰器和 functools 模块

 

 

 

 

 

 

理解Python中的装饰器

文章先由stackoverflow上面的一个问题引起吧,如果使用如下的代码:

@makebold
@makeitalic
def say():
   return "Hello"
打印出如下的输出:
Hello
你会怎么做?
最后给出的答案是:

def makebold(fn):
    def wrapped():
        return "" + fn() + ""
    return wrapped
 
def makeitalic(fn):
    def wrapped():
        return "" + fn() + ""
    return wrapped
 
@makebold
@makeitalic
def hello():
    return "hello world"
 
print hello() ## 返回 hello world

 

现在我们来看看如何从一些最基础的方式来理解Python的装饰器。英文讨论参考Here。

装饰器是一个很著名的设计模式,经常被用于有切面需求的场景,较为经典的有插入日志、性能测试、事务处理等。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量函数中与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

1.1. 需求是怎么来的?

装饰器的定义很是抽象,我们来看一个小例子。

def foo():
    print 'in foo()'
foo()

这是一个很无聊的函数没错。但是突然有一个更无聊的人,我们称呼他为B君,说我想看看执行这个函数用了多长时间,好吧,那么我们可以这样做:

import time
def foo():
    start = time.clock()
    print 'in foo()'
    end = time.clock()
    print 'used:', end - start
 
foo()

很好,功能看起来无懈可击。可是蛋疼的B君此刻突然不想看这个函数了,他对另一个叫foo2的函数产生了更浓厚的兴趣。
怎么办呢?如果把以上新增加的代码复制到foo2里,这就犯了大忌了~复制什么的难道不是最讨厌了么!而且,如果B君继续看了其他的函数呢?

1.2. 以不变应万变,是变也

还记得吗,函数在Python中是一等公民,那么我们可以考虑重新定义一个函数timeit,将foo的引用传递给他,然后在timeit中调用foo并进行计时,这样,我们就达到了不改动foo定义的目的,而且,不论B君看了多少个函数,我们都不用去修改函数定义了!

import time
 
def foo():
    print 'in foo()'
 
def timeit(func):
    start = time.clock()
    func()
    end =time.clock()
    print 'used:', end - start
 
timeit(foo)

看起来逻辑上并没有问题,一切都很美好并且运作正常!……等等,我们似乎修改了调用部分的代码。原本我们是这样调用的:foo(),修改以后变成了:timeit(foo)。这样的话,如果foo在N处都被调用了,你就不得不去修改这N处的代码。或者更极端的,考虑其中某处调用的代码无法修改这个情况,比如:这个函数是你交给别人使用的。

1.3. 最大限度地少改动!

既然如此,我们就来想想办法不修改调用的代码;如果不修改调用代码,也就意味着调用foo()需要产生调用timeit(foo)的效果。我们可以想到将timeit赋值给foo,但是timeit似乎带有一个参数……想办法把参数统一吧!如果timeit(foo)不是直接产生调用效果,而是返回一个与foo参数列表一致的函数的话……就很好办了,将timeit(foo)的返回值赋值给foo,然后,调用foo()的代码完全不用修改!

#-*- coding: UTF-8 -*-
import time
 
def foo():
    print 'in foo()'
# 定义一个计时器,传入一个,并返回另一个附加了计时功能的方法
def timeit(func):
    # 定义一个内嵌的包装函数,给传入的函数加上计时功能的包装
    def wrapper():
        start = time.clock()
        func()
        end =time.clock()
        print 'used:', end - start
     
    # 将包装后的函数返回
    return wrapper
 
foo = timeit(foo)
foo()

这样,一个简易的计时器就做好了!我们只需要在定义foo以后调用foo之前,加上foo = timeit(foo),就可以达到计时的目的,这也就是装饰器的概念,看起来像是foo被timeit装饰了。在在这个例子中,函数进入和退出时需要计时,这被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。与传统编程习惯的从上往下执行方式相比较而言,像是在函数执行的流程中横向地插入了一段逻辑。在特定的业务领域里,能减少大量重复代码。面向切面编程还有相当多的术语,这里就不多做介绍,感兴趣的话可以去找找相关的资料。

这个例子仅用于演示,并没有考虑foo带有参数和有返回值的情况,完善它的重任就交给你了 :)
上面这段代码看起来似乎已经不能再精简了,Python于是提供了一个语法糖来降低字符输入量。

import time
 
def timeit(func):
    def wrapper():
        start = time.clock()
        func()
        end =time.clock()
        print 'used:', end - start
    return wrapper
 
@timeit
def foo():
    print 'in foo()'
 
foo()

重点关注第11行的@timeit,在定义上加上这一行与另外写foo = timeit(foo)完全等价,千万不要以为@有另外的魔力。除了字符输入少了一些,还有一个额外的好处:这样看上去更有装饰器的感觉。

-------------------
要理解python的装饰器,我们首先必须明白在Python中函数也是被视为对象。这一点很重要。先看一个例子:

def shout(word="yes") :
    return word.capitalize()+" !"
 
print shout()
# 输出 : 'Yes !'
 
# 作为一个对象,你可以把函数赋给任何其他对象变量 
scream = shout
 
# 注意我们没有使用圆括号,因为我们不是在调用函数
# 我们把函数shout赋给scream,也就是说你可以通过scream调用shout
print scream()
# 输出 : 'Yes !'
 
# 还有,你可以删除旧的名字shout,但是你仍然可以通过scream来访问该函数
del shout
try :
    print shout()
except NameError, e :
    print e
    #输出 : "name 'shout' is not defined"
 
print scream()
# 输出 : 'Yes !'

我们暂且把这个话题放旁边,我们先看看python另外一个很有意思的属性:可以在函数中定义函数:

def talk() :
    # 你可以在talk中定义另外一个函数
    def whisper(word="yes") :
        return word.lower()+"...";

    # ... 并且立马使用它
    print whisper()
 
# 你每次调用'talk',定义在talk里面的whisper同样也会被调用
talk()
# 输出 :
# yes...
 
# 但是"whisper" 不会单独存在:
try :
    print whisper()
except NameError, e :
    print e
    #输出 : "name 'whisper' is not defined"*

函数引用
从以上两个例子我们可以得出,函数既然作为一个对象,因此:
1. 其可以被赋给其他变量
2. 其可以被定义在另外一个函数内
这也就是说,函数可以返回一个函数,看下面的例子:

 

def getTalk(type="shout") :
    # 我们定义另外一个函数
    def shout(word="yes") :
        return word.capitalize()+" !"
 
    def whisper(word="yes") :
        return word.lower()+"...";
 
    # 然后我们返回其中一个
    if type == "shout" :
        # 我们没有使用(),因为我们不是在调用该函数
        # 我们是在返回该函数
        return shout
    else :
        return whisper
 
# 然后怎么使用呢 ?
# 把该函数赋予某个变量
talk = getTalk()     
 
# 这里你可以看到talk其实是一个函数对象:
print talk
#输出 : 
 
# 该对象由函数返回的其中一个对象:
print talk()
 
# 或者你可以直接如下调用 :
print getTalk("whisper")()
#输出 : yes...

还有,既然可以返回一个函数,我们可以把它作为参数传递给函数:

 

def doSomethingBefore(func) :
    print "I do something before then I call the function you gave me"
    print func()
 
doSomethingBefore(scream)
#输出 :
#I do something before then I call the function you gave me
#Yes !

这里你已经足够能理解装饰器了,其他它可被视为封装器。也就是说,它能够让你在装饰前后执行代码而无须改变函数本身内容。

 

 

 

手工装饰
那么如何进行手动装饰呢?

# 装饰器是一个函数,而其参数为另外一个函数
def my_shiny_new_decorator(a_function_to_decorate) :
    # 在内部定义了另外一个函数:一个封装器。
    # 这个函数将原始函数进行封装,所以你可以在它之前或者之后执行一些代码
    def the_wrapper_around_the_original_function() :

        # 放一些你希望在真正函数执行前的一些代码
        print "Before the function runs"
 
        # 执行原始函数
        a_function_to_decorate()
 
        # 放一些你希望在原始函数执行后的一些代码
        print "After the function runs"
 
    #在此刻,"a_function_to_decrorate"还没有被执行,我们返回了创建的封装函数
    #封装器包含了函数以及其前后执行的代码,其已经准备完毕
    return the_wrapper_around_the_original_function
 
# 现在想象下,你创建了一个你永远也不远再次接触的函数
def a_stand_alone_function() :
    print "I am a stand alone function, don't you dare modify me"
 
a_stand_alone_function()
#输出: I am a stand alone function, don't you dare modify me
 
# 好了,你可以封装它实现行为的扩展。可以简单的把它丢给装饰器
# 装饰器将动态地把它和你要的代码封装起来,并且返回一个新的可用的函数。
a_stand_alone_function_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()
#输出 :
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs
现在你也许要求当每次调用a_stand_alone_function时,实际调用却是a_stand_alone_function_decorated。
实现也很简单,可以用my_shiny_new_decorator来给a_stand_alone_function重新赋值。
a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()
#输出 :
#Before the function runs
#I am a stand alone function, don't you dare modify me
#After the function runs
# And guess what, that's EXACTLY what decorators do !

 

装饰器揭秘
前面的例子,我们可以使用装饰器的语法:

@my_shiny_new_decorator
def another_stand_alone_function() :
    print "Leave me alone"
 
another_stand_alone_function()
#输出 :
#Before the function runs
#Leave me alone
#After the function runs

当然你也可以累积装饰:

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
 
sandwich()
#输出 : --ham--
sandwich = bread(ingredients(sandwich))
sandwich()
#outputs :
#
# #tomatoes#
# --ham--
# ~salad~
#<\______/>

使用python装饰器语法:

@bread
@ingredients
def sandwich(food="--ham--") :
    print food
 
sandwich()
#输出 :
#
# #tomatoes#
# --ham--
# ~salad~
#<\______/>

装饰器的顺序很重要,需要注意:

@ingredients
@bread
def strange_sandwich(food="--ham--") :
    print food
 
strange_sandwich()
#输出 :
##tomatoes#
#
# --ham--
#<\______/>
# ~salad~

最后回答前面提到的问题:

# 装饰器makebold用于转换为粗体
def makebold(fn):
    # 结果返回该函数
    def wrapper():
        # 插入一些执行前后的代码
        return "" + fn() + ""
    return wrapper
 
# 装饰器makeitalic用于转换为斜体
def makeitalic(fn):
    # 结果返回该函数
    def wrapper():
        # 插入一些执行前后的代码
        return "" + fn() + ""
    return wrapper
 
@makebold
@makeitalic
def say():
    return "hello"
 
print say()
#输出: hello
 
# 等同于
def say():
    return "hello"
say = makebold(makeitalic(say))
 
print say()
#输出: hello

内置的装饰器

内置的装饰器有三个,分别是staticmethod、classmethod和property,作用分别是把类中定义的实例方法变成静态方法、类方法和类属性。由于模块里可以定义函数,所以静态方法和类方法的用处并不是太多,除非你想要完全的面向对象编程。而属性也不是不可或缺的,Java没有属性也一样活得很滋润。从我个人的Python经验来看,我没有使用过property,使用staticmethod和classmethod的频率也非常低。

class Rabbit(object):
     
    def __init__(self, name):
        self._name = name
     
    @staticmethod
    def newRabbit(name):
        return Rabbit(name)
     
    @classmethod
    def newRabbit2(cls):
        return Rabbit('')
     
    @property
    def name(self):
        return self._name

这里定义的属性是一个只读属性,如果需要可写,则需要再定义一个setter:
@name.setter
def name(self, name):
    self._name = name
functools模块
functools模块提供了两个装饰器。这个模块是Python 2.5后新增的,一般来说大家用的应该都高于这个版本。但我平时的工作环境是2.4 T-T

2.3.1. wraps(wrapped[, assigned][, updated]): 
这是一个很有用的装饰器。看过前一篇反射的朋友应该知道,函数是有几个特殊属性比如函数名,在被装饰后,上例中的函数名foo会变成包装函数的名字wrapper,如果你希望使用反射,可能会导致意外的结果。这个装饰器可以解决这个问题,它能将装饰过的函数的特殊属性保留。

import time
import functools
 
def timeit(func):
    @functools.wraps(func)
    def wrapper():
        start = time.clock()
        func()
        end =time.clock()
        print 'used:', end - start
    return wrapper
 
@timeit
def foo():
    print 'in foo()'
 
foo()
print foo.__name__

首先注意第5行,如果注释这一行,foo.__name__将是'wrapper'。另外相信你也注意到了,这个装饰器竟然带有一个参数。实际上,他还有另外两个可选的参数,assigned中的属性名将使用赋值的方式替换,而updated中的属性名将使用update的方式合并,你可以通过查看functools的源代码获得它们的默认值。对于这个装饰器,相当于wrapper = functools.wraps(func)(wrapper)。

深入浅出 Python 装饰器:16 步轻松搞定 Python 装饰器
 

 

 

而Python使用了一种相对于Decorator Pattern和Annotation来说非常优雅的方法,这种方法不需要你去掌握什么复杂的OO模型或是Annotation的各种类库规定,完全就是语言层面的玩法:一种函数式编程的技巧。如果你看过本站的《函数式编程》,你一定会为函数式编程的那种“描述你想干什么,而不是描述你要怎么去实现”的编程方式感到畅快。(如果你不了解函数式编程,那在读本文之前,还请你移步去看看《函数式编程》) 好了。

1. 函数

在python中,函数通过def关键字、函数名和可选的参数列表定义。通过return关键字返回值。我们举例来说明如何定义和调用一个简单的函数:

 

def foo():
    return 1
foo()
1

 

方法体(当然多行也是一样的)是必须的,通过缩进来表示,在方法名的后面加上双括号()就能够调用函数

2. 作用域

python中,函数会创建一个新的作用域。python开发者可能会说函数有自己的命名空间,差不多一个意思。这意味着在函数内部碰到一个变量的时候函数会优先在自己的命名空间里面去寻找。让我们写一个简单的函数看一下 本地作用域 和 全局作用域有什么不同:

 

a_string = "This is a global variable"
def foo():
    print locals()
print globals()
{..., 'a_string': 'This is a global variable'}
foo() # 2
{}

 

内置的函数globals返回一个包含所有python解释器知道的变量名称的字典(为了干净和洗的白白的,我省略了python自行创建的一些变量)。在#2我调用了函数 foo 把函数内部本地作用域里面的内容打印出来。我们能够看到,函数foo有自己独立的命名空间,虽然暂时命名空间里面什么都还没有。

3. 变量解析规则

当然这并不是说我们在函数里面就不能访问外面的全局变量。在python的作用域规则里面,创建变量一定会在当前作用域里创建一个变量,但是访问或者修改变量时会先在当前作用域查找变量,没有找到匹配变量的话会依次向上在闭合的作用域里面进行查看找。所以如果我们修改函数foo的实现让它打印全局的作用域里的变量也是可以的:

 

a_string = "This is a global variable"
def foo():
    print a_string # 1
foo()
This is a global variable

 

在#1处,python解释器会尝试查找变量a_string,当然在函数的本地作用域里面找不到,所以接着会去上层的作用域里面去查找。

但是另一方面,假如我们在函数内部给全局变量赋值,结果却和我们想的不一样:

 

a_string = "This is a global variable"
def foo():
    a_string = "test" # 1
    print locals()
foo()
{'a_string': 'test'}
a_string # 2
'This is a global variable'

 

我们能够看到,全局变量能够被访问到(如果是可变数据类型(像list,dict这些)甚至能够被更改)但是赋值不行。在函数内部的#1处,我们实际上新创建了一个局部变量,隐藏全局作用域中的同名变量。我们可以通过打印出局部命名空间中的内容得出这个结论。我们也能看到在#2处打印出来的变量a_string的值并没有改变。

4. 变量生存周期

值得注意的一个点是,变量不仅是生存在一个个的命名空间内,他们都有自己的生存周期,请看下面这个例子:

 

def foo():
    x = 1
foo()
print x # 1
#Traceback (most recent call last):
#NameError: name 'x' is not defined

 

#1处发生的错误不仅仅是因为作用域规则导致的(尽管这是抛出了NameError的错误的原因)它还和python以及其它很多编程语言中函数调用实现的机制有关。在这个地方这个执行时间点并没有什么有效的语法让我们能够获取变量x的值,因为它这个时候压根不存在!函数foo的命名空间随着函数调用开始而开始,结束而销毁。

5. 函数参数

python允许我们向函数传递参数,参数会变成本地变量存在于函数内部。

 

def foo(x):
    print locals()
foo(1)
{'x': 1}

 

在Python里有很多的方式来定义和传递参数,完整版可以查看 python官方文档。我们这里简略的说明一下:函数的参数可以是必须的位置参数或者是可选的命名,默认参数。

 

def foo(x, y=0): # 1
    return x - y
foo(3, 1) # 2
2
foo(3) # 3
3
foo() # 4
#Traceback (most recent call last):
#TypeError: foo() takes at least 1 argument (0 given)
foo(y=1, x=3) # 5
2

 

在#1处我们定义了函数foo,它有一个位置参数x和一个命名参数y。在#2处我们能够通过常规的方式来调用函数,尽管有一个命名参数,但参数依然可以通过位置传递给函数。在调用函数的时候,对于命名参数y我们也可以完全不管就像#3处所示的一样。如果命名参数没有接收到任何值的话,python会自动使用声明的默认值也就是0。需要注意的是我们不能省略第一个位置参数x, 否则的话就会像#4处所示发生错误。

目前还算简洁清晰吧, 但是接下来可能会有点令人困惑。python支持函数调用时的命名参数(个人觉得应该是命名实参)。看看#5处的函数调用,我们传递的是两个命名实参,这个时候因为有名称标识,参数传递的顺序也就不用在意了。

当然相反的情况也是正确的:函数的第二个形参是y,但是我们通过位置的方式传递值给它。在#2处的函数调用foo(3,1),我们把3传递给了第一个参数,把1传递给了第二个参数,尽管第二个参数是一个命名参数。

一个简单的概念:函数的参数可以有名称和位置。这意味着在函数的定义和调用的时候会稍稍在理解上有点儿不同。我们可以给只定义了位置参数的函数传递命名参数(实参),反之亦然!如果觉得不够可以查看官方文档

6. 嵌套函数

Python允许创建嵌套函数。这意味着我们可以在函数里面定义函数而且现有的作用域和变量生存周期依旧适用。

 

def outer():
    x = 1
    def inner():
        print x # 1
    return inner() # 2
outer()
1

 

这个例子有一点儿复杂,但是看起来也还行。想一想在#1发生了什么:python解释器需找一个叫x的本地变量,查找失败之后会继续在上层的作用域里面寻找,这个上层的作用域定义在另外一个函数里面。对函数outer来说,变量x是一个本地变量,但是如先前提到的一样,函数inner可以访问封闭的作用域(至少可以读和修改)。在#2处,我们调用函数inner,非常重要的一点是,inner也仅仅是一个遵循python变量解析规则的变量名,python解释器会优先在outer的作用域里面对变量名inner查找匹配的变量.

7. 函数是python世界里的一级类对象

显而易见,在python里函数和其他东西一样都是对象。(此处应该大声歌唱)啊!包含变量的函数,你也并不是那么特殊!

 

issubclass(int, object) # all objects in Python inherit from a common baseclass
#True
def foo():
    pass
foo.__class__ # 1
#
issubclass(foo.__class__, object)
#True

 

你也许从没有想过,你定义的函数居然会有属性。没办法,函数在python里面就是对象(Python一切皆对象),和其他的东西一样,也许这样描述会太学院派太官方了点:在python里,函数只是一些普通的值而已和其他的值一毛一样。这就是说你可以把函数像参数一样传递给其他的函数或者说从函数里面返回函数!如果你从来没有这么想过,那看看下面这个例子:

 

def add(x, y):
    return x + y
def sub(x, y):
    return x - y
def apply(func, x, y): # 1
    return func(x, y) # 2
apply(add, 2, 1) # 3
3
apply(sub, 2, 1)
1

 

这个例子对你来说应该不会很奇怪。add和sub是非常普通的两个python函数,接受两个值,返回一个计算后的结果值。在#1处你们能看到准备接收一个函数的变量只是一个普通的变量而已,和其他变量一样。在#2处我们调用传进来的函数:“()代表着调用的操作并且调用变量包含的值。在#3处,你们也能看到传递函数并没有什么特殊的语法。” 函数的名称只是很其他变量一样的表标识符而已。

你们也许看到过这样的行为:“python把频繁要用的操作变成函数作为参数进行使用,像通过传递一个函数给内置排序函数的key参数从而来自定义排序规则。那把函数当做返回值回事这样的情况呢:

 

def outer():
    def inner():
        print "Inside inner"
    return inner # 1
foo = outer() #2
foo
#
foo()
#Inside inner

 

这个例子看起来也许会更加的奇怪。在#1处我把恰好是函数标识符的变量inner作为返回值返回出来。这并没有什么特殊的语法:”把函数inner返回出来,否则它根本不可能会被调用到。“还记得变量的生存周期吗?每次函数outer被调用的时候,函数inner都会被重新定义,如果它不被当做变量返回的话,每次执行过后它将不复存在。

在#2处我们捕获住返回值 – 函数inner,将它存在一个新变量foo里。我们能够看到,当对变量foo进行求值,它确实包含函数inner,而且我们能够对他进行调用。初次看起来可能会觉得有点奇怪,但是理解起来并不困难是吧。坚持住,因为奇怪的转折马上就要来了

8. 闭包

我们先不急着定义什么是闭包,先来看看一段代码,仅仅是把上一个例子简单的调整了一下:

 

def outer():
    x = 1
    def inner():
        print x # 1
    return inner
foo = outer()
foo.func_closure
#(,)

 

在上一个例子中我们了解到,inner作为一个函数被outer返回,保存在一个变量foo,并且我们能够对它进行调用foo()。不过它会正常的运行吗?我们先来看看作用域规则。

所有的东西都在python的作用域规则下进行工作:“x是函数outer里的一个局部变量。当函数inner在#1处打印x的时候,python解释器会在inner内部查找相应的变量,当然会找不到,所以接着会到封闭作用域里面查找,并且会找到匹配。

但是从变量的生存周期来看,该怎么理解呢?我们的变量x是函数outer的一个本地变量,这意味着只有当函数outer正在运行的时候才会存在。根据我们已知的python运行模式,我们没法在函数outer返回之后继续调用函数inner,在函数inner被调用的时候,变量x早已不复存在,可能会发生一个运行时错误。

万万没想到,返回的函数inner居然能够正常工作。Python支持一个叫做函数闭包的特性,用人话来讲就是,嵌套定义在非全局作用域里面的函数能够记住它在被定义的时候它所处的封闭命名空间。这能够通过查看函数的func_closure属性得出结论,这个属性里面包含封闭作用域里面的值(只会包含被捕捉到的值,比如x,如果在outer里面还定义了其他的值,封闭作用域里面是不会有的)

记住,每次函数outer被调用的时候,函数inner都会被重新定义。现在变量x的值不会变化,所以每次返回的函数inner会是同样的逻辑,假如我们稍微改动一下呢?

 

def outer(x):
    def inner():
        print x # 1
    return inner
print1 = outer(1)
print2 = outer(2)
print1()
1
print2()
2

 

从这个例子中你能够看到闭包 – 被函数记住的封闭作用域 – 能够被用来创建自定义的函数,本质上来说是一个硬编码的参数。事实上我们并不是传递参数1或者2给函数inner,我们实际上是创建了能够打印各种数字的各种自定义版本。

闭包单独拿出来就是一个非常强大的功能, 在某些方面,你也许会把它当做一个类似于面向对象的技术:outer像是给inner服务的构造器,x像一个私有变量。使用闭包的方式也有很多:你如果熟悉python内置排序方法的参数key,你说不定已经写过一个lambda方法在排序一个列表的列表的时候基于第二个元素而不是第一个。现在你说不定也可以写一个itemgetter方法,接收一个索引值来返回一个完美的函数,传递给排序函数的参数key。

不过,我们现在不会用闭包做这么low的事(⊙o⊙)…!相反,让我们再爽一次,写一个高大上的装饰器!

9. 装饰器

装饰器其实就是一个闭包,把一个函数当做参数然后返回一个替代版函数。我们一步步从简到繁来瞅瞅:

 

def outer(some_func):
    def inner():
        print "before some_func"
        ret = some_func() # 1
        return ret + 1
    return inner
def foo():
    return 1
decorated = outer(foo) # 2
decorated()
#before some_func
#2

 

仔细看看上面这个装饰器的例子。们定义了一个函数outer,它只有一个some_func的参数,在他里面我们定义了一个嵌套的函数inner。inner会打印一串字符串,然后调用some_func,在#1处得到它的返回值。在outer每次调用的时候some_func的值可能会不一样,但是不管some_func的之如何,我们都会调用它。最后,inner返回some_func() + 1的值 – 我们通过调用在#2处存储在变量decorated里面的函数能够看到被打印出来的字符串以及返回值2,而不是期望中调用函数foo得到的返回值1。

我们可以认为变量decorated是函数foo的一个装饰版本,一个加强版本。事实上如果打算写一个有用的装饰器的话,我们可能会想愿意用装饰版本完全取代原先的函数foo,这样我们总是会得到我们的”加强版“foo。想要达到这个效果,完全不需要学习新的语法,简单地赋值给变量foo就行了:

 

foo = outer(foo)
foo # doctest: +ELLIPSIS
#

 

现在,任何怎么调用都不会牵扯到原先的函数foo,都会得到新的装饰版本的foo。

假设有如下函数:

 

def now():
    print '2013-12-25'
f = now
f()
#2013-12-25

 

现在假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

 

def log(func):
    def wrapper(*args, **kw):
        print 'call %s():' % func.__name__
        return func(*args, **kw)
    return wrapper

 

观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。

10. 使用 @ 标识符将装饰器应用到函数

Python2.4支持使用标识符@将装饰器应用在函数上,只需要在函数的定义前加上@和装饰器的名称。在上一节的例子里我们是将原本的方法用装饰后的方法代替:

 

add = wrapper(add)

 

这种方式能够在任何时候对任意方法进行包装。但是如果我们自定义一个方法,我们可以使用@进行装饰:

 

@wrapper
def add(a, b):
    return Coordinate(a.x + b.x, a.y + b.y)

 

需要明白的是,这样的做法和先前简单的用包装方法替代原有方法是一模一样的, python只是加了一些语法糖让装饰的行为更加的直接明确和优雅一点。

多个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)

 

这意味着decorator(arg1, arg2)这个函数需要返回一个“真正的decorator”。

11. *args and **kwargs

我们已经完成了一个有用的装饰器,但是由于硬编码的原因它只能应用在一类具体的方法上,这类方法接收两个参数,传递给闭包捕获的函数。如果我们想实现一个能够应用在任何方法上的装饰器要怎么做呢?再比如,如果我们要实现一个能应用在任何方法上的类似于计数器的装饰器,不需要改变原有方法的任何逻辑。这意味着装饰器能够接受拥有任何签名的函数作为自己的被装饰方法,同时能够用传递给它的参数对被装饰的方法进行调用。

非常巧合的是Python正好有支持这个特性的语法。可以阅读 Python Tutorial 获取更多的细节。当定义函数的时候使用了*,意味着那些通过位置传递的参数将会被放在带有*前缀的变量中, 所以:

 

def one(*args):
    print args # 1
one()
#()
one(1, 2, 3)
#(1, 2, 3)
def two(x, y, *args): # 2
    print x, y, args
two('a', 'b', 'c')
#a b ('c',)

 

第一个函数one只是简单地讲任何传递过来的位置参数全部打印出来而已,你们能够看到,在代码#1处我们只是引用了函数内的变量args, *args仅仅只是用在函数定义的时候用来表示位置参数应该存储在变量args里面。Python允许我们制定一些参数并且通过args捕获其他所有剩余的未被捕捉的位置参数,就像#2处所示的那样。
*操作符在函数被调用的时候也能使用。意义基本是一样的。当调用一个函数的时候,一个用*标志的变量意思是变量里面的内容需要被提取出来然后当做位置参数被使用。同样的,来看个例子:

 

def add(x, y):
    return x + y
lst = [1,2]
add(lst[0], lst[1]) # 1
3
add(*lst) # 2
3

 

#1处的代码和#2处的代码所做的事情其实是一样的,在#2处,python为我们所做的事其实也可以手动完成。这也不是什么坏事,*args要么是表示调用方法大的时候额外的参数可以从一个可迭代列表中取得,要么就是定义方法的时候标志这个方法能够接受任意的位置参数。
接下来提到的**会稍多更复杂一点,**代表着键值对的餐宿字典,和*所代表的意义相差无几,也很简单对不对:

 

def foo(**kwargs):
    print kwargs
foo()
#{}
foo(x=1, y=2)
#{'y': 2, 'x': 1}

 

当我们定义一个函数的时候,我们能够用**kwargs来表明,所有未被捕获的关键字参数都应该存储在kwargs的字典中。如前所诉,args、kwargs并不是python语法的一部分,但在定义函数的时候,使用这样的变量名算是一个不成文的约定。和*一样,我们同样可以在定义或者调用函数的时候使用**。

 

dct = {'x': 1, 'y': 2}
def bar(x, y):
    return x + y
bar(**dct)
#3

 

12. 更通用的装饰器

有了这招新的技能,我们随随便便就可以写一个能够记录下传递给函数参数的装饰器了。先来个简单地把日志输出到界面的例子:

 

def logger(func):
    def inner(*args, **kwargs): #1
        print "Arguments were: %s, %s" % (args, kwargs)
        return func(*args, **kwargs) #2
    return inner

 

请注意函数inner,它能够接受任意数量和类型的参数并把它们传递给被包装的方法,这让我们能够用这个装饰器来装饰任何方法。

 

@logger
def foo1(x, y=1):
    return x * y
@logger
def foo2():
    return 2
foo1(5, 4)
#Arguments were: (5, 4), {}
#20
foo1(1)
#Arguments were: (1,), {}
#1
foo2()
#Arguments were: (), {}
#2

 

随便调用我们定义的哪个方法,相应的日志也会打印到输出窗口,和我们预期的一样。

13. 带参数的装饰器:

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

 

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print '%s %s():' % (text, func.__name__)
            return func(*args, **kw)
        return wrapper
    return decorator

 

这个3层嵌套的decorator用法如下:

 

@log('execute')
def now():
    print '2013-12-25'

 

执行结果如下:

 

>>> now()
execute now():
2013-12-25

 

和两层嵌套的decorator相比,3层嵌套的效果是这样的:

 

now = log('execute')(now)

 

我们来剖析上面的语句,首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

14. 装饰器的副作用

以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper'

 

>>> now.__name__
'wrapper'

 

因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

 

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print 'call %s():' % func.__name__
        return func(*args, **kw)
    return wrapper

 

或者针对带参数的decorator:

 

import functools

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print '%s %s():' % (text, func.__name__)
            return func(*args, **kw)
        return wrapper
    return decorator

 

import functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。

当然,即使是你用了functools的wraps,也不能完全消除这样的副作用。你会发现,即使是你你用了functools的wraps,你在用getargspec时,参数也不见了。要修正这一问题,我们还得用Python的反射来解决,当然,我相信大多数人的程序都不会去getargspec。所以,用functools的wraps应该够用了。

15. class式的 Decorator

首先,先得说一下,decorator的class方式,还是看个示例:

 

class myDecorator(object):
 
    def __init__(self, fn):
        print "inside myDecorator.__init__()"
        self.fn = fn
 
    def __call__(self):
        self.fn()
        print "inside myDecorator.__call__()"
 
@myDecorator
def aFunction():
    print "inside aFunction()"
 
print "Finished decorating aFunction()"
 
aFunction()
 
# 输出:
# inside myDecorator.__init__()
# Finished decorating aFunction()
# inside aFunction()
# inside myDecorator.__call__()

 

1)一个是__init__(),这个方法是在我们给某个函数decorator时被调用,所以,需要有一个fn的参数,也就是被decorator的函数。
2)一个是__call__(),这个方法是在我们调用被decorator函数时被调用的。
上面输出可以看到整个程序的执行顺序。

这看上去要比“函数式”的方式更易读一些。

上面这段代码中,我们需要注意这几点:

1)如果decorator有参数的话,__init__() 成员就不能传入fn了,而fn是在__call__的时候传入的。

16. 一些decorator的示例

好了,现在我们来看一下各种decorator的例子:

16.1 给函数调用做缓存

这个例实在是太经典了,整个网上都用这个例子做decorator的经典范例,因为太经典了,所以,我这篇文章也不能免俗。

 

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)
            cache[args] = result
        return result
 
    return wrapper
 
@memo
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

 

上面这个例子中,是一个斐波拉契数例的递归算法。我们知道,这个递归是相当没有效率的,因为会重复调用。比如:我们要计算fib(5),于是其分解成fib(4) + fib(3),而fib(4)分解成fib(3)+fib(2),fib(3)又分解成fib(2)+fib(1)…… 你可看到,基本上来说,fib(3), fib(2), fib(1)在整个递归过程中被调用了两次。

而我们用decorator,在调用函数前查询一下缓存,如果没有才调用了,有了就从缓存中返回值。一下子,这个递归从二叉树式的递归成了线性的递归。

16.2 Profiler的例子

这个例子没什么高深的,就是实用一些。

 

import cProfile, pstats, StringIO
 
def profiler(func):
    def wrapper(*args, **kwargs):
        datafn = func.__name__ + ".profile" # Name the data file
        prof = cProfile.Profile()
        retval = prof.runcall(func, *args, **kwargs)
        #prof.dump_stats(datafn)
        s = StringIO.StringIO()
        sortby = 'cumulative'
        ps = pstats.Stats(prof, stream=s).sort_stats(sortby)
        ps.print_stats()
        print s.getvalue()
        return retval
 
    return wrapper

 

16.3 注册回调函数

下面这个示例展示了通过URL的路由来调用相关注册的函数示例:

 

class MyApp():
    def __init__(self):
        self.func_map = {}
 
    def register(self, name):
        def func_wrapper(func):
            self.func_map[name] = func
            return func
        return func_wrapper
 
    def call_method(self, name=None):
        func = self.func_map.get(name, None)
        if func is None:
            raise Exception("No function registered against - " + str(name))
        return func()
 
app = MyApp()
 
@app.register('/')
def main_page_func():
    return "This is the main page."
 
@app.register('/next_page')
def next_page_func():
    return "This is the next page."
 
print app.call_method('/')
print app.call_method('/next_page')

 

注意:
1)上面这个示例中,用类的实例来做decorator。
2)decorator类中没有__call__(),但是wrapper返回了原函数。所以,原函数没有发生任何变化。

16.4 给函数打日志

下面这个示例演示了一个logger的decorator,这个decorator输出了函数名,参数,返回值,和运行时间。

 

from functools import wraps
def logger(fn):
    @wraps(fn)
    def wrapper(*args, **kwargs):
        ts = time.time()
        result = fn(*args, **kwargs)
        te = time.time()
        print "function      = {0}".format(fn.__name__)
        print "    arguments = {0} {1}".format(args, kwargs)
        print "    return    = {0}".format(result)
        print "    time      = %.6f sec" % (te-ts)
        return result
    return wrapper
 
@logger
def multipy(x, y):
    return x * y
 
@logger
def sum_num(n):
    s = 0
    for i in xrange(n+1):
        s += i
    return s
 
print multipy(2, 10)
print sum_num(100)
print sum_num(10000000)

 

上面那个打日志还是有点粗糙,让我们看一个更好一点的(带log level参数的):

 

import inspect
def get_line_number():
    return inspect.currentframe().f_back.f_back.f_lineno
 
def logger(loglevel):
    def log_decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            ts = time.time()
            result = fn(*args, **kwargs)
            te = time.time()
            print "function   = " + fn.__name__,
            print "    arguments = {0} {1}".format(args, kwargs)
            print "    return    = {0}".format(result)
            print "    time      = %.6f sec" % (te-ts)
            if (loglevel == 'debug'):
                print "    called_from_line : " + str(get_line_number())
            return result
        return wrapper
    return log_decorator

 

但是,上面这个带log level参数的有两具不好的地方,
1) loglevel不是debug的时候,还是要计算函数调用的时间。
2) 不同level的要写在一起,不易读。

我们再接着改进:

 

import inspect
 
def advance_logger(loglevel):
 
    def get_line_number():
        return inspect.currentframe().f_back.f_back.f_lineno
 
    def _basic_log(fn, result, *args, **kwargs):
        print "function   = " + fn.__name__,
        print "    arguments = {0} {1}".format(args, kwargs)
        print "    return    = {0}".format(result)
 
    def info_log_decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            result = fn(*args, **kwargs)
            _basic_log(fn, result, args, kwargs)
        return wrapper
 
    def debug_log_decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            ts = time.time()
            result = fn(*args, **kwargs)
            te = time.time()
            _basic_log(fn, result, args, kwargs)
            print "    time      = %.6f sec" % (te-ts)
            print "    called_from_line : " + str(get_line_number())
        return wrapper
 
    if loglevel is "debug":
        return debug_log_decorator
    else:
        return info_log_decorator

 

你可以看到两点,
1)我们分了两个log level,一个是info的,一个是debug的,然后我们在外尾根据不同的参数返回不同的decorator。
2)我们把info和debug中的相同的代码抽到了一个叫_basic_log的函数里,DRY原则。

16.5 一个MySQL的Decorator

下面这个decorator是我在工作中用到的代码,我简化了一下,把DB连接池的代码去掉了,这样能简单点,方便阅读。

 

import umysql
from functools import wraps
 
class Configuraion:
    def __init__(self, env):
        if env == "Prod":
            self.host    = "coolshell.cn"
            self.port    = 3306
            self.db      = "coolshell"
            self.user    = "coolshell"
            self.passwd  = "fuckgfw"
        elif env == "Test":
            self.host   = 'localhost'
            self.port   = 3300
            self.user   = 'coolshell'
            self.db     = 'coolshell'
            self.passwd = 'fuckgfw'
 
def mysql(sql):
 
    _conf = Configuraion(env="Prod")
 
    def on_sql_error(err):
        print err
        sys.exit(-1)
 
    def handle_sql_result(rs):
        if rs.rows > 0:
            fieldnames = [f[0] for f in rs.fields]
            return [dict(zip(fieldnames, r)) for r in rs.rows]
        else:
            return []
 
    def decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            mysqlconn = umysql.Connection()
            mysqlconn.settimeout(5)
            mysqlconn.connect(_conf.host, _conf.port, _conf.user, \
                              _conf.passwd, _conf.db, True, 'utf8')
            try:
                rs = mysqlconn.query(sql, {})
            except umysql.Error as e:
                on_sql_error(e)
 
            data = handle_sql_result(rs)
            kwargs["data"] = data
            result = fn(*args, **kwargs)
            mysqlconn.close()
            return result
        return wrapper
 
    return decorator
 
@mysql(sql = "select * from coolshell" )
def get_coolshell(data):
    ... ...
    ... ..

 

16.6 线程异步

下面量个非常简单的异步执行的decorator,注意,异步处理并不简单,下面只是一个示例。

 

from threading import Thread
from functools import wraps
 
def async(func):
    @wraps(func)
    def async_func(*args, **kwargs):
        func_hl = Thread(target = func, args = args, kwargs = kwargs)
        func_hl.start()
        return func_hl
 
    return async_func
 
if __name__ == '__main__':
    from time import sleep
 
    @async
    def print_somedata():
        print 'starting print_somedata'
        sleep(2)
        print 'print_somedata: 2 sec passed'
        sleep(2)
        print 'print_somedata: 2 sec passed'
        sleep(2)
        print 'finished print_somedata'
 
    def main():
        print_somedata()
        print 'back in main'
        print_somedata()
        print 'back in main'
 
    main()

 

16.7 超时函数

这个函数的作用在于可以给任意可能会hang住的函数添加超时功能,这个功能在编写外部API调用 、网络爬虫、数据库查询的时候特别有用。

timeout装饰器的代码如下:

 

# coding=utf-8
# 测试utf-8编码
import sys

reload(sys)
sys.setdefaultencoding('utf-8')

import signal, functools


class TimeoutError(Exception): pass


def timeout(seconds, error_message="Timeout Error: the cmd 30s have not finished."):
    def decorated(func):
        result = ""

        def _handle_timeout(signum, frame):
            global result
            result = error_message
            raise TimeoutError(error_message)

        def wrapper(*args, **kwargs):
            global result
            signal.signal(signal.SIGALRM, _handle_timeout)
            signal.alarm(seconds)

            try:
                result = func(*args, **kwargs)
            finally:
                signal.alarm(0)
                return result
            return result

        return functools.wraps(func)(wrapper)

    return decorated


@timeout(2)  # 限定下面的slowfunc函数如果在5s内不返回就强制抛TimeoutError Exception结束
def slowfunc(sleep_time):
    a = 1
    import time
    time.sleep(sleep_time)
    return a


# slowfunc(3) #sleep 3秒,正常返回 没有异常


print slowfunc(11)  # 被终止

 

 

16.8 Trace函数

有时候出于演示目的或者调试目的,我们需要程序运行的时候打印出每一步的运行顺序 和调用逻辑。类似写bash的时候的bash -x调试功能,然后Python解释器并没有 内置这个时分有用的功能,那么我们就“自己动手,丰衣足食”。

Trace装饰器的代码如下:

 

# coding=utf-8
# 测试utf-8编码
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

import sys,os,linecache
def trace(f):
  def globaltrace(frame, why, arg):
    if why == "call": return localtrace
    return None
  def localtrace(frame=1, why=2, arg=4):
    if why == "line":
      # record the file name and line number of every trace
      filename = frame.f_code.co_filename
      lineno = frame.f_lineno
      bname = os.path.basename(filename)
      print "{}({}): {}".format(  bname,
                    lineno,
                    linecache.getline(filename, lineno)),
    return localtrace
  def _f(*args, **kwds):
    sys.settrace(globaltrace)
    result = f(*args, **kwds)
    sys.settrace(None)
    return result
  return _f

@trace
def xxx():
  a=1
  print a
  print 22
  print 333

xxx() #调用

#######################################
C:\Python27\python.exe F:/sourceDemo/flask/study/com.test.bj/t2.py
t2.py(31):   a=1
t2.py(32):   print a
1
t2.py(33):   print 22
22
t2.py(34):   print 333
333

Process finished with exit code 0

 

 

16.9 单例模式

 

# coding=utf-8
# 测试utf-8编码
# 单例装饰器
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

# 使用装饰器实现简单的单例模式
def singleton(cls):
    instances = dict()  # 初始为空
    def _singleton(*args, **kwargs):
        if cls not in instances:  #如果不存在, 则创建并放入字典
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return _singleton

@singleton
class Test(object):
    pass
if __name__ == '__main__':
    t1 = Test()
    t2 = Test()
    # 两者具有相同的地址
    print t1
    print t2

 

16.10 LRUCache

下面要分享的这个LRUCache不是我做的,是github上的一个库,我们在实际环境中有用到。

先来说下这个概念,cache的意思就是缓存,LRU就是Least Recently Used,即最近最少使用,是一种内存管理算法。总结来说这就是一种缓存方法,基于时间和容量。

一般在简单的python程序中,遇到需要处理缓存的情况时最简单的方式,声明一个全局的dict就能解决(在python中应尽量避免使用全局变量)。但是只是简单情况,这种情况会带来的问题就是内存泄漏,因为可能会出现一直不命中的情况。

由此导致的一个需求就是,你要设定这个dict的最大容量,防止发生泄漏。但仅仅是设定最大容量是不够的,设想当你的dict变量已被占满,还是没有命中,该如何处理。

这时就需要加一个失效时间了。如果在指定失效期内没有使用到该缓存,则删除。

综述上面的需求和功能就是LRUCache干的事了。不过这份代码做了更进一层的封装,可以让你直接把缓存功能做为一个装饰器来用。具体实现可以去参考代码,别人实现之后看起来并不复杂 :)

 

from lru import lru_cache_function

@lru_cache_function(max_size=1024, expiration=15*60)
def f(x):
    print "Calling f(" + str(x) + ")"
    return x

f(3) # This will print "Calling f(3)", will return 3
f(3) # This will not print anything, but will return 3 (unless 15 minutes have passed between the first and second function call).

 

代码: https://github.com/the5fire/Python-LRU-cache/blob/master/lru.py

从python3.2开始内置在functools了lru_cache的功能,说明这个需求是很普遍的。

17. 小结

在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。

decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。

最后留个小作业:

请编写一个decorator,能在函数调用的前后打印出'begin call''end call'的日志。

再思考一下能否写出一个@log的decorator,使它既支持:

 

@log
def f():
    pass

 

又支持:

 

@log('execute')
def f():
    pass

 

 

18. Refer:

[1] 12步轻松搞定python装饰器

http://python.jobbole.com/81683/

[2] 装饰器

liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386819879946007bbf6ad052463ab18034f0254bf355000

[3] Python修饰器的函数式编程

http://coolshell.cn/articles/11265.html

[4] Python Decorator Library

https://wiki.python.org/moin/PythonDecoratorLibrary

[5] Python装饰器实例:调用参数合法性验证

http://python.jobbole.com/82114/

[6] Python 装饰器

http://python.jobbole.com/82344/

[7] 两个实用的Python的装饰器

http://blog.51reboot.com/%E4%B8%A4%E4%B8%AA%E5%AE%9E%E7%94%A8%E7%9A%84python%E7%9A%84%E8%A3%85%E9%A5%B0%E5%99%A8/

[8] Python 中的闭包总结

http://dwz.cn/2CiO78

[9] Python 的闭包和装饰器

https://segmentfault.com/a/1190000004461404

[10] Python修饰器的问题

https://segmentfault.com/q/1010000004595899

 

 

 

#装饰器函数

#装饰器里引入通用功能处理
#1.引入日志 2.函数执行时间统计 3.执行函数前预备处理 4.执行函数后清理功能

#例1:无参数的函数
#decorator.py
from time import ctime, sleep
def timefun(func):
    def wrappedfunc():
        print "%s called at %s"%(func.__name__, ctime())
        return func()
    return wrappedfunc

@timefun
def foo():
    pass

foo()
sleep(2)
foo()

 

#例2:被装饰的函数有参数
#decorator2.py
from time import ctime, sleep
def timefun(func):
    def wrappedfunc(a, b):
        print "%s called at %s"%(func.__name__, ctime())
        print a, b
        return func(a, b)
    return wrappedfunc

@timefun
def foo(a, b):
    print a+b

foo(3,5)
sleep(2)
foo(2,4)

 

#例3:装饰器带参数,在原有装饰器的基础上,设置外部变量
#decorator2.py
from time import ctime, sleep
def timefun_arg(pre="hello"):
    def timefun(func):
        def wrappedfunc():
            print "%s called at %s %s"%(func.__name__, ctime(), pre)
            return func()
        return wrappedfunc
    return timefun

@timefun_arg("12345")
def foo():
    pass

@timefun_arg("abcde")
def too():
    pass


foo()
sleep(2)
foo()


too()
sleep(2)
too()

 

#例4:装饰器和闭包混用
#coding=utf-8
from time import time
def logged(when):
    def log(f, *args, **kargs):
        print "fun:%s args:%r kargs:%r" %(f, args, kargs)
        #%r字符串的同时,显示原有对象类型
        
    def pre_logged(f):
        def wrapper(*args, **kargs):
            log(f, *args, **kargs)
            return f(*args, **kargs)
        return wrapper
    
    def post_logged(f):
        def wrapper(*args, **kargs):
            now=time()
            try:
                return f(*args, **kargs)
            finally:
                log(f, *args, **kargs)
                print "time delta: %s"%(time()-now)
        return wrapper

    try:
        return {"pre":pre_logged, "post":post_logged}[when]
    except KeyError, e:
        raise ValueError(e), 'must be "pre" or "post"'
    
@logged("post")
def fun(name):
    print "Hello, ", name
    
fun("world!")

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

你可能感兴趣的:(Python)