我是 Python 的新手,刚刚开始学习装饰器。我试图在代码运行时从装饰器本身访问一个变量,以便对来自装饰器的变量执行验证。
出于保密原因,我无法发布代码,但下面的代码解释了我想要实现的目标。 (显然代码不起作用)
def validation_func(func):
@wraps(func)
def wrapper(*args, **kwargs):
func(*args, **kwargs)
if wrapper.answer == 1337:
print ('1337')
else:
print('Hi')
return wrapper
@validation_func
def math_add():
answer = 1336 + 1
我能否从我修饰的函数 (math_add) 中访问变量“answer”并以类似于代码示例的方式执行验证?还是不可能这样做,因为装饰器在代码开始运行之前不会识别“答案”?
你可以这样做:
from functools import wraps
def validation_func(func):
@wraps(func)
def wrapper(*args, **kwargs):
func(wrapper, *args, **kwargs)
if wrapper.answer == 1337:
print ('1337')
else:
print('Hi')
return wrapper
@validation_func
def math_add(wrapper):
wrapper.answer = 1336 + 1
math_add()
输出
1337
我解决了明显的问题。
请注意,客户端看不到他们的用法有任何变化,但是
math_add()
,正如所定义的,现在有一个参数。 (这可能会让程序员稍后阅读这段代码时感到困惑)。
Python 将在进入函数时创建一个包含新变量的范围,并在函数退出时销毁该范围 - 包括所有局部变量。
装饰器调用函数 - 并且可以在函数完成之前和 after 运行,但不能 during - 不适用于普通函数。
在某些情况下,在函数作用域中创建的变量可以比它长寿 - 使用闭包和非局部变量,或者一些可以暂停的特殊函数类型,将控制权返回给调用者,稍后恢复,如生成器函数或异步协程函数的情况(在此暂停期间,可以检查函数局部变量)。然而,所有这些情况都需要在函数本身中进行转换:它的元属性和字节码本身,以引起暂停本身。
这意味着它是“可能但不是真正可行的装饰器”——它可以完成,但需要重新编译函数本身,并且可能有很多需要处理的极端情况。
但是,如果函数不是装饰器,而是写成它 calls 一些东西,并将这个调用放在它的主体中,那么是的,被调用函数可以并且相对容易地检查此时的调用者状态它被称为。那将是一个“反向装饰器”。它甚至可以与传统的装饰器一起工作,传统的装饰器会注释元数据关于应该进行哪些检查。
最后但同样重要的是,另一种方法:可以利用语言中调试器使用的工具:可以生成回调,其中包含有关运行范围的大量信息。 我认为除了有一个“被调用的函数”而不是装饰器之外,这是可行的方法。请参阅以下文档:https://docs.python.org/3/library/sys.html#sys.settrace
所以,这是一个示例装饰器,它可以使用调试基础设施来做你想做的事情,在遇到返回或异常时检查局部变量:
import sys
from functools import wraps
sentinel = object()
def validation(assertions):
depth = 0
def tracer(frame, event, arg):
nonlocal depth
if event == "call":
depth += 1
if depth > 1:
return None
frame.f_trace_lines = False
frame.f_trace_opcodes = False
return tracer
# this code should only be reached for "return" and exception codes:
for variable_name, expected_value in assertions.items():
current_value = frame.f_locals.get(variable_name, sentinel)
if current_value != expected_value:
print(f"{variable_name} contains {current_value}. Expected {expected_value}")
return tracer
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
sys.settrace(tracer)
try:
result = func(*args, **kwargs)
finally:
sys.settrace(None)
return result
return wrapper
return decorator
@validation({"answer": 1337})
def math_add():
answer = 1336 + 2
math_add()
将上面的代码片段作为 Python 程序运行,打印出来:
answer contains 1338. Expected 1337
除了上面关于 sys.settrace 的文档,您可能还想检查 Frame 对象上可用的属性有哪些。 (例如,
f_locals
属性是反映局部变量内容的字典)。
https://docs.python.org/3/library/inspect.html#types-and-members