在 Python 中使用 attr 模块和元类

Posted

技术标签:

【中文标题】在 Python 中使用 attr 模块和元类【英文标题】:Using attr module and metaclasses in Python 【发布时间】:2020-02-06 22:38:51 【问题描述】:

我正在尝试使用 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 中工作,我会很高兴。

【问题讨论】:

【参考方案1】:

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

我的意思是 - 它是 Python 运行时在调用元类 __init__ 时填充参数“class, name, bases, namespace”,您不能轻易更改它以便传递 class, attr1, attr2,因为由attrs 创建的__init__ 需要。

简而言之,如果不进一步查看 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

同样,我不知道 attr 库,也许它不适用于 继承(但我怀疑 - 它应该做得很好),那么你可以使用 元类注入属性在你的类上应用@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

【讨论】:

是的,子类化与 attrs 配合得很好。我们从未尝试过将元类子类化,然后将它们本身用作元类,我不确定该 TBH 是否有用例。一旦你像这样完成完整的元类,所有的期望都超出了预期。

以上是关于在 Python 中使用 attr 模块和元类的主要内容,如果未能解决你的问题,请参考以下文章

参数化类和元类有啥区别(请使用 Python 中的代码示例)?

Python入门之Python的单例模式和元类

Python学习 Day14 python 类和元类(metaclass)的理解和简单运用

python 类和元类(metaclass)的理解和简单运用

__new__、__init__ 和元类(和超类)

python类:描述器Descriptors和元类MetaClasses