【问题标题】:Class `__init__` with decorator invoked on import类`__init__`,在导入时调用装饰器
【发布时间】:2020-08-04 01:39:12
【问题描述】:

我有一个装饰器,可以帮助定义来自全局配置文件的类属性。该函数如下所示:

config_data = {
    "resourceA": {"a": "foo", "b": "bar"},
    "resourceB": {"x": "baz", "y": "boo"}
}

def autoinit(resource: str = None, data: dict = config_data, **kwargs):
    print(f'here for {resource}')
    if not kwargs:
        if resource:
            kwargs = data[resource]  # get defaults

    def wrapper(func):
        def wrapped(*args, **configs):
            kwargs.update(configs)  # update kwargs with overrides
            return func(*args, **kwargs)

        return wrapped

    return wrapper

这个装饰器的行为方式是,如果我有一个类:

class ResourceA:
    @autoinit(resource="resourceA")
    def __init__(self, a, b):
        self.a = a
        self.b = b
    # other stuff

class ResourceB:
    @autoinit(resource="resourceB")
    def __init__(self, x, y):
        self.x = x
        self.y = y
    # other stuff

将自动从数据配置中设置。

现在,当我执行导入时

from module.submodule import ResourceA

我的结果是:

here for resourceA
here for resourceB

当我还没有实例化类时,为什么会调用装饰器?

这是我遇到的真正问题的虚拟版本。它打断了我的测试,因为data: dict = config_data 实际上是从默认文件加载的。当我运行测试时,存在一个默认文件不存在的版本,但我无法通过它。

有什么想法吗?

【问题讨论】:

  • 嗯,因为这就是装饰器的工作方式。 装饰器在你调用它时被调用!即@autoinit,相当于def __init__(self,) ...; __init__ = autoinit(__init__)。你的装饰器返回一个包装器,当你实例化类时 that 会被调用。
  • 导入时创建类并执行其中的方法定义(不是方法本身的代码)以将函数注册为方法。这也包括装饰器(如果存在)。
  • @juanpa.arrivillaga,想把它作为答案发布吗?
  • @sgerbhctim 是的,当您导入模块时,全局范围内的类定义会执行(全局范围内的所有内容都会执行),然后您在类定义中调用装饰器。
  • @sgerbhctim 将def autoinit下面的四行移动到def wrapper下面

标签: python decorator


【解决方案1】:

可以这样修改装饰器功能:

def autoinit(resource: str = None, data: dict = config_data, **kwargs):

    def wrapper(func):

        def wrapped(*args, **configs):

            nonlocal kwargs

            if not kwargs:
                if resource:
                    kwargs = data[resource]

            kwargs.update(configs)  # update kwargs with overrides
            return func(*args, **kwargs)

        return wrapped

    return wrapper

【讨论】:

  • 我的意思是在def wrapperdef wrapped之间;这个想法是让这些行在autoinit 被调用时不执行,而是在__init__ 被调用时执行。
  • 是的,在此版本中没有打印任何内容的唯一原因是您删除了 print 调用,而不是因为您实际上按照您希望的方式延迟了事情。
  • 是的,我承认这一点。我最终犯了一个错误......但是,这不起作用,因为UnboundLocalError: local variable 'kwargs' referenced before assignment
  • 表面上的修复将是nonlocal kwargs,但您可能应该进行更多更改 - 多次调用__init__ 正在以不安全的方式共享和变异单个kwargs dict。 (此外,将该代码移至 wrapper 并不足以延迟它 - 如果您希望它在 __init__ 运行时运行,则需要将其移至 wrapped。)
猜你喜欢
  • 2019-12-20
  • 2013-04-07
  • 2023-03-14
  • 2023-04-04
  • 1970-01-01
  • 2015-12-06
  • 1970-01-01
  • 2019-03-25
  • 1970-01-01
相关资源
最近更新 更多