#python #python-3.x #type-hinting #mypy #typing
#python #python-3.x #подсказка типа #mypy #ввод
Вопрос:
Я только начинаю разбираться в более сложных типах в Python, в частности, typing.Generic
.
Допустим, у меня есть базовый класс и подкласс этого:
class Base:
def base_method(self):
pass
class Sub(Base):
def sub_method(self):
pass
Теперь я хочу создать класс, который будет иметь переменную экземпляра, которая может быть «a Base
или любым его подклассом». Для этого я должен использовать typing.TypeVar
и typing.Generic
:
class FullyTypedContainer(Generic[BaseOrSubclass]):
def __init__(self, p: BaseOrSubclass):
self._p = p
@property
def p(self) -> BaseOrSubclass:
return self._p
Это отлично работает; тип p
«передается» через BaseOrSubclass
, чтобы языковые серверы, такие как PyLance, видели, что FullTypedContainer(Sub()).p
метод вызывается sub_method()
, но FullyTypedContainer(Base()).p
не вызывается.
Итак, каков тип для этой функции?
def get_random_list_of_containers():
a = random.randint(0, 1)
if a == 0:
return [FullyTypedContainer(Sub()), FullyTypedContainer(Base())]
else:
return [FullyTypedContainer(Base()), FullyTypedContainer(Sub())]
-> typing.List[FullyTypedContainer]
: не указывает типFullyTypedContainer
, и поэтому кажется, чтоget_list_of_containers()[0].p
это рассматривается как типAny
.-> typing.List[FullyTypedContainer[Base]]
: «принуждает» все иметь типBase
(notBase
или один из его подклассов), и поэтомуget_list_of_containers()[0].p
считается, что у него никогда не было методаsub_method()
.-> typing.List[FullyTypedContainer[typing.Union[Base, Sub]]
: кажется, это лучший вариант, но мне потребуется вручную вести список каждого подклассаBase
.
Комментарии:
1. Вам действительно нужны списки? Может быть, это должно быть a
tuple
?2. @juanpa.arrivillaga
tuple
илиlist
не имеет значения; дело в том, что я хочу знать, как ввести значение,Generics
возвращаемое из функции без параметров.3. Ну, это актуально, потому что кортежи могут содержать и могут быть типизированы неоднородно, но списки должны быть однородными
4. @juanpa.arrivillaga Я вижу это сейчас, но на самом деле это противоречит моему вопросу, поэтому я редактирую.
5. Мне не совсем понятно, чего именно вы ожидаете. Поскольку выходные данные являются полностью случайными, проверка типов всегда должна предполагать наихудший случай
Base
. Даже если это »Base
или подкласс», это все равно означает не больше возможностей, чемBase
гарантировано. Для проверки типов нет способа узнать, чтоget_list_of_containers()[0].p
это определенно не простоBase
. Итак, можете ли вы уточнить, что проверка типов должна выводить из аннотации на практике?
Ответ №1:
Здесь есть две проблемы, и обе проблемы связаны с различием универсальных типов: оба List
и ваш пользовательский универсальный тип FullyTypedContainer
являются инвариантными.
Предположим Sub
, что это подтип Base
. Задан общий тип GenType
:
- Если
GenType[Sub]
это подтипGenType[Base]
, то он ковариантен. Многие контейнеры интуитивно ковариантны. - Если
GenType[Base]
это подтипGenType[Sub]
, то он контравариантен. Это звучит нелогично, ноCallable
тип на самом деле контравариантен по отношению к его типам аргументов. Вызываемый объект, который принимаетBase
аргумент, может использоваться там, где нам требуется вызываемый объект, который принимаетSub
аргумент. - Если ни одно из вышеперечисленных не выполняется, то
GenType
является инвариантным.
Тип Python List
инвариантен, и различие Generic
типов зависит от его TypeVar
— и TypeVar
s по умолчанию являются инвариантными. Итак, чтобы правильно ввести аннотацию вашей функции, вам нужно внести два изменения:
- Используйте тип ковариантной последовательности, например
Sequence
. - Сделайте
BaseOrSubclass
переменную типа также ковариантной.
В коде это будет выглядеть так:
BaseOrSubclass = TypeVar("BaseOrSubclass", bound=Base, covariant=True)
class FullyTypedContainer(Generic[BaseOrSubclass]):
def __init__(self, p: BaseOrSubclass): ...
def get_list_of_containers() -> Sequence[FullyTypedContainer[Base]]:
return [FullyTypedContainer(Sub()), FullyTypedContainer(Base())]
Вот более полный пример в mypy-play: https://mypy-play.net/?mypy=latestamp;python=3.10amp;gist=85af8d25521c9d4b8454719685b37fc8
Примечание по List
сравнению Sequence
:
Технически List
не эквивалентно Sequence
:
List
всегда ссылается на тип для встроенногоlist
.Sequence
это любой «подобный списку» тип, который определяет__getitem__
,__len__
, несколько других методов, таких как__reverse__
и т.д.count
Это также охватываетtuple
,str
,bytes
типы и тому подобное.
Комментарии:
1.
List
является лиMutableSequence
2. Я не думаю, что это связано с ковариацией или что добавление ковариации решает проблему. Даже с a
Sequence[FullyTypedContainer[Base]]
и ковариацией вы все равно теряете статические типы каждого элемента (помимо того, что они являютсяBase
s)3. @joel Но, как вы сказали, списки / последовательности однородны, и просто нет способа кодировать типы каждого отдельного элемента.
Sequence[T]
означает, что каждый элемент имеет либо типT
, либо некоторый подтипT
, который, я думаю, является тем, что хочет OP. Если OP хочет кодировать «толькоBase
и его подтипSub
, и ни один из других подтипов», то я согласен, чтоList[FullyTypedContainer[Union[Base, Sub]]]
это более точный тип, но тогда вам все равно нужно сделатьFullyTypedContainer
ковариантным (или использоватьList[Union[FullyTypedContainer[Base], FullyTypedContainer[Sub]]]
).4. @ZecongHu хороший момент
List[FullyTypedContainer[Union[Base, Sub]]
. Я думаюList[FullyTypedContainer[Base]]
, что в любом случае лучше. Возможно, проблема в том, что не совсем ясно, что OP хочет, чтобы функция выполняла. Я основываю свой ответ на том факте, с которым они тестируютсяget_list_of_containers()[0].p
. После индексации ковариация не имеет значения5. извините, как только вы
[0].p
, ковариация не имеет значения
Ответ №2:
списки однородны по своему типу: все элементы имеют одинаковый статический тип. Лучшее, что вы можете сделать, это List[FullyTypedContainer[Base]]
или List[FullyTypedContainer[Union[Base, Sub]]
. Или используйте пользовательскую коллекцию, которая позволяет вам привязывать конкретное содержимое к определенным типам и использовать его вместо списка
Комментарии:
1. Отредактировал мой вопрос, чтобы уйти от этого
tuple
предложения; в реальном случае, с которым я сталкиваюсь, содержимое списка неизвестно во время сценария.