Как стать автором
Обновить

Комментарии 42

Прикольно! В питоне тоже есть typescript)

Не совсем, вы сможете выполнить код на питоне даже если в типах указан str, а вы подаете на вход int, просто линтер будет ругаться, а код будет выполняться:). Не знаю, как в TS это сделано - скорее всего, код там работать не будет, я прав?

В TS тоже есть режим, при котором компилятор ругается, но всё равно компилирует некорректный код.

Классная статья! Вам удалось собрать в одной статье много полезных советов. Я сам пишу и на Rust и на Python, тоже пытаюсь использовать "плюшки" Rust везде, где можно, но некоторые способы продемонстрированные здесь более лаконичные чем мои. Спасибо)

Единственное что, разве в Python с его GIL нужны мютексы? Разве что для модуля multiprocessing какого-нибудь.

Это перевод. Можете попробовать спросить автора в комментариях к его посту на Reddit.

А, не заметил)

GIL избавляет вас от повреждения памяти, и гарантирует что любые объекты, с которыми вы работаете, остаются корректными объектами Python, но он не защищает состояние этих объектов от гонок.


Банальный код вроде foo.bar += 1 уже содержит гонку если объект foo разделяемый и не защищён мьютексом.

Насколько я помню, это верно даже для кода i += 1, потому что она компилируется в несколько инструкций байт-кода Python, а значит, не атомарна.

Ну, переменные редко бывают разделяемыми между потоками, поэтому я и привёл чуть-чуть более сложный пример. А так да, i += 1 не атомарна.

Стоит добавить что GIL в каком-то смысле гарантирует бОльшую производительность (особенно для однопоточных приложений), потому что потокобезопасные структуры данных мало того что требуют дополнительного времени на качественную разработку и отладку, так еще и зачастую просаживают производительность, т.к. сами по себе используют различные примитивы синхронизации.

Даже lock-free структуры используют в том или ином виде атомарные операции CPU (cmpxchg и его друзья), что примерно раза в 2 медленнее чем подобные неатомарные операции.

Поэтому GIL, можно сказать, гарантирует отсутствие головной боли у контрибьюторов CPython :)

Добавьте проверку во время выполнения, чтобы убедиться, что нормализованная ограничивающая рамка действительно нормализована

Использует assert

Другим подходом может быть полное удаление метода close и просто использование клиента в качестве менеджера контекста

Автор, видимо, не в курсе что менеджеры и были созданы для решения данной задачи

По поводу мьютексов: менеджеры работают и тут https://docs.python.org/3/library/threading.html#using-locks-conditions-and-semaphores-in-the-with-statement

Правда, поскольку нет системы владения, нет смысла и передавать в мьютекс что-то (хотя могли бы фичу такую добавить)

Автор, видимо, не в курсе что менеджеры и были созданы для решения данной задачи

Автор в курсе. В одном из параграфов об этом упоминается:

Блокировки Python реализуют интерфейс context manager, что означает, что вы можете использовать их в блоке with,
чтобы быть уверенными, что они автоматически разблокируются в конце области
действия.

В Питоне есть незаслуженно забытый TypedDict, позволяющий фиксировать по типам содержимое словаря. Хорош, чтобы вернуть из функции 3-4 разных значения, но при этом dataclass делать не хочется, так как больше нигде они пачкой не идут.

Так ведь TypedDict тоже надо объявлять. Только датакласс вам ещё сгенерирует конструктор, проверит типы, и вообще сделает всю рутину

TypedDict лучше оставить для легаси, которое использовало словари, и API которого так просто не изменить

Типизированные словари лучше ложатся на SELECT запросы к базам данных.

Если ключ employee_pk в словаре TaskSelectDict содержит None, значит клиент хочет получить задачи, к которым не назначен исполнитель.

Если этого ключа в словаре нет -- клиент хочет получить задачи, независимо от того назначен им исполнитель или нет.

В случае же с классами данных надо придумывать другой способ логического разделения этих двух случаев использования.

Только датакласс вам ещё сгенерирует конструктор

У словарей по умолчанию есть конструктор.

проверит типы

Если речь о статической проверке, MyPy умеет проверять типы и для TypedDict, и для dataclass.

Во время исполнения -- ни TypedDict, ни dataclass никаких проверок типов не делают. Если, конечно, Вы не имели ввиду dataclass из библиотеки Pydantic.

Не хочу разводить холивар, но в сущности вы изобрели более страшную и менее производительную версию С# :)

Не холивар, так обесценивание)) Красота дело субьективное, конечно. Но с точки зрения питона это большой и отличный шаг вперёд.

Идея понятна, но на мой взгляд у каждого языка есть некоторое сочетание компактности/безопасности, нарушение которого увеличивает накладные расходы и превращает этот язык в другой язык.

Наверное проще сразу взять другой язык, а в питоне просто больше интеграционных тестов написать.

Но жменьку полезных советов оценил.

db = Database()
car_id = db.get_car_id("Mazda")
driver_id = db.get_driver_id("Stig")
info = db.get_ride_info(driver_id, car_id
)

Заметили ошибку?

Тут можно так же использовать keyword-only аргументы

class Database:    
    def get_car_id(self, brand: str) -> int:    
    def get_driver_id(self, name: str) -> int:    
    def get_ride_info(self, *, car_id: int, driver_id: int) -> RideInfo

