Этот спор древен примерно также как и вопрос: -табы или пробелы? Однажды из-за этого я умудрился даже с коллегами поругаться, поэтому давайте разберёмся из-за чего бывают конфликты у задротов 🤔
Суть в следующем: допустим у нас есть произвольный тип T, но мы хотим задать такой тайпхинт, чтобы иметь возможность оставить переменную с типом Т неопределенной. Как мы это сделаем?
Существует 3 решения:
a: T = None
b: Optional[T] = None
c: Union[T, None] = NoneДалее подробнее про каждый из вариантов.
Вариант A:
Самый простой и некоторые линтеры вполне корректно его обработают, подменив на вариант B, но заточенные на типизацию вроде mypy и pyright дадут вам смачного леща, потому что ну а какого хрена вы присвоили неверный тип, а??
Вариант B:
Самый распространенный вариант и куда лучше чем A, но идейно неверный. Представьте следующее выражение:
def f(b: Optional[T]): passНа уровне логики аргумент b является опциональным, но в то же время, если мы его не передадим в функцию, то программа упадет с ошибкой, т к фактически на уровне кода b является обязательным. Какая-то суперпозиция получается 🤔 Дело в том, что на самом деле понятия опционального аргумента и аргумента, способного принимать None - это совершенно разные и не связанные понятия, которые ошибочно были склеины в классе Optional. В помойку его!
Вариант C:
Фактически это аналог записи вида
c: T | None появившейся с версии 3.10, но если вы используете версию младше или пишете обратносовместимый код, то это тот самый вариант, который верен и на уровне логики и на уровне кода. Его я и считаю правильным использовать.Давайте рассмотрим способы возможного применения последнего варианта в разрезе опциональности. Допустим у нас есть класс пользователя:
class User:
id: int
name: Union[str, None]
last_name: Union[str, None] = NoneИз записи атрибутов следует следующая логика:
* id - атрибут типа int, который должен быть обязательно указан.
* name - атрибут, который может быть как str, так и None, но должен быть обязательно указан.
* last_name - атрибут, который может быть как str, так и None, но является опциональным т к он уже имеет дефолтное значение.
Хинт: для того чтоб быстро зарефачить проект с варианта B на вариант C можно воспользоваться утилитой no-optional.
Установка:
pip install no-optionalРефакторинг одного файла:
no_optional <filename>А следующей командой можно отредактировать весь проект целиком:
find <project_folder> -iname "*.py" | xargs python -m no_optional#dev