Как стать более буквальным в Python

Пришло время поговорить о литералах 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

Оцените статью
devanswers.ru
Добавить комментарий