如何解决使用装饰器管理函数内部的属性
这是一个我正在处理的较大代码的玩具示例。假设我有一个函数,我想以一种可以记录有效、遗留或禁止的参数的方式装饰它:
@option("1")
@option("2",legacy=True)
def do_stuff(opt):
print(f"You chose {opt}!")
do_stuff("1")
do_stuff("2")
do_stuff("3")
这样的结果将类似于
You chose 1!
2 is deprecated!
You chose 2!
3 is not allowed!
我现在尝试以一些不同的方式实现这种行为,我最近的尝试是这样的:
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,"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
它几乎有效。问题是它给了我双重的遗留信息:
You chose 1!
2 is deprecated!
2 is deprecated!
You chose 2!
3 is not allowed!
如果有人有任何想法,请告诉我!谢谢:D
更新:以不同的顺序装饰作品:
@option("2",legacy=True)
@option("1")
def do_stuff(opt):
print(f"You chose {opt}!")
解决方法
您已经在包装器周围创建了一个包装器。 error: invalid use of incomplete type 'class xxx'
装饰器实际上用“更深”的函数属性更新每个包装器,这在这里实际上很关键,但由于每个包装器都保留自己的 wraps
和 .opts
(指向相同的列出对象),所以每一层包装器将 .legacy
然后 print(f"{option} is deprecated!")
这只是调用嵌套包装器,它通过相同的检查。
所以观察:
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,"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)>,'2']}
为真的情况下,它只是 hasattr(wrapper,"opts") and option not in wrapper.opts
而不调用 return
。 (除了设计之外,这几乎肯定会引发值错误)。
以相反的顺序进行装饰“有效”,因为您最后添加了 func
,因此只有最外层的包装器具有 legacy
属性。
一种解决方案是以某种方式识别已经被装饰的函数,它们已经被包装,而不是创建另一个包装器,只需更新包装器上的必要属性并返回原始包装器。
首先,让我们将 .legacy
和 add_opt
移到外面以消除装饰器内部的混乱,没有充分的理由将它们定义在内部:
add_legacy
然后我们可以执行以下操作:
from functools import wraps
def _add_opt(f,option):
if not hasattr(f,"opts"):
f.opts = []
f.opts.append(option)
def _add_legacy_opt(f,"legacy"):
f.legacy = []
f.legacy.append(option)
老实说,这种依赖函数属性的方法似乎很脆弱。但也许你可以想出一个更强大的方法,但这至少应该澄清问题并为你提供解决方案。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。