# Python装饰器

By [kurass.eth](https://paragraph.com/@kurass) · 2023-02-02

---

如果我们一些函数内部有大量的与业务无关且重复的代码，那么就可以将这些代码抽出形成一个装饰器来使用。

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

装饰器如何写
------

### 普通装饰器

使用@语法糖

    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)
    

装饰器中wrapper指定\* args, \*\*kwargs参数，就可以接受任意被装饰函数的参数了。

### 高级点的装饰器

#### 带参数的装饰器

编写带参数的装饰器，需要在普通装饰器上再封装一层。

    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)
    

logging(level=‘INFO’)在被打在某个函数上时会立即执行，返回一个装饰器。这其实就是闭包，返回的装饰器里已经通过参数指定了level了。

#### 类装饰器

装饰器的接口约束：必须接受一个callable对象（一般callable对象都是函数），然后返回一个callable对象。除函数外，若某个对象重载了\__call_\_()方法，那么这个对象就是callable的。

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

**call\_\_这种前后带下划线的方法叫内置方法。重载这些方法会改变对象一些内部行为。像上面的例子中就让一个类对象能够被调用。 这样我们就可以实现类的装饰器了。我们让类的构造函数\_\_init**()j接受一个函数，然后重载\__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)
    

#### 带参数的类装饰器

带参数的类装饰器就要复杂一些，但我们只需要遵守装饰器的接口约束就能很容易自己写出带参数的类装饰器。 类装饰器的构造函数的参数要改为装饰器的参数，然后\__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)
    

#### 内置装饰器

@property @staticmethod @classmethod

#### 一些坑

*   装饰过的函数函数签名\__name_\_和文档\__doc_\_已经改变。
    
*   不能装饰静态方法@staticmethod和类方法@classmethod，因为@staticmethod和@classmethod返回的补上callable对象，而是staticmethod和classmethod对象
    

#### 优化装饰器

*   [decorator.py](http://decorator.py)
    
*   wrapt

---

*Originally published on [kurass.eth](https://paragraph.com/@kurass/python)*
