Pull to refresh
79
0.4
Tishka17 @Tishka17

Пользователь

Send message

Ну вот поэтому у меня в dishka всё полагается на типы и есть достаточно строгая валидация графа

Делают. По классике есть зависимости типа "singletone", а есть"scoped". Если у вашего объекта есть Стейт, который не должен шариться между обработчиками запроса (банально, Коннект к бд) - это scoped зависимость и её надо бы пересоздавать (оптимизации приемлемы, но концептуально, один инстанс не должен одновременно использоваться)

Моя позиция: можно и нужно. А вот когда зависимостей станет много и следить за "скоупами" станет лень, можно брать контейнер. Такой, который по минимуму влезает в существующий код

Я не знаю стандартного определения, это моё описание того что подразумевают под такими инструментами и мой опыт. Когда я пилил dishka я опирался на свой опыт использования "плохих ioc-контейнеров" и у меня вышло то что мне нравится. Если не углубляться в детали, то для использования надо всего 3-4 вызова функций, просто не таких как у вас и тоже не таких как у аналогов.

Уже после релиза dishka мне дали почитать книгу "Внедрение зависимостей в .Net" Марка Симана. Могу порекомендовать для ознакомления, она во многом передает мои представления, хотя по сравнению с ней, я несколько переосмыслил, например, работу со скоупами.

если глобалов 2 - это всё ещё глобалы

Раз уж зашла речь о контейнерахв питоне, я обязан высказаться.

Dependency injection это полезный паттерн, который говорит что мы не должны сами брать зависимости ни из глобальной переменной, ни создавать по месту. Если мы просто делаем глобальный сторадж, который предоставляет доступ к куче разных объектов - это паттерн SericeLocator, к DI он не имеет отношения. DI-фреймворк (IoC-контейнер) может быть похож на него если юзать через глобал, но это не обязательно делать так.

Что же делает Ioc-container? Во-первых, умеет создавать сложную иерархию объектов, передавая одни в другие - собственно реализуя DI. Во-вторых, понимает где надо создать новый объект, а где переиспользовать старый, то есть имеет некоторые скоупы. Хороший контейнер умеет чуть больше: требует минимального вмешательства в сами объекты, умеет в финализацию, изолированные куски графа зависимостей, позднее связывание зависимостей. Я для себя формулировал такой список требований: https://dishka.readthedocs.io/en/stable/requirements/technical.html

Как обращаться к контейнеру? Тут могут быть варианты. В общем случае, он не должен быть фиксированно в одном экземпляре. У нас так же должна быть возможность подменить граф зависимостей для тестов. Я считаю самым удачным решением - использовать возможности фреймворков для передачи инстанса контейнера в конченые обработчики, но это не всегда возможно.

Какие могут быть подводные камни при разработке? Во-первых, сделать удобное api. Для обозначения разных объектов достаточно нативно используют типы. Использовать названия для всех сущностей достаточно сложно, так как среди них могут быть одноимённые и вообще следить за уникальностью имен по большомц проекту нереально. Во-вторых, скоупы можно реализовать по разному, но в любом случае нужен их контроль и иерархия. Я считаю что число скоупов должно быть произвольным, но некоторые разработчики начинают с классических 3, упуская, что уже в классических же либах их стало больше. В-третьих, мне кажется важным чтобы граф зависимостей можно было отлаживать. То есть чтобы ошибки были понятные, чтобы был полный контроль за ним.

Могу предложить посмотреть свой проект dishka, про него уже было несколько статей на Хабре. Там мы постарались сделать очень простое API, реаилзововав всё выше названное и даже больше.

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

Я думаю эта ссылка обязана быть в контексте такой статьи: https://github.com/ag2ai/faststream/

  1. Наследование - полезный инструмент, просто не надо его тащить везде.

  2. Полиморфизм подтипов реализуется через подтипы. Это не обязательно наследование. Интерфейс - это тоже тип, но в некоторых языках для реализации интерфейса надо отнаследоваться от него, в некоторых - указать что вы ег ореализовали, а в третьих просто реализовать и механика языка сама проверит соответствие безо всяких наследований