В итоге имя аргумента придется задавать явно, например следующий вызов будет смотреться как минимум странно:

info = db.get_ride_info(car_id=driver_id, driver_id=car_id)

Попытка вызова метода без явного указания имени аргументов будет вызывать ошибку при попытке запуска скрипта.

Но, конечно же, в таком случае нельзя полагаться на то, что mypy или другой анализатор выдаст ошибку. Но зачастую такой подход тоже полезен.

Можно создать разные типы с помощью NewType https://docs.python.org/3/library/typing.html#typing.NewType

Тогда CarId и DriverId будут разные типы, которые нельзя будет смешивать (типо разные BBox из статьи), но в некоторым местах надо будет кастить, что не особо удобно, особенно в тестах, потому что там будет много таких кастов, но терпимо.

def get_car_id():
    result = ...
    return CarId(result)

Связка питона и раста лучшая для ML и всей остальной черной работы.

У меня такое впечатление, что производительность тут явно будет принесена в жертву безопасности.

Какие основания для такого впечатления? Это проверки времени компиляции.

Какого же времени компиляции, всё в рантайме будет создаваться и оперироваться более сложными типами.

всё в рантайме будет создаваться

Расскажите пожалуйста что именно "всё" и какими более сложными типами оперировать. PEP-484 вам в помощь.

Очевидно, датаклассы. Когда счёт объектов идёт на тысячи — уже начинает быть заметно, что манипуляции с датаклассами в несколько раз медленнее манипуляций с кортежами. А ещё вы не использовали slots, на чём потеряли ещё несколько процентов производительности


UPD: а впрочем, есть же typing.NamedTuple. Если задача не требует мутабельных объектов, то, наверно, можно попробовать позаменять датаклассы на NamedTuple, совместив таким образом удобство датаклассов и скорость кортежей. А если мутабельность всё-таки нужна, то там же рядышком есть typing.TypedDict — в рантайме обычный dict, немножко медленнее чем кортежи, но всё ещё быстрее датаклассов

Очевидно, датаклассы.

Только они или как было заявлено "всё"? По ним согласен.

NewType тоже.

Changed in version 3.10: NewType is now a class rather than a function. There is some additional
runtime cost when calling NewType over a regular function. However, this
cost will be reduced in 3.11.0.

-----

Ну хорошо, не всё вообще, а всё что заменяет примитивные типы на более сложные.

Вряд ли она просядет сильно (да и если экономить на всех спичках — в первую очередь надо менять язык). Кроме того, часть советов тут вообще никак не влияет на рантайм.

Производительность уже принесена в жертву в момент выбора питона.

Совсем не факт, особенно в сложных приложениях. Мне как-то пришлось переписывать с С++/STL на Java некий компонент телекоммуникационого приложения - просто "переведенный" построчно, он уже работал почти в 2 раза быстрее за счет более оптимизированных библиотек.
Не говоря уже о времени, которое занимает написание одного и того же кода...

за счет более оптимизированных библиотек

... которые написаны на чём? На сишечке. И при чём тут питон тогда?

А какая разница на чем оно под капотом написано если ты этого никогда не видишь и не трогаешь?

При том, что Питон - это гораздо более удобное средство вызова "оптимизированных библиотек", чем С/С++. Позволяющее быстро получить работающий код, а потом - если такая нужда возникнет - его оптимизировать. А не наоборот, что как известно - "the root of all evil".

А чо сразу C/C++? В мире вагон других языков.

Поправьте, если не прав, но ведь альтернативные конструкторы (секция про функции-конструкторы) создаются через @classmethod, а не @staticmethod, нельзя же внутри метода класса прямо ссылаться на него.

К тому же, в лоб указывать тип "Rectangle" тоже идея не лучшая: получится, что отнаследованные классы всё ещё будут возвращать Rectangle, а не себя. Сейчас (начиная с 3.11) эту напасть можно решать через typing.Self, раньше как-то нужно было оперировать на typing.TypeVar.

А статья неплохая, нашёл полезные моменты, спасибо.

Про typing.Self в статье есть упоминание в последнем параграфе соответствующей части.

Я так понял, что то, что через @staticmethod создаётся, то не очень конструктор, а больше фабричный метод. Если мне память насчёт GoF не изменяет. :)

Дам ещё один совет (он у меня вырос на распробовании Haskell - но Rust, во многом, его идеями вдохоновлялся (в купе с zero cost abstractions)).

- Стараться пользоваться иммутабельными данными
- Создавать новые структуры данных при необходимости (используя map \ filter \ reduce)
- в развитии предыдущего: стараться, чтобы каждой переменной вы могли дать обозначение в терминах "бизнес-логики" что примерно эквивалентно "одинаковому уровню абстракции в рамках одной функции".

А в чем смысл статьи? "Используйте строгую типизацию в питоне"? Так все используют, иначе это адский ад, прямо как в нативном JS

Вы можете спросить о смысле автора на Реддит. В конце есть ссылка.

Если пробежитесь по разделам статьи то заметите, что речь там не только про строгую типизацию.

По поводу "все используют" хотелось бы подкрепления фактами.

В питоне есть встроенная типизация уже, typing практически не нужен, есть list, dict, any и тп. По поводу Dataclass - можно использовать NamedTuple.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории