我花了一些时间调查几周前的collections.namedtuple
module。该模块使用工厂函数将动态数据(新namedtuple
类的名称和类属性名称)填充到一个非常大的字符串中。然后使用字符串(表示代码)作为参数执行exec
,并返回新类。
有没有人知道为什么这样做,当有一种特定的工具可以随时使用这种东西,即元类?我自己还没有尝试过这样做,但似乎namedtuple
模块中发生的所有事情都可以使用namedtuple
元类轻松完成,如下所示:
class namedtuple(type):
等等。
答案 0 :(得分:19)
issue 3974中有一些提示。作者提出了一种创建命名元组的新方法,该方法被以下注释拒绝:
原始版本的好处似乎是它更快, 感谢硬编码关键方法。 - Antoine Pitrou
使用exec没有什么不圣洁的。早期版本使用其他版本 方法,他们证明不必要的复杂和意外 问题。这是命名元组的一个关键特性 相当于手写的课程。 - Raymond Hettinger
此外,以下是the original namedtuple
recipe:
......食谱已经发展到现在的exec风格,我们得到了所有 Python的高速内置参数检查免费。新的 建立和执行模板的风格同时使__new__和 __repr__功能比此配方的早期版本更快更干净。
如果您正在寻找其他替代方案:
答案 1 :(得分:4)
作为旁注:我经常反对使用exec
的另一个反对意见是某些地点(阅读公司)出于安全原因禁用它。
除了高级Enum
和NamedConstant
之外,the aenum library *还有基于NamedTuple
的{{1}}。
答案 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确实暗示了这一点:
这是命名元组的关键功能,它们完全等同于手写类。