0

我正在尝试使用该attr包来简单地创建一个具有属性和方法的元类,以便在 Python 3 中的进一步类定义中使用。我想使用该attrs包,因为我有很多简单的存储类,在初始化时只需要一些属性. 一切正常,除了当我尝试将元类添加到主类时,代码失败

TypeError: __init__() takes from 1 to 2 positional arguments but 4 were given

一个简单的 MWE 将是:

from attr import attrs, attrib
from abc import ABCMeta

@attrs
class MetaClass(ABCMeta):

    my_attribute = attrib()

    def my_method(self):
        pass

class MyClass(object, metaclass=MetaClass):
    pass

对于 Python 2/3 兼容性,我通常使用 6 包和add_metaclass其中的装饰器,但如果它可以在 Python 2 或 3 中工作,我会很高兴。

4

1 回答 1

3

attrs库确实__init__为它正在装饰的类生成了一个方法。__new__但是,元类对和方法有一个明确定义的签名__init__,其参数由 Python 运行时本身填充,每当class执行语句时(即连同其主体一起)。

我的意思是 - 它是 Python 运行时在class, name, bases, namespace对 metaclass 的调用中填充参数 " " __init__,你不能轻易地改变它,以便class, attr1, attr2传递它,因为__init__created byattrs需要。

简而言之,如果不进一步attrs查看文档以查看是否可以抑制其覆盖__init__(以及它创建的一些其他魔术方法),则不能attrs与元类一起使用。

作为记录,Python 3.7“数据类”允许人们“关闭”__init__方法的创建,但即便如此,元类也是必须仔细考虑的东西,很难想到用于检测的高级功能仅因为语言语法允许而对元类开箱即用的类。

总而言之,这可能是一个“x,y”问题——我建议不要仅仅因为你认为它的一些特性在那里可以很好地发挥作用,就尝试使用带有元类的 3rd 方包,而是描述它是什么您想在另一个问题中使用自定义元类来实现。
元类方法中的几行代码 __new__可能会为您提供所需的功能,但不会产生未知的副作用。

特别是,如果您只是想添加my_attribute = attrib()到使用自定义元类创建的所有类中,则不应尝试在元类中创建它。也许,您只需要一个 Base 超类,根本不需要元类:

from abc import ABC
...

@attrs
class Base(ABC):
   my_attribute = attrib()

class MyClass(Base):
   pass

同样,我不知道attrlib,也许它不适用于继承(但我怀疑 - 它应该做得很好),然后你可以使用元类来注入属性并在你的类上应用@attrs装饰器,但是不要尝试在您的元类本身上这样做:

from attr import attrs, attrib
from abc import ABCMeta


class MetaClass(ABCMeta):

    my_attribute = attrib()

    def __new__(metacls, name, bases, namespace, **kw):
        cls = super().__new__(metacls, name, bases, namespace, **kw)
        # if the 'attrs' decorator modifies the type of "cls", 
        # the original __init__ won't be called automatically.
        # since we are inheriting from other superclass, we'd better
        # call it manually here, and suppress its automatic execution
        # bellow. 
        super(MetaClass, cls).__init__(cls, name, bases, namespace, **kw)
        cls.my_attribute = attrib()
        return attrs(cls)

    def __init__(cls, name, bases, namespace, **kw):
        pass


class MyClass(object, metaclass=MetaClass):
    pass
于 2019-10-09T15:05:14.350 回答