Python 3.6中的通用命名元组

25

我正在尝试创建一个命名元组的通用版本,如下:

T1 = TypeVar("T1")
T2 = TypeVar("T2")

class Group(NamedTuple, Generic[T1, T2]):
    key: T1
    group: List[T2]

g = Group(1, [""])  # expecting type to be Group[int, str]

然而,我收到了以下错误:

TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

我不确定还有什么其他方法可以实现我在这里尝试做的事情,或者是否可能是某个层面上类型机制中的错误。


你可以进一步阐述一下,你打算在哪些方面通用NamedTuple,我个人认为它已经很通用了。从你的代码片段中我无法识别出…… - guidot
1
特殊的NamedTuple不支持任何其他基类。句号。NamedTuple基类将所有使用委托给一个元类,该元类将运行collections.namedtuple()并进行少量自定义,这意味着生成的类*仅继承自tuple*。这是NamedTuple的一个更广泛的问题,不仅限于Generic - Martijn Pieters
1个回答

18

所以这是一个元类冲突,因为在Python 3.6中,typing的NamedTupleGeneric使用不同的元类(typing.NamedTupleMetatyping.GenericMeta),这是Python无法处理的。恐怕除了从tuple子类化并手动初始化值之外,没有其他解决方案:

T1 = TypeVar("T1")
T2 = TypeVar("T2")

class Group(tuple, Generic[T1, T2]):

    key: T1
    group: List[T2]

    def __new__(cls, key: T1, group: List[T2]):
        self = tuple.__new__(cls, (key, group))
        self.key = key
        self.group = group
        return self            

    def __repr__(self) -> str:
        return f'Group(key={self.key}, group={self.group})'

Group(1, [""])  # --> Group(key=1, group=[""])

由于PEP 560563,这已在Python 3.7中得到修复。
Python 3.7.0b2 (v3.7.0b2:b0ef5c979b, Feb 28 2018, 02:24:20) [MSC v.1912 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from __future__ import annotations
>>> from typing import *
>>> T1 = TypeVar("T1")
>>> T2 = TypeVar("T2")
>>> class Group(NamedTuple, Generic[T1, T2]):
...     key: T1
...     group: List[T2]
...
>>> g: Group[int, str] = Group(1, [""])
>>> g
Group(key=1, group=[''])

当然,在Python 3.7中,你可以使用数据类(dataclass),它们比较轻量级(且可变),但是具有类似的作用。
from dataclasses import dataclass, astuple
from typing import Generic, TypeVar, List

T1 = TypeVar('T1')
T2 = TypeVar('T2')

@dataclass
class Group(Generic[T1, T2]):

     # this stores the data like a tuple, but isn't required
     __slots__ = ("key", "group")

     key: T1
     group: List[T2]

     # if you want to be able to unpack like a tuple...
     def __iter__(self):
          yield from astuple(self)


g: Group[int, str] = Group(1, ['hello', 'world'])
k, v = g
print(g)

我不确定python 3.7中类型检查器对我的解决方案/你的解决方案的处理效果如何,但我怀疑它可能不是无缝的。

编辑

我找到了另一种解决方案——创建一个新的元类

import typing
from typing import *

class NamedTupleGenericMeta(typing.NamedTupleMeta, typing.GenericMeta):
    pass


class Group(NamedTuple, Generic[T1,T2], metaclass=NamedTupleGenericMeta):

    key: T1
    group: List[T2]


Group(1, ['']) # --> Group(key=1, group=[''])

3
第二个解决方案看起来不错,但遗憾的是它并不能完全用于类型提示:g: Group[int, str] = Group(1, [""]) 会报错: TypeError: 'type' object is not subscriptable - pdowling
2
你仍然不能在NamedTuple子类中使用Generic[...]提示,因为由于相同的元类冲突,新生成的命名元组类将不具备所需的__class_getitem__钩子,从而使像Group[str, int]这样的具体提示无法生效。这是因为NamedTuple元类返回一个新的类对象,并且只有tuple作为基类,而没有Generic,并且记录可用类型变量的类上所需的__parameters__属性完全消失了。 - Martijn Pieters
1
请在您的回答中更加明确,因为您需要在所有使用该子类的模块中使用编译器开关。 - Martijn Pieters
21
在 Python 3.9 中,即使类没有被使用,OP的原始代码在声明类时也会出现错误,错误信息为“TypeError: Multiple inheritance with NamedTuple is not supported”。我需要将其翻译成中文吗? - cowlinator
2
另外,在Python 3.7及以上版本中,typing.GenericMeta似乎不存在。https://github.com/mkdocstrings/mkdocstrings/issues/2。 - cowlinator
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接