【问题标题】:python decorator on class: TypeError: super() argument 1 must be type, not function类上的python装饰器:TypeError:super()参数1必须是类型,而不是函数
【发布时间】:2021-08-05 19:11:05
【问题描述】:

我在类之上使用装饰器来注册组件。这是我的代码

import functools
registry = {}

def register(name=None):
    """A decorator for registering modules
    :param name: (optional) name for component
    """
    def _wrap_func(func):
        registry[name or func.__name__] = func

        @functools.wraps(func)
        def _wrap_args(*args, **kwargs):
            return func(*args, **kwargs)

        return _wrap_args
    return _wrap_func

class Base:

    def __init__(self, arg):
        self.arg = arg

@register(name="module1")
class Module1(Base):

    def __init__(self, arg):
        super(Module1, self).__init__(arg=arg)
        # super().__init__(arg=arg)

@register(name="module2")
class Module2(Base):

    def __init__(self, arg):
        super(Module2, self).__init__(arg=arg)

到目前为止还不错。 registryregister 按预期工作

print(registry)
# {'module1': <class '__main__.Module1'>, 'module2': <class '__main__.Module2'>}

但是,在装饰类上调用构造函数会引发错误。

module1 = Module1(arg='some1')
print(module1)

Traceback (most recent call last):
  File "/tmp.py", line xx, in <module>
    module1 = Module1(arg='some1')
  File "/tmp.py", line xx, in _wrap_args
    return func(*args, **kwargs)
  File "/tmp.py", line xx, in __init__
    super(Module1, self).__init__(arg=arg)
TypeError: super() argument 1 must be type, not function

我以为functools.wraps 负责隐藏装饰器,但这里不是。 如果我将super(Module1, self).__init__(arg=arg) 更改为super().__init__(arg=arg),它可以工作! 这是装饰器的预期行为还是我对 registry() 函数的定义有问题?


编辑:我最近发现继承被破坏了

@register(name="module3")
class Module3(Module1):
    pass

导致

Traceback (most recent call last):
  File "tmp.py", line xx, in <module>
    class Module3(Module1):
TypeError: function() argument 'code' must be code, not str

我知道装饰类变成了一个函数,但是如何解决这个问题?它只是从文档中不那么明显。

  1. 我提到了PEP-3129,他们说 装饰器的详细检查,请参考PEP 318 没有一个带参数的类装饰器示例!

  2. 我发现的最有用的文档是Primer on decorators 他们省略了类装饰器带参数

【问题讨论】:

  • 可能是因为类不是函数?
  • 这能回答你的问题吗? Python functools.wraps equivalent for classes
  • 这是否意味着我们不应该在(派生)类上使用装饰器?
  • atm 的问题是,一旦装饰,Module1 就被视为一个函数,(即来自装饰器的_wrap_func()。所以尽可能使用super().。跨度>
  • register 被编写为函数装饰器。你为什么把它应用到课堂上?或者如果它应该是一个类装饰器,为什么它返回函数?

标签: python python-3.x python-decorators


【解决方案1】:

我研究了@dataclass 的实现并找到了正确的方法。 Unlike what's said in docs and guides elsewhere,类装饰器实现与函数装饰器略有不同——我们不需要接收参数并调用它。

这是一个有效的方法:

def register(cls=None, name=None):
    """
    A decorator for registering modules
    :param name: (optional) name for this component
    """
    def _wrap_cls(cls):
        registry[name or cls.__name__] = cls
        return cls
    if cls is None:
        return _wrap_cls
    else:
        return _wrap_cls(cls)

【讨论】:

  • 有人在没有评论原因的情况下否决了我的答案!使用 cls=None 参数,它同时处理 @register@register() 用法。
【解决方案2】:

您面临的问题是,您实际上从装饰器返回了一个函数,而不是一个类。为什么不直接注册类然后返回类本身呢?

registry = {}

def register(name=None):
    def inner(cls):
        registry[name or cls.__name__] = cls
        return cls

    return inner


class Base:
    def __init__(self, arg):
        self.arg = arg


@register(name="file1")
class Module1(Base):

    def __init__(self, arg):
        super(Module1, self).__init__(arg=arg)


@register()
class Module2(Base):

    def __init__(self, arg):
        print('init called from Module2')
        super(Module2, self).__init__(arg=arg)


print(registry)
print(registry['Module2'](10))

一切正常,输出:

{'file1': <class '__main__.Module1'>, 'Module2': <class '__main__.Module2'>}
init called from Module2
<__main__.Module2 object at 0x000001B83B0B7FD0>

【讨论】:

  • 使用装饰器创建类注册表没有意义,因为它们不是继承的。您可以通过将代码添加到基本元类 init 方法来保存所有输入。编辑:我的意思是 new 方法
猜你喜欢
  • 1970-01-01
  • 2017-10-01
  • 1970-01-01
  • 2013-02-15
  • 2020-11-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多