Comments 36
на самом деле, в Python есть своя система типов
Шок!
Если же серьёзно, то всё жду поста с рассказом о системах типов от практики, а не от теории. Вроде «вот сложная задача и с формальным подходом к типам её решить на порядок проще, чем без него».
На практике это проблемы связанные с написанием нового и поддержкой существующего кода. С одной стороны, аннотации типов служат документацией и расширяют возможности для автоматической или автоматизированной (с подсказками IDE) генерации кода. С другой, чем раньше удается отловить ошибку, тем меньше стоимость ее исправления. Проверка кода на соответствие типов дает возможность разработчикам крайне дешево отлавливать довольно широкий класс багов непосредственно в процессе кодинга.
Для самого простого примера рекомендую посмотреть библиотеку pydantic. Вместо того чтобы парсить на входе json в dict, мы парсим его в типизированную структуру данных, автоматически валидируя. Не нужно будет каждый раз проверять, а точно ли есть такой ключ словаря (для глубокой вложенности это вообще ад) а точно знаем, допускается или не допускается отсутствие элемента, и даже знаем что за тип данных там лежит.
>даже знаем что за тип данных там лежит
Справедливости ради, библиотека запускает неявные конвертации, например, если в json пришла строка, а в модели описан int, то строка конвертируется в int и кладётся в модельку. А вообще да, очень удобная библиотека, там ещё есть автоматическое чтение настроек из переменных окружения.from pydantic import BaseModel
class MyModel(BaseModel):
value: int # тут таб, но хабр его не отображает
print(MyModel.parse_raw('{"value": "20"}')) -> "value=20"
P.S. Как в этом новомодном интерфейсе набирать комментарии? Где кнопка цитирования? Почему, когда я сделал абзац текстом, я не могу добавить в него пустую строку, вместо этого создаётся новый блок кода?
АТД действительно отличный подход к описанию практически любой проблемы. После того, как привыкаешь к АТД очень грустно возвращаться к языкам без онных и костылить всякие вспомогательные переменные-флаги, навязывать доп логику на значения null, -1 и т.п.
Но не знаю насколько удобно пользоваться АТД в питоне. Не пробовал, все зависит от того, насколько много надо городить избыточного бойлерплейта, чтобы пользоваться АТД. А по статье это непонятно, пока сам не попробуешь не поймёшь.
Вроде «вот сложная задача и с формальным подходом к типам её решить на порядок проще, чем без него».
Практически любая сложная бизнес-логика. Классический пример — это компиляторы. Если писать входной файл генератора, то проще сначала написать парсер на ocamlyacc, а потом на bison (тот же yacc), чем наоборот. Просто потому, что Ocaml хорошо типизирован, а yacc/bison — нет (он всё передаёт через *void).
Итак, алгебраический тип данных — это такой тип данных, который является композицией других типов. Вот такое незатейливое определение.
data Foo = Bar | Baz
Какие типы я тут скомпозировал?
Или вот из вашего же примера:
Например, тип bool — это двухэлементное множество, которое содержит элементы True и False
data Foo = Bar () | Baz ()
т.е. в некотором смысле, это композиция единичных типов.
Или я не прав?
Ну вы в своём примере привели тип bool
как множество из True
и False
. В Haskell этот тип описывается кстати аналогичным образом из моего примера:
data Bool = True | False
Это ведь по прежнему называется алгебраическим типом данных? Я могу быть не прав, но возможно определение алгебраических типов данных как композиция других типов это не исчерпывающее определение, а лишь описание одного из свойств этой сущности.
Если уж наш проект раздулся до размеро, при которых требуется типизация, не проще ли будет достать из широких штанин тот же самый C# и написать сразу на нем?
Что делать, если проект раздувался постепенно и долго, а теперь для доставания C# нужно переписать не одну сотню kloc, на что начальство категорически не согласно? Так-то конечно лучше сразу писать на подходящем инструменте, но тут вечно все упираются в то, что для точного выбора инструментов нужно сначала формализовать требования, а требования постоянно меняются.
Ну вот например я сейчас работаю над высокопроизводительной серверной частью ММО, написаной на питоне. Выкинуть и переписать - бессмысленно, да и разработка на C# или Java выйдет в разы менее продуктивной (если что у меня 10+ лет разработки и на Java и на Python в достаточно крупных проектах и больших компаниях).
Добавлю, что такая типизация ещё отлично помогает IDE с автодополнениями.
То есть от типизации польза примерно такая же как и от typescript для javascript, но без необходимости трансляции и отдельного языка - работать становится гораздо проще и продуктивнее.
Простой пример: есть json с определённой структурой данных. В питоне можно задекларировать это с помощью https://docs.python.org/3/library/typing.html#typing.TypedDict и получить статически типизированный код, который во время исполнения будет работать непосредственно с обычными словарями/списками/строками/числами, прочитанными из json-файла без каких-либо конвертаций. В Java так нельзя - лучшее, что мне там встречалось - это Immutables + Jackson, которые всё равно будут требовать перегенерации кода каждый раз, когда трогаешь исходники. И такие фокусы в них займут сотню строк кода против десятка в питоне.
dropbox.tech/application/our-journey-to-type-checking-4-million-lines-of-python
Вообще он показывает любое отрицание.
NoReturn, согласно PEP484, можно использовать только в качестве возвращаемого значения. То что mypy пропускает в аргументе похоже на баг.
А как отличать разные типы содержащие пустое множество значений? На мой взгляд, они должны быть полностью идентичны, а значит зачем нам плодить сущности? Я понимаю, что в других языках механика объявления таких типов существует, но имхо это больше выглядит как костыль для реализации определенных фич языка, а не алгебраически разные сущности.
Чем это хуже использования типа-суммы? Такой подход плох тем, что можно создать концептуально невалидное значение SignInMethod(), все атрибуты которого будут None.
Так проблема в том, что и в нормальном ООП так никто не делает, это костыли.
Зато можно (и будет лучше) спокойно сделать какой-нибудь абстрактный класс (может и интерфейс, но он не сильно сюда подходит, как по мне) с методом входа, затем переопределить этот метод в наследниках, а не воротить кучу опциональных полей, тем более что такой тип не должен быть валидным, как вы и написали, будет пропускать дополнительные значения.
from abc import abstractmethod, ABC
class Client:
"""
Условный класс для объектов, осуществляющих вход
"""
pass
class SignInMethod(ABC):
@abstractmethod
def sign_in(self, client: Client): ...
class SignInWithEmail(SignInMethod):
def __init__(self, email: str, password: str):
self._email = email
self._password = password
# override только для заглушкек
def sign_in(self, client: Client):
client.sign_in_with_email(
self._email,
self._password
)
Только зачем то нужно передавать Client в SignInMethod. Чтобы открыть дверь, нужно предать замок в ключ и вызвать метод открыть. От такого логического поворота головы коллег могут не выдержать, и начнут лопаться как воздушные шарики.
Это как раз вариант с иерархией классов. Часто тип-сумма (тип Union
в Python) является более простой альтернативой. Представьте, например, что нужен не только метод sign_in
, а метод log
для логирования, и метод save_to_db
для сохранения в базу данных. Получится, что абстрактный класс должен определять, а все конкретные классы должны реализовывать и эти новые методы. А если таких методов будет не 3, а 10? Каждый класс в иерархии должен знать как обрабатывать sign_in
, как логировать какую-то информацию о себе log
, как сохранить в БД save_to_db
, и т.п. Скорее всего, понадобится использовать паттерн Visitor, чтобы как-то разделить ответственности, определять новый интерфейс visitor-а и реализации.
client.sign_in_with_email(
У вас sign_in_with_email
протекает в два места, соответственно, при добавлении нового метода или изменении старого, надо будет лезть и в код клиента и в код метода логина. Скорее всего рано или поздно кто-то что-то забудет из этого.
Интересный вариант. Как будет развиваться этот подход при добавлении нового метода авторизации?
первым делом подумал про sympy
Алгебраические типы данных и Python