Python 3.6 中的通用 NamedTuple

问题描述 投票:0回答:2

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

我不确定如何才能实现我在这里尝试做的事情,或者这是否可能是某种程度的打字机制中的错误。

python generics python-3.6 typing
2个回答
18
投票

所以这是一个元类冲突,因为在 python 3.6 中,输入

NamedTuple
Generic
使用不同的元类(
typing.NamedTupleMeta
typing.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=[''])

0
投票

老问题,但我想我已经遇到了一些效果很好的东西。根据this答案,我得出以下结论:

from typing import TYPE_CHECKING, TypeVar, Generic
from typing_extensions import reveal_type

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

if TYPE_CHECKING:
    # This is how Group looks like to the type checker
    class Group(tuple[T1, T2], Generic[T1, T2]):
        def __new__(cls, key: T1, group: T2) -> "Group[T1, T2]": ...
        @property
        def key(self) -> T1: ...
        @property
        def group(self) -> T2: ...

else:
    # This is the actual implementation of Group, aka literally a tuple with two boltons
    class Group(tuple):
        def __new__(cls, key, group):
            return tuple.__new__(cls, (key, group))

        @property
        def key(self):
            return self[0]

        @property
        def group(self):
            import os

            os.blow_up_the_world()


# Can unpack/index the Group because its literally a tuple
G1 = Group(1, "hello")
key1, grp1 = G1
key1 = G1[0]
grp1 = G1[1]
key1 = G1.key
grp1 = G1.group

# Types are ok
if TYPE_CHECKING:
    reveal_type(key1)  # Revealed type is "builtins.int"
    reveal_type(grp1)  # Revealed type is "builtins.str"
    reveal_type(G1.key)  # Revealed type is "builtins.int"
    reveal_type(G1.group)  # Revealed type is "builtins.str"
    reveal_type(G1[0])  # Revealed type is "builtins.int"
    reveal_type(G1[1])  # Revealed type is "builtins.str"

# And it *is* a generic
G2 = Group(False, 1.0)
key2, msg2 = G2
if TYPE_CHECKING:
    reveal_type(key2)  # Revealed type is "builtins.bool"
    reveal_type(msg2)  # Revealed type is "builtins.float"

mypy 游乐场

也就是以非常小心的方式直接向类型检查器撒谎。对于这样的伎俩一定要小心。类型检查器不会在这里保护您免受自己的伤害,因此最好坚持非常简单的行为并编写大量您否则认为微不足道的测试。

P.S.:你知道吗,你可以将哈希值粘贴到 mypy-play 链接中,这样就可以了(!):DD

© www.soinside.com 2019 - 2024. All rights reserved.