详解python的装饰器decorator

装饰器本质上是一个python函数,它可以让其它函数在不需要任何代码改动的情况下增加额外的功能。

装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志,性能测试,事务处理,缓存,权限校验等场景。

装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能无关的雷同代码并继续重用。

原文:http://www.cnblogs.com/cicaday/p/python-decorator.html

概括的讲:装饰器的作用就是为已经存在的函数或对象添加额外的功能。

怎么写一个装饰器?

在python2.4以前,为一个函数添加额外功能的写法是:

def debug(func):
    def wrapper():
        print('[DEBUG]: enter {}()'.format(func.__name__))
        return func()
    return wrapper()

def say_hello():
    print('hello')
say_hello = debug(say_hello)
'''
输出:
[DEBUG]: enter say_hello()
hello
'''

上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。

def debug(func):
    def wrapper():
        print('[DEBUG]: enter {}()'.format(func.__name__))
        return func()
    return wrapper()

@debug
def say_hello():
    print('hello')

这是最简单的装饰器,但是有一个问题,如果被装饰的函数需要传入参数,那么这个装饰器就坏了。因为返回的函数并不能接受参数,你可以指定装饰器函数wrapper接受和原函数一样的参数,比如:

def debug(func):
    def wrapper(something):  # 指定一模一样的参数
        print "[DEBUG]: enter {}()".format(func.__name__)
        return func(something)
    return wrapper  # 返回包装过函数

@debug
def say(something):
    print "hello {}!".format(something)

这样你就解决了一个问题,但又多了N个问题。因为函数有千千万,你只管你自己的函数,别人的函数参数是什么样子,鬼知道?还好Python提供了可变参数*args和关键字参数**kwargs,有了这两个参数,装饰器就可以用于任意目标函数了。

def debug(func):
    def wrapper(*args, **kwargs):  # 指定宇宙无敌参数
        print "[DEBUG]: enter {}()".format(func.__name__)
        print 'Prepare and say...',
        return func(*args, **kwargs)
    return wrapper  # 返回

@debug
def say(something):
    print "hello {}!".format(something)

至此,你已完全掌握初级的装饰器写法。

高级一点的装饰器

带参数的装饰器类装饰器属于进阶的内容。在理解这些装饰器之前,最好对函数的闭包和装饰器的接口约定有一定了解。

带参数的装饰器

假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么装饰器就会是这样的。

def logging(level):
    def wrapper(func):
        def inner_wrapper(*args, **kwargs):
            print('[{level}]:enter function {func}()'.format(level=level,func=func.__name__))
            return func(*args, **kwargs)
        return inner_wrapper
    return wrapper

#@logging(level='INFO')
def say(something):
    print('say {}'.format(something))

# 如果没有使用@语法,等同于
# say = logging(level='INFO')(say)

@logging(level='DEBUG')
def do(something):
    print('do {}...'.format(something))

if __name__ == '__main__':
    say('hello')
    do("my work")

基于类实现的装饰器

装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重载了__call__()方法,那么这个对象就是callable的。

 

class Test():
    def __call__(self):
        print 'call me!'

t = Test()
t()  # call me

 

 

__call__这样前后都带下划线的方法在Python中被称为内置方法,有时候也被称为魔法方法。重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。

 

回到装饰器上的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象(不太严谨,详见后文)。那么用类来实现也是也可以的。我们可以让类的构造函数__init__()接受一个函数,然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果。

class logging(object):
    def __init__(self,func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print("[DEBUG]: enter function {func}()".format(func=self.func.__name__))
        return self.func(*args, **kwargs)

@logging
def say(something):
    print('say {}'.format(something))

say('hihao')

带参数的类装饰器

如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接受的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载__call__方法是就需要接受一个函数并返回一个函数。

class logging(object):
    def __init__(self,level='INFO'):
        self.level = level

    def __call__(self, func):
        def wrapper(*args, **kwargs):
            print("[{level}]: enter function {func}()".format(
                level=self.level,
                func=func.__name__))
            func(*args, **kwargs)
        return wrapper

@logging(level='INFO')
def say(something):
    print("say {}!".format(something))

say('nihao')

内置的装饰器

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

@property

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

def getx(self):
    return self._x


def setx(self, value):
    self._x = value


def delx(self):
    del self._x


# create a property
x = property(getx, setx, delx, "I am doc for x property")

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

 

@property
def x(self): ...

# 等同于

def x(self): ...
x = property(x)

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

@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)

 

至此,我们上文提到的装饰器接口定义可以更加明确一些,装饰器必须接受一个callable对象,其实它并不关心你返回什么,可以是另外一个callable对象(大部分情况),也可以是其他类对象,比如property。

 

 

知乎上

12步轻松搞定python装饰器

详解Python装饰器由浅入深

Python实战小程序——装饰器

浅谈Python装饰器

Python 装饰器

 

 

posted @ 2017-10-31 15:12  highly  阅读(163)  评论(0编辑  收藏  举报