Generic NamedTuple in Python 3.6

后端 未结 1 489
后悔当初
后悔当初 2021-01-04 07:07

I\'m trying to create a generic version of a NamedTuple, as follows:

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

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


        
1条回答
  •  伪装坚强ぢ
    2021-01-04 07:53

    So this is a metaclass conflict since in python 3.6 the typing NamedTuple and Generic use different metaclasses (typing.NamedTupleMeta and typing.GenericMeta), which python can't handle. I'm afraid there is no solution to this, other than to subclass from tuple and manually initialise the values:

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

    Due to PEPs 560 and 563 this is fixed in 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=[''])
    

    Of course in python 3.7 you can just use a dataclass which are less lightweight (and mutable) but serve similar purposes.

    from dataclasses import dataclass, astuple
    from typing import Generic, TypeVar, List
    
    T1 = TypeVar('T1')
    T2 = TypeVar('T2')
    
    @dataclass
    class Group(Generic[T1, T2]):
    
         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)
    

    How well type checkers handle my solution / yours in python 3.7 though I haven't checked. I suspect it may not be seamless.


    Edit

    I found another solution -- make a new metaclass

    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 讨论(0)
提交回复
热议问题