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

时间:2015-01-28 04:17:25

标签: python metaclass python-internals namedtuple

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

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

class namedtuple(type):

等等。

4 个答案:

答案 0 :(得分:19)

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

  

原始版本的好处似乎是它更快,   感谢硬编码关键方法。    - Antoine Pitrou

  

使用exec没有什么不圣洁的。早期版本使用其他版本   方法,他们证明不必要的复杂和意外   问题。这是命名元组的一个关键特性   相当于手写的课程。 - Raymond Hettinger

此外,以下是the original namedtuple recipe

的部分说明
  

......食谱已经发展到现在的exec风格,我们得到了所有   Python的高速内置参数检查免费。新的   建立和执行模板的风格同时使__new__和   __repr__功能比此配方的早期版本更快更干净。

如果您正在寻找其他替代方案:

答案 1 :(得分:4)

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

除了高级EnumNamedConstant之外,the aenum library *还有基于NamedTuple的{​​{1}}。

* metaclassenumenum34反向移植的作者撰写。

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

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

答案 3 :(得分: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): ...

乍一看这很容易,但是编写自己的与某些(复杂的)nt元类很好地配合的mataclass可能很快就会成为问题。此限制可能不会经常出现,但经常会足以阻碍namedtuple的使用。因此,将所有namedtuple类都设为type类型,并消除自定义元类的复杂性绝对是一个优势。


* Raymond Hettinger的comment确实暗示了这一点:

  

这是命名元组的关键功能,它们完全等同于手写类。