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

问题描述 投票:20回答:4

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

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

class namedtuple(type):

等等

python metaclass python-internals namedtuple
4个回答
19
投票

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

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

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

另外,这里是the original namedtuple recipe描述的一部分:

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

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


4
投票

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

除了先进的EnumNamedConstantthe aenum library *还有NamedTuple,这是metaclass为基础。


* aenumenumenum34 backport的作者撰写。


1
投票

这是另一种方法。

""" 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(有一些可以转换为AttributeError的工作)。错误的元素计数的错误消息有点奇怪,但它得到了重点。我不希望这与Python 2一起使用。

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


0
投票

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

一个类只能有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的comment暗示了它:

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

© www.soinside.com 2019 - 2024. All rights reserved.