Всего они выделил 23 паттерна. Ключевая особенность их в том, что почти все из них построены на наследовании.

Ерунда, авторы изначально говорят о том что композиция лучше наследования в большинстве случаев. На наследовании, если мне не изменяет память, основаны только шаблонный метод и парный к нему фабричный метод. Всё остальное - про реализацию интерфейсов и вообще говоря применимо не только к ООП

А ещё есть отличные полезные проверки типа удаления неиспользуемых имортов, которые ломают сгенрированный grpc код, но это уже камень в сторону grpc

Есть очень сомнительные правила вроде требования включать "if TYPE_CHECKING" которые могут сломать рантайм, хотя тайпчекер будет думать что всё ок.

В статье было "Код в asyncio остаётся последовательным", это некорректно. Вы начали спорить с мои возражением против этого, залезая в детали как именно таски переключаются. У вас абстракции потекли

любой поток созданный в пайтон-коде будет использовать GIL.

Использовать - да, занимать - нет. Запустите в этом потоке код, который отпускает GIL и гил будет отпущен. Я не знаю к чему вы прицепились, но изначальный тезис был про то, что потоки, запущенные из питона все ещё могут работать параллельно, так как код в них может gil отпускать. В статье была некорректная формулировка, что для CPU-bound задач потоки, созданные в питоне не подходят. Подходят, с оговорками.

Наличеи async await не далает код конкурентным. Его таким делает create_task/gather. Но это такая же важная часть asyncio. Некорректно говорить, что код с использованием asyncio последовательный. Это зависит от кода - либо он запускает логические "потоки", либо не запускает. И детали работы этих "потоков" влияют на то как код будет выполняться, какие виды синхронизации нужны и т.п.

Да хватит говорить о "потоках низлежащего языка". Если вы написали числодробилку на C (или взяли готовую), которая не порождает потоки сама, то внезано там тоже гил может быть не нужен. Такие места даже в стандартной библиотеке есть (упомянутый выше hashlib прекрасно отпускает gil). Ещё раз: НЕТ НИКАКИХ ПОТОКОВ ЯЗЫКА XXX. Есть код, которому нужен GIL или не нужен, это зависит от кода, который выполняется в потоке, а не от как поток был создан.

Так что «питонячий поток» - это ОС поток + PyThreadState, который использует интерпретатор. Потоки же, порождаемые С-шными либами - это тоже OC потоки, но они могут выполняться реально параллельно (на разных ядрах CPU), т.к. они вообще ничего не знают про GIL. 

Откуда взялись потоки порождаемые сишными либами? Я не про них. Если в потоке, порожденном питоном, отпустить GIL (а это делается в куче случаев, начиная с банального hashlib), то гил отпустится и можно занять несколько ядер.

Чтобы переключить итерацию, вы либо явно await'ите вашу таску

Вы мне сейчас рассказываете разницу между кооперативной и вытесняющей многозадачностью. Она действительно есть. Но наличие кооперативности не делает конкуретный код внезапно последовательным или синхронным.

Триггером для запуска асинк функции в отдельной таске является создание таски из корутины. Когда именно физически она будет запущена зависит от состояния потока и мыслей планировщика.

Вы буквально скакзали что при использовании numpy будут какие-то другие потоки. Хотя потоки у нас одни - потоки ОС. Претензия была к этому. Цитирую: "Только это будут не питонячие потоки (например, numpy)"

Information

Rating
2,656-th
Location
Москва, Москва и Московская обл., Россия
Date of birth
Registered
Activity

Specialization

Backend Developer, Mobile Application Developer
Lead
Python
Docker
Linux
SQL
Git
Golang
Android SDK