Продолжаем разговор про интересные PEPы. Поговорим сразу про два:
- PEP-823: https://peps.python.org/pep-0823
- PEP-824: https://peps.python.org/pep-0824
В целом они оба об одном: использовании
? для решения проблемы обращения к None при работе с данными.Первый PEP описывает два новых оператора
?. и ?[], которые позволяют представить выражение some?.field как:
if some is not None:
field = some.field
else:
field = None
Аналогично:
some?[field] будет делать тоже самое, но для для some[field].Второй - позволяет использовать оператор
?? вместо is not None и тернарных выражений. Теперь мы сможем писать some ?? 'default' вместо some if some is not None else 'default'. И есть вариант ??= по аналогии с +=, тд.Данная попытка уже не первая. Первая была сделана очень много лет назад в PEP-505, который отклонили.
Но теперь, два данных PEP'а проспонсированы лично Гвидо, так что шансы хорошие.
Примеры
Давайте посмотрим на более реалистичные случаи. Например, если у вас есть вот такая вложенная структура данных, и нужно получить имя покупателя из очень глубокого слоя, то сейчас мы вынуждены писать:
def get_customer_name(data: Data) -> str | None:
"""Get customer name in lower case if it exists."""
customer = data.customer
if customer is not None:
user = customer.user
if user is not None:
return user.name.lower()
return None
Превращается в:
def get_customer_name(data: Data) -> str | None:
return data.customer?.user?.name.lower()
Как будет работать второй вариант? Если
data.customer является None, то мы сразу его возвращаем. Если нет, пробуем получить .user, проверяем его на None. И тд.Важный момент:
__getattr__ и __getitem__ никак не меняются, они просто не вызываются при None.В большинстве языков (PHP/JS/TS/C#/Kotlin/Swift/Rust/Dart) такая фича уже есть, она делает работу с вложенными опциональными данными сильно удобнее.
Типизация
Ничего не меняется:
-
get_customer_name все еще возвращает str | None- Обращение через
. к optional полю - все еще ошибка- Обращение через
?. к optional полю - не ошибка, а просто NoneПерформанс
Самое интересно! Первый вариант
get_customer_name выполняет много опкодов на ровном месте.Условия, сравнения, прыжки,
LOAD_ATTR и тд. Что делает данный код сложным для оптимизации и JIT'ирования. Но data.customer?.user?.name.lower() оптимизировать очень удобно, потому что можно сделать один новый опкод LOAD_OPTIONAL_ATTR (или добавить еще один oparg к текущему LOAD_ATTR), а уже его будет очень удобно оптимизировать. Потому что весь код проверок будет жить в одном месте рядом на C, а не куча условий на питоне. И последовательность таких LOAD_OPTIONAL_ATTR будет удобно находить и возможно оптимизировать в том числе.Почему монадические?
Потому что на заголовок нужно было как-то забайтить! 🌚
Но и контекст, конечно, реально существует. Например в
dry-python/returns уже много лет была доступна конструкция Maybe с методом .bind, который ведет себя прямо как ?. оператор и не продолжает выполнение после первого None / Nothing.Как вы уже поняли, я дикий фанат таких штук. Ждем всем чатом!
Одной строкой
-
dmr@0.16.0 почти готов; изменений просто капец сколько. Каждая часть фреймворка вылизывается и становится на порядок лучше. Релиз в конце #opensource_september - Наша бесплатная конференция 17 октября в Нижнем Новгороде получила двух новых спикеров к 6 текущим. Итого: 8 докладов от топовых питон ютюберов и опенсорсеров! Анонс новых докладов будет в самое ближайшее время. А пока можно регистрироваться
- Обратите внимание, что ждать подтверждения не нужно! Мы стараемся всем сразу высылать письма, но они нужны только для моральной поддержки :) Было много вопросов по статусу письма. Если вы зарегались, то можете приходить! Пустим всех.
Обсуждение: Что вы думаете? Не возникает ли у вас вопросиков по данной фиче? Как вы сейчас решаете данную проблему с boilerplate кодом?