custom_decorator

自定义装饰器

1 写一个简单的装饰器

def foo():
    print 'foo'

foo     #表示是函数
foo()   #表示执行 foo 函数

1.1 一个普通的装饰器

装饰器

import functools
def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('call %s():' % func.__name__)
        print('args = {}'.format(*args))
        return func(*args, **kwargs)

    return wrapper

这样就定义了一个打印出方法名及其参数的装饰器。

使用

@log
def test(p):
    print(test.__name__ + " param: " + p)

test("I'm a param")

备注

值得注意的是 @functools.wraps(func),这是 python 提供的装饰器。它能把原函数的元信息拷贝到装饰器里面的 func 函数中。
函数的元信息包括 docstring、name、参数列表等等。

可以尝试去除 @functools.wraps(func),你会发现 test.__name__的输出变成了 wrapper。

1.2 带参数的装饰器

import functools

def log_with_param(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            print('call %s():' % func.__name__)
            print('args = {}'.format(*args))
            print('log_param = {}'.format(text))
            return func(*args, **kwargs)

        return wrapper

    return decorator

@log_with_param("param")
def test_with_param(p):
    print(test_with_param.__name__)

等同于

# 传入装饰器的参数,并接收返回的 decorator 函数
decorator = log_with_param("param")
# 传入 test_with_param 函数
wrapper = decorator(test_with_param)
# 调用装饰器函数
wrapper("I'm a param")

1.3 类装饰器

在 python 中我们可以利用一个函数作为装饰器来装饰另一个函数,但是装饰器只能是函数吗 ? 当然了我们还可以使用类来作为装饰器!

class A(object):
    def __init__(self,func):
        print('定义初始化函数')
        print('func name is %s'%func.__name__)
        self.__func = func

    def __call__(self):
        print('call 方法作为装饰器中的功能')
        self.__func()
        print('增加的功能 2')

@A
def B():
    print('这个是 B 是原函数')

def C():
    print('这个是 C 是原函数')

if __name__ == "__main__":
    # 装饰器
    print "---------------"
    B()

    # 直接在 A 中调用 C
    print "---------------"
    # 等同于 x=A(C);x()
    # 允许一个类的实例像函数一样被调用。实质上说,这意味着 x() 与 x.__call__() 是相同的。
    A(C)()

这个的输出结果是:

 定义初始化函数
func name is B
---------------
call 方法作为装饰器中的功能
这个是 B 是原函数
增加的功能 2
---------------
定义初始化函数
func name is C
call 方法作为装饰器中的功能
这个是 C 是原函数
增加的功能 2

其中 butterfly 中的 handler 都是被 protocol_json.Protocol 装饰

2 装饰器的本质

2.1 本质

装饰器本质就是返回了个新的函数

# 将原函数作为参数传入
def decorator(func):
    def newfunc():
        # 调用之前 do something
        pass
        return func()  # 调用原函数
    # 将新函数返回
    return newfunc


def func(): pass

func = decorator(func)  # 手动的把新函数赋值给旧函数
func()  # newfunc()

变为了

@decorator  # 相当于 func = decorator(func)
def func(): pass

2.2 装饰器加强库

decorator.py 和 wrapt 两个库是装饰器加强库,可以很方便的定义和使用装饰器函数,wrapt 比 decorator.py 功能更强些

使用 wrapt 库可以相当于对返回新函数的这块进行了封装,用户只需要实现 newfunc 的功能即可,即

原方式

def decorator(func):
    def newfunc():
        # 调用之前 do something
        pass
        return func()  # 调用原函数
    # 将新函数返回
    return newfunc

现方式


import wrapt

# without argument in decorator
@wrapt.decorator
def newfunc(wrapped, instance, args, kwargs):  # instance is must
    # 调用之前 do something
    pass
    return wrapped(*args, **kwargs)

3 wrapt

wrapt

使用 wrapt 只需要定义一个装饰器函数,但是函数签名是固定的,必须是 (wrapped, instance, args, kwargs)

注意第二个参数 instance 是必须的,就算你不用它。当装饰器装饰在不同位置时它将得到不同的值,比如装饰在类实例方法时你可以拿到这个类实例。根据 instance 的值能够更加灵活的调整装饰器

另外,args 和 kwargs 也是固定的,注意前面没有星号。在装饰器内部调用原函数时才带星号。

3.1 不带参数的装饰器

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

上面的功能相当于如下功能:

def logging(func):  # 将原函数作为参数传入
    def newfunc(*args, **kwargs):
        print "[DEBUG]: enter"
        return func(*args, **kwargs)  # 调用原函数
    # 将新函数返回
    return newfunc


@logging  # 相当于 say = logging(say)
def say(something): pass

3.2 带参数的装饰器

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

4 handler 装饰器例子

# coding=utf8
"""
# Description:
api demo
"""
import logging

from xlib import retstat
from xlib.util import wrapt
from xlib.util import funcsigs
from xlib.middleware import funcattr
from xlib.httpgateway import Request

__info = "demo"
__version = "1.0.1"
logger = logging.getLogger("service")

def requested(method):
    """
    装饰器:限制请求方法
    """
    @wrapt.decorator
    def wrapper(wrapped, instance, args, kwargs):
        """
        Args:
            - wrapped:被装饰的函数或类方法
            - instance:
                - 如果被装饰者为普通类方法,该值为类实例
                - 如果被装饰者为 classmethod 类方法,该值为类
                - 如果被装饰者为类/函数/静态方法,该值为 None
            - args:调用时的位置参数(注意没有 * 符号)
            - kwargs:调用时的关键字参数(注意没有 ** 符号)
        """
        # (1) 提取原 func 中的 req, 不论使用位置参数还是关键字参数
        sig = funcsigs.signature(wrapped)
        # 将位置参数 args 转为 dict kargs
        kargs = dict(zip([k for k in sig.parameters if k not in kwargs], args))
        req = kargs.get("req", kwargs.get("req"))

        if req.wsgienv.get("REQUEST_METHOD") != method:
            req.error_str = "method not support"
            return retstat.ERR, {}, [(__info, __version)]

        return wrapped(*args, **kwargs)
    return wrapper


@funcattr.api
@requested("GET")
def hello(req, str_info):
    """
    demo
    """
    isinstance(req, Request)
    logger.info("str_info={str_info}".format(str_info=str_info))
    return retstat.OK, {"str_info": str_info}, [(__info, __version)]

5 传送门

Last updated