PythonJuniorTechnical
Чем Optional[X] отличается от X | None?
Семантически эквивалентны: typing.Optional[X] — alias для Union[X, None]; X | None — синтаксис PEP 604, Python 3.10+. Оба означают «значение типа X или None». Это про nullable type, НЕ про «параметр необязательный» — обязательность задаёт наличие default value.
Что означают
typing.Optional[X]— alias дляtyping.Union[X, None]. Введён в Python 3.5 с модулем typing.X | None— синтаксис union types (PEP 604), доступен в аннотациях с Python 3.10. Используетtypes.UnionType,isinstance(v, int | str)работает в runtime.- Семантически эквивалентны: mypy, pyright, pyre трактуют их одинаково.
Это про тип, не про обязательность
Распространённая путаница: Optional[X] ≠ «параметр необязательный». Обязательность аргумента определяется только наличием default value в сигнатуре.
# обязательный, может быть None
def f1(name: str | None) -> None: ...
f1() # TypeError: missing required argument
f1(None) # OK
# необязательный, по умолчанию None
def f2(name: str | None = None) -> None: ...
f2() # OK
# необязательный, default не None
def f3(name: str = "anon") -> None: ...
f3() # OK
Когда что использовать
- Python 3.10+ — пишите
X | None; короче и не требует импорта. - Поддержка 3.9 и старше —
Optional[X]илиfrom __future__ import annotations(annotations станут строками, можно использовать новый синтаксис, но runtime-генерик от 3.9 не получите). - В сложных union:
str | int | Noneчитается лучше, чемOptional[Union[str, int]].
Narrowing после проверки
def greet(name: str | None) -> str:
if name is None:
return "hi, anon"
# mypy/pyright теперь знают: name: str (None отброшен)
return name.upper()
def length(x: str | int | None) -> int:
if x is None:
return 0
if isinstance(x, str):
return len(x) # x: str
return x # x: int
# walrus + narrowing
def get_user(d: dict[str, str | None]) -> str:
if (name := d.get("name")) is not None:
return name.upper() # name: str
return "anon"
Runtime-нюансы
- В 3.10+
isinstance(x, int | str)работает (PEP 604). - В 3.9 и ниже нужен
isinstance(x, (int, str)). Optional[X]в runtime =Union[X, None];typing.get_args(Optional[int]) == (int, NoneType).- Pydantic v2 различает
Optional[X]как nullable и default; дляX | None = Noneполе опционально и принимает null.
Подводные камни
- Думать, что
Optional[X]делает аргумент опциональным — нет, нужен default. - Смешивать стили
Optional[X]иX | Noneвнутри одного проекта — выбирайте один. - Использовать
X | Noneв коде, который должен запускаться на 3.9 безfrom __future__ import annotations— SyntaxError на этапе импорта. - Не сужать тип после
if x is None— линтер ругается на доступ к атрибутам Optional. - Pydantic v1 vs v2: разное поведение для
Optionalполей без default — лучше всегда указывать default явно. - В словарях
dict.get("k")возвращаетX | None; типизатор требует narrowing перед использованием.
Common mistakes
- Говорить, что Optional делает параметр необязательным.
- Не знать Union[X, None].
- Игнорировать target Python version.
What the interviewer is testing
- Объясняет semantic equivalence.
- Различает None-ability и default.
- Знает современный синтаксис.