У меня есть функция с несколькими kwargs по умолчанию. Один из них (где-то посередине) — логический переключатель, управляющий типом возвращаемого значения.
Я хотел бы создать две перегрузки для этого метода с Literal[True/False]
, но с сохранением значения по умолчанию.
Моя идея заключалась в следующем:
from typing import overload, Literal
@overload
def x(a: int = 5, t: Literal[True] = True, b: int = 5) -> int: ...
@overload
def x(a: int = 5, t: Literal[False] = False, b: int = 5) -> str: ...
def x(a: int = 5, t: bool = True, b: int = 5) -> int | str:
if t:
return 5
return "asd"
Но mypy вызывает:
error: Overloaded function signatures 1 and 2 overlap with incompatible return types
Я предполагаю, что это потому, что x()
будет конфликтовать.
Но я не могу удалить значение по умолчанию = False
во второй перегрузке, так как ему предшествует arg a
со значением по умолчанию.
Как я могу перегрузить это правильно, чтобы
x()
→ int
x(t=True)
→ int
x(t=False)
→ str
@ mkrieger1, хотя это решило бы эту игрушечную проблему. Я просто хочу добавить типизацию в довольно известный пакет, который на данный момент не типизирован, и об изменении интерфейса только из-за проблем с типизацией не может быть и речи :)
Обратите внимание, что сигнатуры функций в ваших примерах — это просто аргументы, а не обязательно аргументы ключевого слова. Поэтому заголовок должен читаться как «с несколькими необязательными аргументами».
Это старая проблема. Причина в том, что вы указываете значение по умолчанию в обеих ветвях, поэтому x()
возможен в обеих, а тип возврата не определен.
У меня есть следующий шаблон для таких случаев:
from typing import overload, Literal
@overload
def x(a: int = 5, t: Literal[True] = True, b: int = 5) -> int: ...
@overload
def x(a: int = 5, *, t: Literal[False], b: int = 5) -> str: ...
@overload
def x(a: int, t: Literal[False], b: int = 5) -> str: ...
def x(a: int = 5, t: bool = True, b: int = 5) -> int | str:
if t:
return 5
return "asd"
Почему и как? Вы должны подумать о способах вызова вашей функции. Сначала вы можете указать a
, затем t
можно указать как kwarg (#2) или arg (#3). Вы также можете оставить a
по умолчанию, тогда t
всегда будет kwarg (снова № 2). Это необходимо, чтобы не ставить arg после kwarg, то есть SyntaxError
. Перегрузка по более чем одному параметру сложнее, но возможна и таким образом:
@overload
def f(a: int = 1, b: Literal[True] = True, c: Literal[True] = True) -> int: ...
@overload
def f(a: int = 1, *, b: Literal[False], c: Literal[True] = True) -> Literal['True']: ...
@overload
def f(a: int = 1, *, b: Literal[False], c: Literal[False]) -> Literal['False']: ...
@overload
def f(a: int, b: Literal[False], c: Literal[True] = True) -> Literal['True']: ...
@overload
def f(a: int, b: Literal[False], c: Literal[False]) -> Literal['False']: ...
def f(a: int = 1, b: bool = True, c: bool = True) -> int | Literal['True', 'False']:
return a if b else ('True' if c else 'False') # mypy doesn't like str(c)
Можно поиграться с перегрузкой здесь.
Это точно решение, спасибо!
Не то, что вы хотите услышать, но я бы избавился от параметра
t
и создал бы две отдельные функции, каждая с одним типом возвращаемого значения.