【问题标题】:Generic NamedTuple in Python 3.6Python 3.6 中的通用 NamedTuple
【发布时间】:2019-08-03 09:34:28
【问题描述】:

我正在尝试创建 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]

但是,我收到以下错误:

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

我不确定还有什么方法可以实现我在这里尝试做的事情,或者这可能是某种程度的打字机制中的错误。

【问题讨论】:

  • 您可以进一步详细说明,您打算在哪些方面概括 NamedTuple,在我看来这是相当笼统的。从您的代码 sn-p 我无法识别...
  • 特殊的NamedTuple 不支持任何其他基类。句号。 NamedTuple 基类将所有使用都委托给一个元类,该元类将运行 collections.namedtuple() 并进行少量定制,这意味着生成的类只继承自 tuple。这是NamedTuple 的一个更广泛的问题,不限于Generic

标签: python generics python-3.6 typing


【解决方案1】:

所以这是一个元类冲突,因为在 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=[""])

由于 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 中,您可以只使用轻量级(且可变)但用于类似目的的数据类。

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=[''])

【讨论】:

  • 第二种解决方案看起来不错,但不幸的是它不能完全用于类型提示:g: Group[int, str] = Group(1, [""]) TypeError: 'type' object is not subscriptable
  • 你仍然不能在NamedTuple 子类中使用 Generic[...] 提示,因为由于相同的元类冲突,新生成的命名元组类不会有__class_getitem__ 钩子需要像Group[str, int] 这样的具体提示起作用。这是因为NamedTuple 元类返回了一个新的类对象,其中只有tuple 作为基类,而不是Generic,并且记录可用类型变量的类上必需的__parameters__ 属性完全没有了。
  • 请在您的回答中更明确地说明这一点,因为您需要在使用该子类的 所有 模块中进行编译器开关。
  • 并使用 dataclass + __iter__ 方法解决方法,当您进行解构赋值时,由于astuple 的限制,生成的变量为Any 类型(此问题github.com/python/mypy/issues/5152)但我猜这是我们在可预见的未来能做的最好的事情......在 mypy 中支持通用命名元组的相关问题已经开放和未修复 5 年了:github.com/python/mypy/issues/685
  • 在 python 3.9 中,OP 的原始代码现在在类声明时中断,即使该类未使用,错误 TypeError: Multiple inheritance with NamedTuple is not supported
猜你喜欢
  • 1970-01-01
  • 2011-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-12
  • 1970-01-01
  • 2017-06-05
相关资源
最近更新 更多