简体   繁体   中英

Python typing support for NamedTuple

Please check the below code

import typing
import abc


class A(abc.ABC):
    @abc.abstractmethod
    def f(self) -> typing.NamedTuple[typing.Union[int, str], ...]:
        ...


class NT(typing.NamedTuple):
    a: int
    b: str


class B(A):

    def f(self) -> NT:
        return NT(1, "s")


print(B().f())

I get an error. In parent class A I want to define method f such that I indicate that any child class should override it by returning a NamedTuple that is made up of int ot str fields only.

But I get a error sayin that:

TypeError: 'NamedTupleMeta' object is not subscriptable

Changing the signature as below helps but then how will I tell typing system that the child class can return NamedTuples's that have only int and str's

class A(abc.ABC):
    @abc.abstractmethod
    def f(self) -> typing.NamedTuple:
        ...

The issue is that fundamentally typing.NamedTuple is not a proper type. It essentially allows you to use the class factory collections.namedtuple using the syntax of inheritance and type annotations. It's sugar.

This is misleading. Normally, when we expect:

class Foo(Bar):
    pass

foo = Foo()
print(isinstance(foo, Bar))

to always print True . But typing.NamedTuple actually, through metaclass machinery, just makes something a descendant of tuple , exactly like collections.namedtuple . Indeed, practically its only reason to exist is to use the NamedTupleMetaclass to intercept class creation. Perhaps the following will be illuminating:

>>> from typing import NamedTuple
>>> class Employee(NamedTuple):
...     """Represents an employee."""
...     name: str
...     id: int = 3
...
>>> isinstance(Employee(1,2), NamedTuple)
False
>>>
>>> isinstance(Employee(1,2), tuple)
True

Some may find this dirty, but as stated in the Zen of Python, practicality beats purity.

And note, people often get confused about collections.namedtuple which is itself not a class, but a class factory. So:

>>> import collections
>>> Point = collections.namedtuple("Point", "x y")
>>> p = Point(0, 0)
>>> isinstance(p, collections.namedtuple)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: isinstance() arg 2 must be a type or tuple of types

Although note, the classes generated by namedtuple / NamedTuple do act as expected when you inherit from them.

Note, your solution:

import typing
import abc


class A(abc.ABC):
    @abc.abstractmethod
    def f(self) -> typing.Tuple:
        ...


class NT(typing.NamedTuple):
    a: int
    b: str


class B(A):

    def f(self) -> NT:
        return NT(1, "s")


print(B().f())

Doesn't pass mypy:

(py38) juan$ mypy test_typing.py
test_typing.py:18: error: Return type "NT" of "f" incompatible with return type "NamedTuple" in supertype "A"
Found 1 error in 1 file (checked 1 source file)

However, usint Tuple does:

class A(abc.ABC):
    @abc.abstractmethod
    def f(self) -> typing.Tuple[typing.Union[str, int],...]:
        ...

Although, that may not be very useful.

What you really want is some sort of structural typing, but I can't think of any way to use typing.Protocol for this. Basically, it can't express "any type with with a variadic number of attributes all of which are typing.Union[int, str] .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM