• Python 内置装饰器


    内置的装饰器

    ​ 内置的装饰器和普通的装饰器原理是一样的,只不过返回的不是函数,而是类对象,所以更难理解一些。

    @property

    ​ 在了解这个装饰器前,你需要知道在不使用装饰器怎么写一个属性。

    def getWidth(self):
    	return self.__width
    
    def setWidth(self, newwidth):
    	self.__width = newwidth
    
    width = property(getWidth, setWidth)
    

    ​ 以上就是一个Python属性的标准写法,其实和Java挺像的,但是太罗嗦。有了@语法糖,能达到一样的效果但看起来更简单。

    @property
    def width(self):
        return self.__width
    
    @width.setter
    def width(self, newWidth):
    	self.__width = newWidth
    

    ​ 属性有三个装饰器:setter, getter, deleter ,都是在property()的基础上做了一些封装,因为setterdeleterproperty()的第二和第三个参数,不能直接套用@语法。getter装饰器和不带getter的属性装饰器效果是一样的,估计只是为了对称吧,本身没有任何存在的意义。经过@property装饰过的函数返回的不再是一个函数,而是一个property对象。

    >>> property()
    <property object at 0x10ff07940>
    

    @staticmethod,@classmethod

    ​ 有了@property装饰器的了解,这两个装饰器的原理是差不多的。@staticmethod返回的是一个staticmethod类对象,而@classmethod返回的是一个classmethod类对象。他们都是调用的是各自的__init__()构造函数。

    class classmethod(object):
        """
        classmethod(function) -> method
        """    
        def __init__(self, function): # for @classmethod decorator
            pass
        # ...
    class staticmethod(object):
        """
        staticmethod(function) -> method
        """
        def __init__(self, function): # for @staticmethod decorator
            pass
        # ...
    

    装饰器的@语法就等同调用了这两个类的构造函数

    class Foo(object):
    
        @staticmethod
        def bar():
            pass
        
        # 等同于 bar = staticmethod(bar
    

    装饰器里的缺点

    ​ 装饰器可以让你代码更加优雅,减少重复,但也不全是优点,也会带来一些问题。

    位置错误的代码

    ​ 让我们直接看示例代码。

    def html_tags(tag_name):
        print 'begin outer function.'
        def wrapper_(func):
            print "begin of inner wrapper function."
            def wrapper(*args, **kwargs):
                content = func(*args, **kwargs)
                print "<{tag}>{content}</{tag}>".format(tag=tag_name, content=content)
            print 'end of inner wrapper function.'
            return wrapper
        print 'end of outer function'
        return wrapper_
    
    @html_tags('b')
    def hello(name='Toby'):
        return 'Hello {}!'.format(name)
    
    hello()
    hello()
    

    ​ 在装饰器中我在各个可能的位置都加上了print语句,用于记录被调用的情况。你知道他们最后打印出来的顺序吗?如果你心里没底,那么最好不要在装饰器函数之外添加逻辑功能,否则这个装饰器就不受你控制了。以下是输出结果:

    begin outer function.
    end of outer function
    begin of inner wrapper function.
    end of inner wrapper function.
    <b>Hello Toby!</b>
    <b>Hello Toby!</b>
    

    错误的函数签名和文档

    ​ 装饰器装饰过的函数看上去名字没变,其实已经变了。

    def logging(func):
        def wrapper(*args, **kwargs):
            """print log before a function."""
            print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
            return func(*args, **kwargs)
        return wrapper
    
    @logging
    def say(something):
        """say something"""
        print "say {}!".format(something)
    
    print say.__name__  # wrapper
    

    ​ 为什么会这样呢?只要你想想装饰器的语法糖@代替的东西就明白了。@等同于这样的写法。

    say = logging(say)
    

    logging其实返回的函数名字刚好是wrapper,那么上面的这个语句刚好就是把这个结果赋值给saysay__name__自然也就是wrapper了,不仅仅是name,其他属性也都是来自wrapper,比如docsource等等。

    使用标准库里的functools.wraps,可以基本解决这个问题。

    from functools import wraps
    
    def logging(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            """print log before a function."""
            print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
            return func(*args, **kwargs)
        return wrapper
    
    @logging
    def say(something):
        """say something"""
        print "say {}!".format(something)
    
    print say.__name__  # say
    print say.__doc__ # say something
    

    看上去不错!主要问题解决了,但其实还不太完美。因为函数的签名和源码还是拿不到的。

    import inspect
    print inspect.getargspec(say)  # failed
    print inspect.getsource(say)  # failed
    

    如果要彻底解决这个问题可以借用第三方包,比如wrapt

    不能装饰@staticmethod或者 @classmethod

    当你想把装饰器用在一个静态方法或者类方法时,不好意思,报错了。

    class Car(object):
        def __init__(self, model):
            self.model = model
    
        @logging  # 装饰实例方法,OK
        def run(self):
            print "{} is running!".format(self.model)
    
        @logging  # 装饰静态方法,Failed
        @staticmethod
        def check_model_for(obj):
            if isinstance(obj, Car):
                print "The model of your car is {}".format(obj.model)
            else:
                print "{} is not a car!".format(obj)
    
    """
    Traceback (most recent call last):
    ...
      File "example_4.py", line 10, in logging
        @wraps(func)
      File "C:Python27libfunctools.py", line 33, in update_wrapper
        setattr(wrapper, attr, getattr(wrapped, attr))
    AttributeError: 'staticmethod' object has no attribute '__module__'
    """
    

    ​ 前面已经解释了@staticmethod这个装饰器,其实它返回的并不是一个callable对象,而是一个staticmethod对象,那么它是不符合装饰器要求的(比如传入一个callable对象),你自然不能在它之上再加别的装饰器。要解决这个问题很简单,只要把你的装饰器放在@staticmethod之前就好了,因为你的装饰器返回的还是一个正常的函数,然后再加上一个@staticmethod是不会出问题的。

    class Car(object):
        def __init__(self, model):
            self.model = model
    
        @staticmethod
        @logging  # 在@staticmethod之前装饰,OK
        def check_model_for(obj):
            pass
    

    如何优化你的装饰器

    ​ 嵌套的装饰函数不太直观,我们可以使用第三方包类改进这样的情况,让装饰器函数可读性更好。

    decorator.py

    decorator.py是一个非常简单的装饰器加强包。你可以很直观的先定义包装函数wrapper(),再使用decorate(func, wrapper)方法就可以完成一个装饰器。

    from decorator import decorate
    
    def wrapper(func, *args, **kwargs):
        """print log before a function."""
        print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
        return func(*args, **kwargs)
    
    def logging(func):
        return decorate(func, wrapper)  # 用wrapper装饰func
    

    你也可以使用它自带的@decorator装饰器来完成你的装饰器。

    from decorator import decorator
    
    @decorator
    def logging(func, *args, **kwargs):
        print "[DEBUG] {}: enter {}()".format(datetime.now(), func.__name__)
        return func(*args, **kwargs)
    

    decorator.py实现的装饰器能完整保留原函数的namedocargs,唯一有问题的就是inspect.getsource(func)返回的还是装饰器的源代码,你需要改成inspect.getsource(func.__wrapped__)

    wrapt

    ​ wrapt是一个功能非常完善的包,用于实现各种你想到或者你没想到的装饰器。使用wrapt实现的装饰器你不需要担心之前inspect中遇到的所有问题,因为它都帮你处理了,甚至inspect.getsource(func)也准确无误。

    import wrapt
    
    # without argument in decorator
    @wrapt.decorator
    def logging(wrapped, instance, args, kwargs):  # instance is must
        print "[DEBUG]: enter {}()".format(wrapped.__name__)
        return wrapped(*args, **kwargs)
    
    @logging
    def say(something): pass
    

    ​ 使用wrapt你只需要定义一个装饰器函数,但是函数签名是固定的,必须是(wrapped, instance, args, kwargs),注意第二个参数instance是必须的,就算你不用它。当装饰器装饰在不同位置时它将得到不同的值,比如装饰在类实例方法时你可以拿到这个类实例。根据instance的值你能够更加灵活的调整你的装饰器。另外,argskwargs也是固定的,注意前面没有星号。在装饰器内部调用原函数时才带星号。

    如果你需要使用wrapt写一个带参数的装饰器,可以这样写。

    def logging(level):
        @wrapt.decorator
        def wrapper(wrapped, instance, args, kwargs):
            print "[{}]: enter {}()".format(level, wrapped.__name__)
            return wrapped(*args, **kwargs)
        return wrapper
    
    @logging(level="INFO")
    def do(work): pass
    
  • 相关阅读:
    GDI绘图1——自定义函数
    GDI绘图1——枚举
    打箱子&地形&鼠标点击移动总结
    使用Playmaker1——基础使用
    图形用户编程工具集比较--转
    Java中抽象类和接口的区别
    myeclipse安装 配置Maven3
    在Ubuntu上安装hadoop2.2.0的一些记录
    Hadoop2.2.0安装配置手册!完全分布式Hadoop集群搭建过程
    获得hadoop源码网址
  • 原文地址:https://www.cnblogs.com/yangliguo/p/8178113.html
Copyright © 2020-2023  润新知