Python 装饰器(Decorator)

引入

如果你学过Java的UML设计模式,那么你一定对Decorator Pattern和你熟悉,Decorator Pattern即装饰器模式(也译修饰器模式),是著名的四人帮(Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. 设计模式:可复用面向对象软件的基础. 北京: 机械工业出版社)书中介绍的23种设计模式之一。

In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class.[1] The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern.[2] The decorator pattern is structurally identical to the chain of responsibility pattern, the difference being that in a chain of responsibility, exactly one of the classes handles the request, while for the decorator, all classes handle the request.

提示:在面向对象的概念中,行为是抽象的说法,体现在代码上的就是方法(函数)。
简述:在面向对象编程中,装饰器模式是一种允许动态或者静态向一个对象添加新的行为而不会影响到其他对象的一种设计模式。

Python 装饰器(Decorator)

Python includes a more natural way of decorating a function by using an annotation on the function that is decorated.
https://en.wikipedia.org/wiki/Decorator_pattern
译:Python提供了一种更为自然的方式来装饰一个函数,这种方法通过在被装饰的函数前加一个注释来时实现。

Python提供了装饰器(Decorator)来更为简便的实现Java的装饰模式(Decorator Pattern),其不需要在类的层面就可以实现对一个函数进行装饰,你可以将装饰理解为添加新的功能。
装饰器充分体现了Python的简洁性,因为在Java是一种设计模式,而在Python这里就只是简单的一句注释。

#下面是来自维基百科的一个例子
def benchmark(func):	#定义了一个装饰器 
    """
    Print the seconds that a function takes to execute.
    """
    from time import time
    def wrapper(*args, **kwargs):
        t0 = time()
        res = func(*args, **kwargs)
        print("function @{0} took {1:0.3f} seconds".format(func.__name__, time() - t0))
        return res
    return wrapper
    
@benchmark				#声明使用装饰器
def wait_some_seconds(num_seconds = 1):
    from time import sleep
    sleep(num_seconds)

wait_some_seconds(1.11)
# function @wait_some_seconds took 1.11 seconds
wait_some_seconds(5)
# function @wait_some_seconds took 5.000 seconds
#结果
function @wait_some_seconds took 1.111 seconds
function @wait_some_seconds took 5.001 seconds

如何来理解:

一个装饰器就是一个以被装饰函数为参数,以wrapper函数为闭包,返回值为wrapper函数的一个函数,其特点是wrapper采用了(*args, **kwargs)即万用参数表作为参数,可以接收任何类型的参数,使用时在被装饰函数前加上 @装饰器名作为使用装饰器的声明。

 

被装饰的函数依然采用自己的函数名进行调用,但是一旦一个函数前有@装饰器名这个注释,就表示其添加了装饰器中wrapper函数所具有的功能,相当于装饰器函数调用了被装饰的函数,将被装饰函数作为参数传进去,所以装饰器函数可以使用被装饰函数的功能,同时又可以使用自身的功能,从而实现装饰效果。

#我们在上面的代码中加入这一条语句:__name__是python所有函数都自动封装的一个对象的属性 即函数的名字
print(wait_some_seconds.__name__)
#结果
wrapper

#这个结果也说明了Python中装饰器的实现正如上面作者个人的理解

但是这并不是我们所期望的,因为所有被装饰的函数的名字都是wrapper,我们只是想给函数添加新功能,而不想做多余的更改。
Python中封装了functools.wraps()这个方法来实现对被装饰函数的__name__属性进行重命名,它其实也是一个装饰器,这个装饰器包含的功能是将被装饰函数的名字赋值给装饰器的wrapper的__name__属性,从而实现了被装饰函数名字的正常显示,虽然这种正常显示是人为进行更改的。

#完全体装饰器 对维基上的Pyhton装饰器例子进行了更改
import functools             #@functools.wraps()方法位置
def benchmark(func):	     #定义了一个装饰器 
    """
    Print the seconds that a function takes to execute.
    """
    from time import time
    @functools.wraps(func)   #声明使用functools.wraps()装饰器
    def wrapper(*args, **kwargs):
        t0 = time()
        res = func(*args, **kwargs)
        print("function @{0} took {1:0.3f} seconds".format(func.__name__, time() - t0))
        return res
    return wrapper
    
@benchmark				#声明使用装饰器
def wait_some_seconds(num_seconds = 1):
    from time import sleep
    sleep(num_seconds)

wait_some_seconds(1.11)
# function @wait_some_seconds took 1.11 seconds
wait_some_seconds(5)
# function @wait_some_seconds took 5.000 seconds
print(wait_some_seconds.__name__)

#结果
function @wait_some_seconds took 1.111 seconds
function @wait_some_seconds took 5.000 seconds
wait_some_seconds
装饰器也可以自带参数,为了把这个参数传进去,我们需要在再加一层函数嵌套,把该参数错作为最外层函数的参数。
import functools

def sample(msg):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s函数名:%s():' % (msg, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator
 
@sample('this is a sample:\n')
def hello():
	print('Hello, World')

hello()

#结果
this is a sample:    #换行得以正确实现是因为%s格式化输出字符串 正确识别了换行符
函数名:hello():
Hello, World

参考资料(这篇廖神其实写的有点难以理解):

廖雪峰的Python教程-装饰器

posted @ 2017-12-19 19:42  从流域到海域  阅读(114)  评论(0编辑  收藏  举报