我正在尝试在类方法中构建一个控制结构,该方法将函数作为输入,并且如果函数被修饰或未修饰,则具有不同的行为。关于如何构建一个行为如下的函数
is_decorated
的任何想法:
def dec(fun):
# do decoration
def func(data):
# do stuff
@dec
def func2(data):
# do other stuff
def is_decorated(func):
# return True if func has decorator, otherwise False
is_decorated(func) # False
is_decorated(func2) # True
是的,这相对容易,因为函数可以添加任意属性,因此装饰器函数在执行其操作时可以添加一个:
def dec(fun):
def wrapped(*args, **kwargs):
pass
wrapped.i_am_wrapped = True
return wrapped
def func(data):
... # do stuff
@dec
def func2(data):
... # do other stuff
def is_decorated(func):
return getattr(func, 'i_am_wrapped', False)
print(is_decorated(func)) # -> False
print(is_decorated(func2)) # -> True
有多种方法可以识别函数是否被包装/装饰。
TLDR 解决方案
def is_decorated(func):
return hasattr(func, '__wrapped__') or func.__name__ not in globals()
场景A
如果我们假设装饰器使用辅助装饰器
functools.wraps
,那么它就非常简单了,因为我们的装饰函数之后将具有属性__wrapped__
。
from functools import wraps
def decorator(function):
@wraps(function)
def _wrapper(*a, **kw): ...
return _wrapper
@decorator
def foo(x, y): ...
def is_decorated(function):
return hasattr(function, '__wrapped__')
场景B
如果我们在装饰器定义期间不使用
wraps
,那么情况会有点不同。
def decorator(function):
def _wrapper(*a, **kw): ...
return _wrapper
@decorator
def foo(x, y): ...
def bar(x, y): ...
print(bar.__name__) # prints 'bar'
print(foo.__name__) # prints '_wrapper' instead of 'foo'
def is_decorated(function):
"""
globals() returns a dictionary which includes
defined functions, classes and many other stuff.
Assuming we haven't defined the inner/wrapper name
as an outer function elsewhere, then it will not be
in globals()
"""
return function.__name__ not in globals()
结论
还有其他更复杂的方法来检查这一点,但是可读性与完整性一样重要,而且这个解决方案非常简单。正如前面的代码块中提到的,该解决方案的唯一“漏洞”是以下情况:
from functools import wraps
def is_decorated(func):
return hasattr(func, '__wrapped__') or func.__name__ not in globals()
def decorator_wraps(function):
@wraps(function)
def _wrapper(*a, **kw): ...
return _wrapper
def decorator_no_wraps(function):
def _wrapper(*a, **kw): ...
return _wrapper
@decorator_wraps
def foo(x, y): ...
@decorator_no_wraps
def bar(x, y): ...
def baz(x, y): ...
for function in (foo, bar, baz):
print(is_decorated(function)) # True, True, False
def _wrapper(): ...
for function in (foo, bar, baz):
print(is_decorated(function)) # True, False, False
如果尝试基于类的装饰器会怎么样?使用
__call__
方法使类成为可调用的类,就像它是一个函数一样。在这里,我们使用 Decorator
类及其后代 dec
并查找该函数是否被装饰或使用了某些特定的装饰器。
class Decorator:
pass
class dec(Decorator):
def __init__(self, func):
self.wrapped_func = func
def __call__(self, *args, **kwargs):
return self.wrapped_func(*args, **kwargs)
def func(data):
pass
@dec
def func2(data):
pass
def is_decorated(func):
return isinstance(func, Decorator)
def is_dec_decorated(func):
return isinstance(func, dec)
assert is_decorated(func) is False
assert is_decorated(func2) is True
assert is_dec_decorated(func2) is True
一般来说,你不能。留下痕迹的装饰者并没有什么特别的。根据出色的 Python 装饰器入门 教程,“装饰器提供了一种用于调用 高阶函数 的简单语法。”
教程中的一个快速说明性示例:
def do_twice(func):
def wrapper_do_twice():
func()
func()
return wrapper_do_twice
from decorators import do_twice
@do_twice
def say_whee():
print("Whee!")
您看到
wrapper_do_twice
内部的do_twice
实际上只是调用传递的函数两次吗?它绝不会修改函数,因此不可能知道(没有 inspection,不要这样做)函数已被修饰。
但是,如果您正在编写自己的自己的装饰器,或者知道您可以利用您正在使用的装饰器正在修改被装饰的函数(与上面的示例相反,上面的示例不修改函数),然后你可以直接检查是否留下了任何标记。有关示例,请参阅this问题。