为什么namedtuple模块不使用元类来创建nt类对象?

python metaclass python-internals namedtuple

1177 观看

4回复

16382 作者的声誉

几周前我花了一些时间调查这个collections.namedtuple模块。该模块使用工厂函数将动态数据(新namedtuple类的名称和类属性名称)填充到一个非常大的字符串中。然后exec使用字符串(表示代码)作为参数执行,并返回新类。

有没有人知道为什么这样做,当有一种特定的工具可供现有的这种东西,即元类?我自己没有尝试过这样做,但似乎namedtuple模块中发生的一切都可以使用namedtuple元类轻松完成,如下所示:

class namedtuple(type):

等等

作者: Rick Teachey 的来源 发布者: 2015 年 1 月 28 日

回应 (4)


19

29215 作者的声誉

决定

问题3974中有一些提示。作者提出了一种创建命名元组的新方法,该方法被拒绝并带有以下注释:

由于对关键方法进行了硬编码,原始版本的好处似乎是速度更快。 - Antoine Pitrou

使用exec没有什么不圣洁的。早期版本使用其他方法,并且它们被证明是不必要的复杂并且具有意外问题。它是命名元组的一个关键特性,它们完全等同于手写类。 - Raymond Hettinger

另外,这是原始namedtuple配方描述的一部分:

...这个配方已经发展到现在的exec风格,我们可以免费获得所有Python的高速内置参数检查。构建和执行模板的新风格使__new__和__repr__函数比此配方的先前版本更快更干净。

如果您正在寻找一些替代实现:

作者: vaultah 发布者: 28.01.2015 04:37

4

38480 作者的声誉

作为旁注:我经常反对使用的另一个反对意见exec是,出于安全原因,某些位置(读取公司)会禁用它。

除了先进的EnumNamedConstant该aenum库 *也有NamedTuplemetaclass基础的。


* aenum由作者enumenum34后端撰写。

作者: Ethan Furman 发布者: 31.03.2016 09:59

1

1039 作者的声誉

这是另一种方法。

""" Subclass of tuple with named fields """
from operator import itemgetter
from inspect import signature

class MetaTuple(type):
    """ metaclass for NamedTuple """

    def __new__(mcs, name, bases, namespace):
        cls = type.__new__(mcs, name, bases, namespace)
        names = signature(cls._signature).parameters.keys()
        for i, key in enumerate(names):
            setattr(cls, key, property(itemgetter(i)))
        return cls

class NamedTuple(tuple, metaclass=MetaTuple):
    """ Subclass of tuple with named fields """

    @staticmethod
    def _signature():
        " Override in subclass "

    def __new__(cls, *args):
        new = super().__new__(cls, *args)
        if len(new) == len(signature(cls._signature).parameters):
            return new
        return new._signature(*new)

if __name__ == '__main__':
    class Point(NamedTuple):
        " Simple test "
        @staticmethod
        def _signature(x, y, z): # pylint: disable=arguments-differ
            " Three coordinates "
    print(Point((1, 2, 4)))

如果这种方法有任何优点,那就是简单。它会更简单但没有NamedTuple.__new__,这只是为了强制执行元素计数。如果没有它,它会愉快地允许额外的匿名元素超过命名元素,省略元素的主要作用是在IndexError按名称访问时忽略元素(有一些可以转换为a的工作AttributeError)。错误的元素计数的错误消息有点奇怪,但它得到了重点。我不希望这与Python 2一起使用。

存在进一步复杂化的空间,例如__repr__方法。我不知道性能如何与其他实现相比(缓存签名长度可能有帮助),但我更喜欢调用约定与本机namedtuple实现相比。

作者: Eirik Fuller 发布者: 24.06.2016 10:04

0

16382 作者的声誉

另一个原因是其他答案都没有达到*。

一个类只能有1个元类。其中一个原因是元类充当创建类的工厂。威利不可能将工厂混合在一起。您必须创建一个“组合工厂”,它知道如何以正确的顺序调用多个工厂,或者创建一个知道“父工厂”的“子工厂”,并正确使用它。

如果namedtuple使用它自己的元类,涉及任何其他元类的继承会破坏:

>>> class M1(type): ...
...
>>> class M2(type): ...
...
>>> class C1(metaclass=M1): ...
...
>>> class C2(metaclass=M2): ...
...
>>> class C(C1, C2): ...
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

相反,如果你想拥有自己的元类并从类继承namedtuple,你必须使用某种所谓的namedtuple_meta元类来做到这一点:

from namedtuple import namedtuple_meta  # pretending this exists

class MyMeta(type): ...

class MyMetaWithNT(namedtuple_meta, MyMeta): ...

class C(metaclass=MyMetaWithNT): ...

..或namedtuple_meta直接从直接继承自定义元类:

class MyMeta(namedtuple_meta): ...

class C(metaclass=MyMeta): ...

这看起来很简单,但编写自己的mataclass可以很快地解决一些(复杂的)nt元类问题。这种限制可能不会经常出现,但往往足以阻碍其使用namedtuple。因此,将所有namedtuple类都设置为type类型并消除自定义元类的复杂性绝对是一个优势。


* Raymond Hettinger的评论暗示了它:

它是命名元组的一个关键特性,它们完全等同于手写类。

作者: Rick Teachey 发布者: 17.06.2019 02:19
32x32