Мне интересно, как (или если это возможно в настоящее время) выразить, что функция вернет подкласс определенного класса, который приемлем для mypy?
Вот простой пример, когда базовый класс Foo
наследуется Bar
и Baz
, и есть вспомогательная функция create()
, которая будет возвращать подкласс Foo
(либо Bar
, либо Baz
). ) в зависимости от указанного аргумента:
class Foo:
pass
class Bar(Foo):
pass
class Baz(Foo):
pass
def create(kind: str) -> Foo:
choices = {'bar': Bar, 'baz': Baz}
return choices[kind]()
bar: Bar = create('bar')
При проверке этого кода с помощью mypy возвращается следующая ошибка:
ошибка: несовместимые типы в присваивании (выражение имеет тип "Foo", переменная имеет тип "Bar")
Есть ли способ указать, что это должно быть приемлемым / допустимым. Что ожидаемое возвращение функции create()
не является (или не может быть) экземпляром Foo
, а вместо этого является его подклассом?
Я искал что-то вроде:
def create(kind: str) -> typing.Subclass[Foo]:
choices = {'bar': Bar, 'baz': Baz}
return choices[kind]()
но этого не существует. Очевидно, в этом простом случае я мог бы сделать:
def create(kind: str) -> typing.Union[Bar, Baz]:
choices = {'bar': Bar, 'baz': Baz}
return choices[kind]()
но я ищу что-то, что обобщает на N возможных подклассов, где N - это число больше, чем я хочу определить как тип typing.Union[...]
.
У кого-нибудь есть идеи о том, как сделать это не замысловато?
В вероятном случае, когда не существует запутанного способа сделать это, я знаю ряд неидеальных способов обойти проблему:
- Обобщить тип возвращаемого значения:
def create(kind: str) -> typing.Any:
...
Это решает проблему типизации с присваиванием, но является обломом, поскольку уменьшает информацию о типе возврата сигнатуры функции.
- Игнорировать ошибку:
bar: Bar = create('bar') # type: ignore
Это подавляет ошибку mypy, но это тоже не идеально. Мне нравится, что это делает более ясным, что bar: Bar = ...
был преднамеренным, а не просто ошибкой кодирования, но подавление ошибки все еще не идеально.
- В ролях:
bar: Bar = typing.cast(Bar, create('bar'))
Как и в предыдущем случае, положительной стороной этого является то, что он делает возврат Foo
к назначению Bar
более намеренно явным. Это, вероятно, лучшая альтернатива, если нет способа сделать то, что я просил выше. Я думаю, что часть моего отвращения к его использованию - это грубость (как в использовании, так и в удобочитаемости) как обернутой функции. Может быть, это просто реальность, поскольку приведение типов не является частью языка - например, create('bar') as Bar
, или create('bar') astype Bar
, или что-то в этом роде.