从装饰器访问装饰函数中的变量

问题描述 投票:0回答:2

我是 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”并以类似于代码示例的方式执行验证?还是不可能这样做,因为装饰器在代码开始运行之前不会识别“答案”?

python decorator python-decorators
2个回答
0
投票

你可以这样做:

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()
,正如所定义的,现在有一个参数。 (这可能会让程序员稍后阅读这段代码时感到困惑)。


0
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.