为什么 namedtuple 模块不使用 metaclass 来创建 ntclass 对象?
Why doesn't the namedtuple module use a metaclass to create nt class objects?
几周前我花了一些时间调查collections.namedtuple
module。该模块使用工厂函数将动态数据(新 namedtuple
class 的名称和 class 属性名称)填充到一个非常大的字符串中。然后以字符串(代表代码)为参数执行exec
,返回新的class。
有没有人知道为什么要这样做,当有一个特定的工具可以轻松获得这种东西时,即 metaclass?我自己没有尝试这样做,但似乎 namedtuple
模块中发生的一切都可以使用 namedtuple
metaclass 轻松完成,如下所示:
class namedtuple(type):
等等等等
issue 3974中有一些提示。作者提出了一种创建命名元组的新方法,被拒绝,评论如下:
It seems the benefit of the original version is that it's faster,
thanks to hardcoding critical methods.
- Antoine Pitrou
There is nothing unholy about using exec. Earlier versions used other
approaches and they proved unnecessarily complex and had unexpected
problems. It is a key feature for named tuples that they are exactly
equivalent to a hand-written class. - Raymond Hettinger
另外,这里是the original namedtuple
recipe的部分描述:
... the recipe has evolved to its current exec-style where we get all
of Python's high-speed builtin argument checking for free. The new
style of building and exec-ing a template made both the __new__ and
__repr__ functions faster and cleaner than in previous versions of this recipe.
如果您正在寻找一些替代实现:
abstract base class + mix-in for named tuples Jan Kaliszewski 的食谱
metaclass-based implementation by Aaron Iles (see his blog post)
作为旁注:我最常看到的反对使用 exec
的另一个反对意见是,出于安全原因,某些位置(阅读公司)禁用它。
除了高级的 Enum
和 NamedConstant
,the aenum library* 还有 NamedTuple
,这是基于 metaclass
的。
这是另一种方法。
""" 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
在按名称访问省略元素时对省略的元素进行 IndexError
(可以翻译成 AttributeError
).元素计数不正确的错误消息有点奇怪,但它明白了要点。我不希望这适用于 Python 2.
还有进一步复杂化的空间,例如 __repr__
方法。我不知道性能与其他实现相比如何(缓存签名长度可能会有所帮助),但与本机 namedtuple
实现相比,我更喜欢调用约定。
经过多年的经验回到这个问题:以下是 none 其他答案突然想到的其他几个原因*。
每个 class 只允许 1 个元class
一个class只能有1个metaclass。 metaclass 充当创建 class 的工厂,并且不可能随意将工厂混合在一起。您必须创建一个知道如何以正确顺序调用多个工厂的“组合工厂”,或者一个知道“父工厂”并正确使用它的“子工厂”。
如果 namedtuple
使用自己的元class,涉及任何其他元class 的继承将中断:
>>> 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
相反,如果您想拥有自己的元class 并继承自 namedtuple
class,则必须使用某种 so-called namedtuple_meta
metaclass 来做到这一点:
from namedtuple import namedtuple_meta # pretending this exists
class MyMeta(type): ...
class MyMetaWithNT(namedtuple_meta, MyMeta): ...
class C(metaclass=MyMetaWithNT): ...
..或者直接从namedtuple_meta
继承自定义元class:
class MyMeta(namedtuple_meta): ...
class C(metaclass=MyMeta): ...
乍一看这看起来很简单,但是编写自己的 metaclass 并与一些(复杂的)nt metaclass 配合得很好可能很快就会出现问题。这个限制可能不会经常出现,但经常会阻碍 namedtuple
的使用。因此,让所有 namedtuple
class 都是 type
类型并消除自定义元 class.
的复杂性绝对是一个优势
元class,还是元编程?
“为什么不使用元class?!?”这个问题忽略了一个基本问题。是:nt的用途是什么?
目的不仅仅是创建一个 class 工厂。如果是这样,metaclass 就完美了。 namedtuple
的真正目的不仅仅是最终功能,而是自动生成一个 class 结构,代码在各个方面都简单易懂,就好像它是由经验丰富的专业人士手写的一样。这需要元编程——自动生成不是class,而是代码 ].这是两个不同的东西。它与较新的 dataclasses
模块非常相似,后者为您编写方法(而不是编写整个 class,如 namedtuple
)。
* Raymond Hettinger 的 comment 确实暗示了这一点:
It is a key feature for named tuples that they are exactly equivalent to a hand-written class.
几周前我花了一些时间调查collections.namedtuple
module。该模块使用工厂函数将动态数据(新 namedtuple
class 的名称和 class 属性名称)填充到一个非常大的字符串中。然后以字符串(代表代码)为参数执行exec
,返回新的class。
有没有人知道为什么要这样做,当有一个特定的工具可以轻松获得这种东西时,即 metaclass?我自己没有尝试这样做,但似乎 namedtuple
模块中发生的一切都可以使用 namedtuple
metaclass 轻松完成,如下所示:
class namedtuple(type):
等等等等
issue 3974中有一些提示。作者提出了一种创建命名元组的新方法,被拒绝,评论如下:
It seems the benefit of the original version is that it's faster, thanks to hardcoding critical methods. - Antoine Pitrou
There is nothing unholy about using exec. Earlier versions used other approaches and they proved unnecessarily complex and had unexpected problems. It is a key feature for named tuples that they are exactly equivalent to a hand-written class. - Raymond Hettinger
另外,这里是the original namedtuple
recipe的部分描述:
... the recipe has evolved to its current exec-style where we get all of Python's high-speed builtin argument checking for free. The new style of building and exec-ing a template made both the __new__ and __repr__ functions faster and cleaner than in previous versions of this recipe.
如果您正在寻找一些替代实现:
abstract base class + mix-in for named tuples Jan Kaliszewski 的食谱
metaclass-based implementation by Aaron Iles (see his blog post)
作为旁注:我最常看到的反对使用 exec
的另一个反对意见是,出于安全原因,某些位置(阅读公司)禁用它。
除了高级的 Enum
和 NamedConstant
,the aenum library* 还有 NamedTuple
,这是基于 metaclass
的。
这是另一种方法。
""" 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
在按名称访问省略元素时对省略的元素进行 IndexError
(可以翻译成 AttributeError
).元素计数不正确的错误消息有点奇怪,但它明白了要点。我不希望这适用于 Python 2.
还有进一步复杂化的空间,例如 __repr__
方法。我不知道性能与其他实现相比如何(缓存签名长度可能会有所帮助),但与本机 namedtuple
实现相比,我更喜欢调用约定。
经过多年的经验回到这个问题:以下是 none 其他答案突然想到的其他几个原因*。
每个 class 只允许 1 个元class
一个class只能有1个metaclass。 metaclass 充当创建 class 的工厂,并且不可能随意将工厂混合在一起。您必须创建一个知道如何以正确顺序调用多个工厂的“组合工厂”,或者一个知道“父工厂”并正确使用它的“子工厂”。
如果 namedtuple
使用自己的元class,涉及任何其他元class 的继承将中断:
>>> 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
相反,如果您想拥有自己的元class 并继承自 namedtuple
class,则必须使用某种 so-called namedtuple_meta
metaclass 来做到这一点:
from namedtuple import namedtuple_meta # pretending this exists
class MyMeta(type): ...
class MyMetaWithNT(namedtuple_meta, MyMeta): ...
class C(metaclass=MyMetaWithNT): ...
..或者直接从namedtuple_meta
继承自定义元class:
class MyMeta(namedtuple_meta): ...
class C(metaclass=MyMeta): ...
乍一看这看起来很简单,但是编写自己的 metaclass 并与一些(复杂的)nt metaclass 配合得很好可能很快就会出现问题。这个限制可能不会经常出现,但经常会阻碍 namedtuple
的使用。因此,让所有 namedtuple
class 都是 type
类型并消除自定义元 class.
元class,还是元编程?
“为什么不使用元class?!?”这个问题忽略了一个基本问题。是:nt的用途是什么?
目的不仅仅是创建一个 class 工厂。如果是这样,metaclass 就完美了。 namedtuple
的真正目的不仅仅是最终功能,而是自动生成一个 class 结构,代码在各个方面都简单易懂,就好像它是由经验丰富的专业人士手写的一样。这需要元编程——自动生成不是class,而是代码 ].这是两个不同的东西。它与较新的 dataclasses
模块非常相似,后者为您编写方法(而不是编写整个 class,如 namedtuple
)。
* Raymond Hettinger 的 comment 确实暗示了这一点:
It is a key feature for named tuples that they are exactly equivalent to a hand-written class.