Пришло время поговорить о литералах Pythons, и я имею в виду это в буквальном смысле 😄.
Теперь, когда мы убрали эту несмешную шутку с дороги.
Что такое литералы и почему они полезны
Основная мотивация их использования заключается в том, что функции могут иметь аргументы, которые могут принимать только определенный набор значений, а возвращаемые функции значения/типы меняются в зависимости от этого ввода. Общими примерами являются (вы можете найти больше здесь):
Это было бы проблемой, если бы мы не могли знать, какой тип имеет возвращаемое значение. Литералы могут помочь нам указать, что выражение имеет только определенное значение. Если мы объединим это с перегрузкой, то сможем добавить подсказки типа к функциям этого типа. Но прежде чем я перейду к примерам, которые меняют свои возвращаемые типы, давайте начнем с чего-то простого:
from typing import Literal
a: Literal[5] = 5
Проверка типов будет знать, что a
всегда должно быть int 5, и покажет предупреждение, если мы попытаемся это изменить:
Другие примеры
Давайте определим функцию, возвращаемый тип которой меняется в зависимости от входного значения. Но сделаем это без литералов и перегрузки:
def fun(param):
if param == "all":
return "all"
elif param == "number":
return 1
Эта функция принимает аргумент param
и возвращает all
или число 1. Тип возврата этой функции — Literal["all", 1]
, но если мы попытаемся сделать это:
b = fun("number")
b + 1
мы получим предупреждение:
А как насчет этого:
b = fun("all")
b + "all"
Type checker не знает, какой тип возвращает эта функция. Мы можем помочь ему в этом, сделав перегрузку.
Перегрузка
Перегрузка в python позволяет описывать функции, которые имеют несколько комбинаций входных и выходных типов (но только одно определение). Вы можете перегрузить функцию с помощью декоратора overload
, как показано здесь:
from typing import overload
@overload
def f(a: int) -> int:
...
@overload
def f(a: str) -> str:
...
def f(a):
<implementation of f>
Сначала создайте функцию и над ней. Затем добавьте ряд функций с декораторами @overload
, которые будут использоваться для помощи в угадывании типов возврата.
Теперь вернемся к литералам. Как исправить функцию fun
? Легко — перегрузите ее (и добавьте подсказки о типе, чтобы убедиться).
@overload
def fun(param: Literal["all"]) -> Literal["all"]:
...
@overload
def fun(param: Literal["number"]) -> int:
...
def fun(param: Literal["all", "number"]) -> Literal["all"] | int:
if param == "all":
return "all"
elif param == "number":
return 1
Как видите, функция разрослась, но теперь мы можем делать это следующим образом:
b = fun("number")
c = b + 1
без каких-либо предупреждений 😎. И будьте внимательны, если тип возврата изменится:
b = fun("all")
c = b + 1
Ссылки
- PEP 586 — Буквальные типы
- typing#overload