与普遍认为的相反,@decorator 和 decorator(…) 并不完全相同。第一个运行 before 名称绑定,后者运行 after 名称绑定。对于顶级函数的常见用例,这允许以低成本测试适用哪种情况。
import sys
def decoraware(subject):
"""
Decorator that is aware whether it was applied using `@deco` syntax
"""
try:
module_name, qualname = subject.__module__, subject.__qualname__
except AttributeError:
raise TypeError(f"subject must define '__module__' and '__qualname__' to find it")
if '.' in qualname:
raise ValueError(f"subject must be a top-level function/class")
# see whether ``subject`` has been bound to its module
module = sys.modules[module_name]
if getattr(module, qualname, None) is not subject:
print('@decorating', qualname) # @decoraware
else:
print('wrapping()', qualname) # decoraware()
return subject
这个例子只会打印它是如何应用的。
>>> @decoraware
... def foo(): ...
...
@decorating foo
>>> decoraware(foo)
wrapping() foo
不过,可以使用相同的方法在每个路径中运行任意代码。
如果应用了多个装饰器,您必须决定是要顶部还是底部主题。对于顶级功能,代码无需修改即可工作。对于底部主题,在检测前使用subject = inspect.unwrap(subject) 解包。
可以在 CPython 上以更通用的方式使用相同的方法。使用 sys._getframe(n).f_locals 可以访问应用了装饰器的本地命名空间。
def decoraware(subject):
"""Decorator that is aware whether it was applied using `@deco` syntax"""
modname, topname = subject.__module__, subject.__name__
if getattr(sys.modules[modname], topname, None) is subject:
print('wrapping()', topname, '[top-level]')
else:
at_frame = sys._getframe(1)
if at_frame.f_locals.get(topname) is subject:
print('wrapping()', topname, '[locals]')
elif at_frame.f_globals.get(topname) is subject:
print('wrapping()', topname, '[globals]')
else:
print('@decorating', topname)
return subject
请注意,与 pickle 类似,如果主题的 __qualname__/__name__ 被篡改或从其定义的命名空间中被 del'ed,则此方法将失败。