Python 使用decorator管理函数内部的属性

Python 使用decorator管理函数内部的属性,python,metaprogramming,python-decorators,Python,Metaprogramming,Python Decorators,这是我现在正在处理的一个较大代码的玩具示例。假设我有一个函数,我想用一种方式来装饰它,我可以记录有效的、遗留的或禁止的参数: @选项(“1”) @选项(“2”,legacy=True) def do_材料(可选): 打印(f“您选择了{opt}!”) 做些什么(“1”) 做些什么(“2”) 做些什么(“3”) 这样做的结果是 You chose 1! 2 is deprecated! You chose 2! 3 is not allowed! 我现在尝试以不同的方式实现此行为,我最新的尝试

这是我现在正在处理的一个较大代码的玩具示例。假设我有一个函数,我想用一种方式来装饰它,我可以记录有效的、遗留的或禁止的参数:

@选项(“1”)
@选项(“2”,legacy=True)
def do_材料(可选):
打印(f“您选择了{opt}!”)
做些什么(“1”)
做些什么(“2”)
做些什么(“3”)
这样做的结果是

You chose 1!
2 is deprecated!
You chose 2!
3 is not allowed!
我现在尝试以不同的方式实现此行为,我最新的尝试是:

从functools导入包装
def选项(opt,legacy=False):
def添加选项(f,选项):
如果不是hasattr(f,“选项”):
f、 opts=[]
f、 opts.append(选项)
def添加_遗留_选项(f,选项):
如果不是hasattr(f,“遗产”):
f、 遗产=[]
f、 legacy.append(选项)
def装饰(func):
@包装(func)
def包装器(选件):
如果hasattr(wrapper,“opts”)和选项不在wrapper.opts中:
打印(不允许使用f“{option}!”)
返回
如果hasattr(wrapper,“legacy”)和wrapper.legacy中的选项:
打印(f“{option}已弃用!”)
返回函数(选项)
添加选项(包装器,选项)
如果是遗产:
添加_legacy_opt(包装器,opt)
返回包装器
返修
它几乎起作用了。问题是它给了我双重的传统信息:

You chose 1!
2 is deprecated!
2 is deprecated!
You chose 2!
3 is not allowed!
如果有人有任何想法,请告诉我!谢谢:D

更新:以不同的顺序进行装饰:

@选项(“2”,legacy=True)
@选项(“1”)
def do_材料(可选):
打印(f“您选择了{opt}!”)

您已经在包装器周围创建了一个包装器。
wrapps
decorator实际上使用“deeper-down”中的函数属性更新每个包装器,这在这里实际上很关键,但由于每个包装器都保留自己的
.opts
.legacy
(指向相同的列表对象),因此每一层包装器都将
打印(f“{option}不推荐使用!”)
然后
return func(option)
这只是调用嵌套包装器,它经过相同的检查

因此,请注意:

In [1]: from functools import wraps
   ...:
   ...:
   ...: def option(opt, legacy=False):
   ...:     def add_opt(f, option):
   ...:         if not hasattr(f, "opts"):
   ...:             f.opts = []
   ...:         f.opts.append(option)
   ...:
   ...:     def add_legacy_opt(f, option):
   ...:         if not hasattr(f, "legacy"):
   ...:             f.legacy = []
   ...:         f.legacy.append(option)
   ...:
   ...:     def decorate(func):
   ...:         @wraps(func)
   ...:         def wrapper(option):
   ...:             if hasattr(wrapper, "opts") and option not in wrapper.opts:
   ...:                 print(f"{option} is not allowed!")
   ...:                 return
   ...:             if hasattr(wrapper, "legacy") and option in wrapper.legacy:
   ...:                 print(f"{option} is deprecated!")
   ...:             return func(option)
   ...:
   ...:         add_opt(wrapper, opt)
   ...:         if legacy:
   ...:             add_legacy_opt(wrapper, opt)
   ...:
   ...:         return wrapper
   ...:
   ...:     return decorate
   ...:

In [2]: @option("2", legacy=True)
   ...: @option("1")
   ...: def do_stuff(opt):
   ...:     print(f"You chose {opt}!")
   ...:

In [3]: vars(do_stuff)
Out[3]:
{'__wrapped__': <function __main__.do_stuff(opt)>,
 'opts': ['1', '2'],
 'legacy': ['2']}

In [4]: vars(do_stuff.__wrapped__)
Out[4]: {'__wrapped__': <function __main__.do_stuff(opt)>, 'opts': ['1', '2']}
然后,我们可以执行以下操作:

def option(opt, legacy=False):

    def decorate(func):
        if hasattr(func, 'opt') or hasattr(func, 'legacy'): # has been decorated
            wrapper = func # keep old wrapper
        else: # wrap for the first time
            @wraps(func)
            def wrapper(option):
                if hasattr(wrapper, "opts") and option not in wrapper.opts:
                    print(f"{option} is not allowed!")
                    return
                if hasattr(wrapper, "legacy") and option in wrapper.legacy:
                    print(f"{option} is deprecated!")
                return func(option)

        _add_opt(wrapper, opt)
        if legacy:
            _add_legacy_opt(wrapper, opt)

        return wrapper

    return decorate
老实说,这种依赖函数属性的方法似乎非常脆弱。但也许你可以想出一个更稳健的方法,但这至少应该澄清问题并给你一个解决方案

def option(opt, legacy=False):

    def decorate(func):
        if hasattr(func, 'opt') or hasattr(func, 'legacy'): # has been decorated
            wrapper = func # keep old wrapper
        else: # wrap for the first time
            @wraps(func)
            def wrapper(option):
                if hasattr(wrapper, "opts") and option not in wrapper.opts:
                    print(f"{option} is not allowed!")
                    return
                if hasattr(wrapper, "legacy") and option in wrapper.legacy:
                    print(f"{option} is deprecated!")
                return func(option)

        _add_opt(wrapper, opt)
        if legacy:
            _add_legacy_opt(wrapper, opt)

        return wrapper

    return decorate