zoukankan      html  css  js  c++  java
  • 『Python』装饰器

    一、参考

    作者:zhijun liu
    链接:https://www.zhihu.com/question/26930016/answer/99243411
    来源:知乎

    建议大家去原答案浏览

    二、装饰器作用

    内裤可以用来遮羞,但是到了冬天它没法为我们防风御寒,聪明的人们发明了长裤,有了长裤后宝宝再也不冷了,装饰器就像我们这里说的长裤,在不影响内裤作用的前提下,给我们的身子提供了保暖的功效。

    再回到我们的主题

    装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

    1、装饰器应用场景

    def foo():
        print('i am foo')
    

     现在有一个新的需求,希望可以记录下函数的执行日志,于是在代码中添加日志代码:

    def foo():
        print('i am foo')
        logging.info("foo is running")
    

     bar()、bar2()也有类似的需求,怎么做?再写一个logging在bar函数里?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个函数:专门处理日志 ,日志处理完之后再执行真正的业务代码

    def use_logging(func):
        logging.warn("%s is running" % func.__name__)
        func()
    
    def bar():
        print('i am bar')
    
    use_logging(bar)
    

    逻辑上不难理解, 但是这样的话,我们每次都要将一个函数作为参数传递给use_logging函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行bar(),但是现在不得不改成use_logging(bar)。那么有没有更好的方式的呢?当然有,答案就是装饰器。

    def use_logging(func):
    
        def wrapper(*args, **kwargs):
            logging.warn("%s is running" % func.__name__)
            return func(*args, **kwargs)
        return wrapper
    
    def bar():
        print('i am bar')
    
    bar = use_logging(bar)
    bar()
    

     函数use_logging就是装饰器,它把执行真正业务方法的func包裹在函数里面,看起来像bar被use_logging装饰了。在这个例子中,函数进入和退出时 ,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。
    @符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作

    def use_logging(func):  # <-----接收函数对象
    
        def wrapper(*args, **kwargs):  # <-----接收函数参数
            logging.warn("%s is running" % func.__name__)
            return func(*args)  # <-----返回函数(参数)的执行结果
        return wrapper  # 返回内置函数对象
    
    @use_logging  # 效果就是foo = use_logging(foo)
    def foo():
        print("i am foo")
    
    @use_logging
    def bar():
        print("i am bar")
    
    bar()
    

    如上所示,这样我们就可以省去bar = use_logging(bar)这一句了,直接调用bar()即可得到想要的结果。如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。

    装饰器在Python使用如此方便都要归因于Python的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。

    2、带参数的装饰器

    装饰器还有更大的灵活性,例如带参数的装饰器:在上面的装饰器调用中,比如@use_logging,该装饰器唯一的参数就是执行业务的函数。装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(a)。这样,就为装饰器的编写和使用提供了更大的灵活性。

    def use_logging(level):  # <-----封装了装饰器对象,接收全局变量
        def decorator(func):
            def wrapper(*args, **kwargs):
                if level == "warn":
                    logging.warn("%s is running" % func.__name__)
                return func(*args)
            return wrapper
        return decorator  # <-----返回装饰器对象
    
    @use_logging(level="warn")  # 返回了一个带上下文的装饰器对象decorator,即一个含有全局变量level="warn"的decorator
    def foo(name='foo'):
        print("i am %s" % name)
    
    foo()
    

     上面的use_logging是允许带参数的装饰器。

    它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。我们可以将它理解为一个含有参数的闭包。

    当我 们使用@use_logging(level=”warn”)调用的时候,Python能够发现这一层的封装,并把参数传递到装饰器的环境中。

    3、类装饰器

    再来看看类装饰器,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

    class Foo(object):
        def __init__(self, func):  # <-----初始化时接收函数对象
            self._func = func
    
        def __call__(self):
            print('class decorator runing')
            self._func()
            print('class decorator ending')
    
    
    @Foo  # <-----检测装饰器为class时,会使用函数对象初始化类并,调用__call__方法
    def bar():
        print('bar')
    
    bar()
    

     三、装饰器和 functools.wraps

    使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的__doc____name__、参数列表,先看例子:

    def logged(func):
        def with_logging(*args, **kwargs):
            print func.__name__ + " was called"
            return func(*args, **kwargs)
        return with_logging
    
    @logged
    def f(x):
       """does some math"""
       return x + x * x
    

     该函数等价于:

    def f(x):
        """does some math"""
        return x + x * x
    f = logged(f)
    

     此时函数f被with_logging取代了,当然它的文档等信息就变成了with_logging函数的信息了。

    print(f.__name__)    # prints 'with_logging'
    print(f.__doc__)     # prints None
    

    这个问题就比较严重的,好在我们有functools.wraps,wraps本身也是一个装饰器,它能把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数也有和原函数一样的元信息了。

    from functools import wraps
    def logged(func):
        @wraps(func)  # <-----装饰器内部使用,修饰内置函数,需要参数为目标函数对象
        def with_logging(*args, **kwargs):
            print func.__name__ + " was called"
            return func(*args, **kwargs)
        return with_logging
    
    @logged
    def f(x):
        """does some math"""
        return x + x * x
    
    print(f.__name__)  # prints 'f'
    print(f.__doc__ )  # prints 'does some math'
    

     四、其他

    1、内置装饰器

    @staticmathod、@classmethod、@property

    2、多个装饰器的执行顺序

    @a
    @b
    @c
    def f ():

     等效于:f = a(b(c(f)))。

  • 相关阅读:
    HDU2586 How far away?(tarjan的LCA)
    You Raise Me Up
    POJ2891 Strange Way to Express Integers(中国剩余定理)
    POJ2142 The Balance(扩展欧几里得)
    HDU 1166模仿大牛写的线段树
    NetWord Dinic
    HDU 1754 线段树裸题
    hdu1394 Minimum Inversion Number
    hdu2795 Billboard
    【完全版】线段树
  • 原文地址:https://www.cnblogs.com/hellcat/p/8633102.html
Copyright © 2011-2022 走看看