分享

Python 如何将多个装饰器合并成一个装饰器

 文炳春秋 2020-03-13

WHY

为什么要合并多个装饰器?

因为堆叠装饰器虽然说不是不行,但是随着项目越来越大,装饰器越堆越多,我们希望将一些关联性强的装饰器合并在一起,而不是像下面的代码一样,搞得像叠罗汉一样

恶心例子:

# 公司业务代码其中一个接口 【flask-restplus】
class Goods(Resource)
    @staticmethod
    @document.request_goods_payload
    @document.response_goods_id_success
    @document.response_goods_time_format_error
    @document.response_goods_name_format_error
    @document.response_goods_weight_format_error
    @document.response_goods_volume_format_error
    @document.response_goods_expect_price_format_error
    @document.response_goods_description_format_error
    @document.response_goods_same_address_error
    @document.response_goods_require_vehicle_error
    @document.response_add_goods_duplicate
    @document.response_unauth_restriction
    @document.response_auth_restriction
    @catch_exception_log()
    @filters.Goods.return_goods_id(goods_id=int, is_dispatch=int, is_prepaid=int)
    @dispatchs.Goods.start(goods_id=int, is_dispatch=int, is_prepaid=int)
    @operations.Goods.add_goods(user_id=int, payload=dict, app_version=str)
    @config_operation.ConfigOperation.get_feed_config(user_id=int, payload=dict, app_version=str)
    @operations.Goods.check_duplicate_goods(user_id=int, payload=dict, app_version=str)
    @operations.Goods.check_add_goods_restriction(user_id=int, payload=dict, app_version=str)
    @verifies.Goods.check_goods_payload_fields(user_id=int, app_version=str, payload=dict, code=str, channel=str)
    @verifies.Token.token(token=str, app_version=str, payload=dict, code=str, channel=str)
    def post():
        """ 预约回头车【需登录】 """
        pass

HOW

解决叠罗汉得方法有三种

  • 方法一:通过参数的里的元组(包含很多个装饰器)
  • 方法二:用指定的多个装饰器将调用链写好先,缺点是每次要加多一个装饰器调用,需要改源代码
  • 方法三:先通过函数调用获得一个装饰器函数先,然后再装饰目标函数

代码如下:

import functools


def deco1(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("deco1")
        return func(*args, **kwargs)
    return wrapper


def deco2(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("deco2")
        return func(*args, **kwargs)
    return wrapper


# 方法一:通过参数的里的元组(包含很多个装饰器)
def composed(*decs, is_reversed=False):
    def deco(f):
        if is_reversed:
            for dec in reversed(decs):
                f = dec(f)
        else:
            for dec in decs:
                f = dec(f)
        return f
    return deco


# 方法二:用指定的多个装饰器将调用链写好先,缺点是每次要加多一个装饰器调用,需要改源代码
def mutiple_deco(func):
    return deco2(deco1(func))

# 方法三:先通过函数调用获得一个装饰器函数先,然后再装饰目标函数
def generate_deco(f, g):
    return lambda x: f(g(x))


# 原来的装饰器装饰方式
@deco2
@deco1
def add(a: int, b: int) -> int:
    return a + b


@composed(deco1, deco2, is_reversed=False)
def add_method1(a: int, b: int) -> int:
    return a + b


@mutiple_deco
def add_method2(a: int, b: int) -> int:
    return a + b


combined_deco = generate_deco(deco2, deco1)

@combined_deco
def add_method3(a: int, b: int) -> int:
    return a + b


if __name__ == "__main__":
    r = add(10, 1)
    print("结果是:", r)

    r = add_method1(10, 2)
    print("结果是:", r)

    r = add_method2(10, 3)
    print("结果是:", r)

    r = add_method3(10, 4)
    print("结果是:", r)

RESULT

经过权衡考虑,我选择方法一,这个方法更可控,并且可以自己调整装饰器的装饰顺序

最终的效果如下:

# 【flask-restplus】
class ApiClass(Resource)
    @staticmethod
    @multi_deco(DOC1, DOC2...)
    @multi_deco(FILTER1, FILTER2...)
    @multi_deco(DISPATCH1, DISPATCH2...)
    @multi_deco(OPERATION1, OPERATION2...)
    @multi_deco(VERIFY1, VERIFY2...)
    def post():
        """ 接口文档 """
        pass

THE END

​ 装饰器合并是一个很好玩的东西,可以将以往无限堆装饰器的方式做的更优雅,一个装饰器调用将相关功能的装饰器放在一起,就很舒服,很爽,天天写代码,也得考虑下优化的方法。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多