Python 3.6中的通用NamedTuple

pdo*_*ing 4 python generics typing python-3.6

我正在尝试创建NamedTuple的通用版本,如下所示:

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]
Run Code Online (Sandbox Code Playgroud)

但是,出现以下错误:

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

我不确定在这里还能做什么,或者在某种程度上这可能是键入机制中的错误。

FHT*_*ell 7

因此,这是一个元类冲突,因为在python 3.6中键入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=[""])
Run Code Online (Sandbox Code Playgroud)

由于PEP 560,此问题已在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 typing import *
>>> T1 = TypeVar("T1")
>>> T2 = TypeVar("T2")
>>> class Group(NamedTuple, Generic[T1, T2]):
...     key: T1
...     group: List[T2]
...
>>> g = Group(1, [""])
>>> g
Group(key=1, group=[''])
Run Code Online (Sandbox Code Playgroud)

尽管我没有检查过,类型检查器在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=[''])
Run Code Online (Sandbox Code Playgroud)

  • 在 python 3.9 中,OP 的原始代码现在会在类声明时中断,即使该类未使用,也会出现错误“TypeError:不支持使用 NamedTuple 进行多重继承” (21认同)
  • 另外,在 Python 3.7+ 上,“typing.GenericMeta”似乎不存在。https://github.com/mkdocstrings/mkdocstrings/issues/2 。 (3认同)
  • 第二种解决方案看起来不错,但不幸的是它不能完全用于类型提示:`g: Group[int, str] = Group(1, [""]) TypeError: 'type' object is not subscriptable` (2认同)
  • 您仍然无法*使用*“NamedTuple”子类中的“Generic[...]”提示,因为由于完全相同的元类冲突,新生成的命名元组类将不会具有创建所需的“__class_getitem__”钩子像“Group[str, int]”这样的具体提示是有效的。这是因为“NamedTuple”元类返回一个新的类对象,仅将“tuple”作为基类,而不是“Generic”,并且记录可用类型变量的类上必需的“__parameters__”属性完全消失了。 (2认同)