Поддержка ввода Python для NamedTuple

Пожалуйста, проверьте приведенный ниже код

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())

Я получаю сообщение об ошибке. В родительском классе A я хочу определить метод f таким образом, чтобы указать, что любой дочерний класс должен переопределить его, возвращая NamedTuple, который состоит только из int или str полей.

Но я получаю сообщение об ошибке:

TypeError: 'NamedTupleMeta' object is not subscriptable

Изменение подписи, как показано ниже, помогает, но тогда как я скажу системе ввода, что дочерний класс может возвращать NamedTuples, которые имеют только int и str

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

person Praveen Kulkarni    schedule 27.02.2020    source источник
comment
Разве вы не можете использовать NT как тип в классе A?   -  person Dan    schedule 27.02.2020
comment
@Dan, по-видимому, однако, OP хотел бы иметь возможность использовать class Foo(NamedTuple): bar: int, и тогда это не сработало бы   -  person juanpa.arrivillaga    schedule 27.02.2020
comment
Теперь, я не особенно хорошо разбираюсь в системах типов, но как это может использовать статическая проверка типов? Если все, что вы знаете, это то, что это какой-то тип, который имеет некоторое количество атрибутов, которые являются typing.Union[str, int], вам все равно придется жаловаться на foo.bar, потому что этот тип не знает о bar...   -  person juanpa.arrivillaga    schedule 27.02.2020


Ответы (1)


Проблема в том, что принципиально typing.NamedTuple не является правильным типом. По сути, это позволяет вам использовать фабрику классов collections.namedtuple с использованием синтаксиса наследования и аннотаций типов. Это сахар.

Это заблуждение. Обычно, когда мы ожидаем:

class Foo(Bar):
    pass

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

всегда печатать True. Но на самом деле typing.NamedTuple с помощью механизма метакласса просто делает что-то потомком tuple, в точности как collections.namedtuple. Действительно, практически единственная причина его существования — использовать NamedTupleMetaclass для перехвата создания класса. Возможно, следующее будет освещать:

>>> 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

Некоторым это может показаться грязным, но, как сказано в Дзен Python, практичность важнее чистоты.

И обратите внимание, люди часто путаются с collections.namedtuple, который сам по себе является не классом, а фабрикой классов. Так:

>>> 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

Обратите внимание, что классы, сгенерированные namedtuple/NamedTuple, действуют, действуют так, как ожидалось, когда вы наследуете от них.

Обратите внимание, ваше решение:

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())

Не проходит 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)

Однако usint Tuple делает:

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

Хотя, это может быть не очень полезно.

Что вам на самом деле нужно, так это какую-то структурную типизацию, но я не могу придумать, как использовать для этого typing.Protocol. По сути, он не может выразить «любой тип с переменным числом атрибутов, все из которых равны typing.Union[int, str].

person juanpa.arrivillaga    schedule 27.02.2020