Получи случайную криптовалюту за регистрацию!

Советы разработчикам (python и не только)

Логотип телеграм канала @advice17 — Советы разработчикам (python и не только) С
Логотип телеграм канала @advice17 — Советы разработчикам (python и не только)
Адрес канала: @advice17
Категории: Технологии
Язык: Русский
Количество подписчиков: 4.59K
Описание канала:

Советы для разработчиков ПО от @Tishka17
Поддержать материально по кнопке в канале, либо https://www.tinkoff.ru/cf/2NkdXaljivI
Programming, python, software architecture и все такое

Рейтинги и Отзывы

3.00

2 отзыва

Оценить канал advice17 и оставить отзыв — могут только зарегестрированные пользователи. Все отзывы проходят модерацию.

5 звезд

0

4 звезд

0

3 звезд

2

2 звезд

0

1 звезд

0


Последние сообщения

2022-08-26 12:46:11 Управление памятью в Python

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

Когда вы создаете какой-то объект, Python сам решает как выделить ему память. Как было упомянуто в прошлой статье, иногда вместо выделения новой ячейки памяти, Python может вернуть ссылку на уже созданный экземпляр.
Когда же объект становится не нужен, он удаляется и память освобождается. Под "ненужным" имеется ввиду тот, на который нет активных ссылок, либо есть только циклические ссылки с другими объектами. В зависимости от реализации удаление объекта может происходить сразу, как только пропадут все ссылки на него, или с задержкой (например, при использовании периодического сборщика мусора). В частности, CPython использует счетчики ссылок (именно их защищает GIL).

Есть несколько мест, которые часто понимаются неверно:

* оператор del - удаляет ссылку на объект. Это может быть удаление переменной, ключа в словаре, элемента/слайса списка. Сам объект при этом не меняется и не удаляется, если нет других причин для этого. Как правило, нет причин делать del имяпеременной, вместо этого лучше ограничить скоуп существования переменной введя дополнительную функцию.

* магический метод __del__ - вызывается при удалении объекта. В подавляющем большинстве случаев вы не должны его переопределять. Так как мы не знаем, когда произойдет удаление объекта, лучше использовать контекстные менеджеры для финализации работы с объектом. Кроме того, в некоторых ситуациях (например, при завершении процесса интерпретатора), __del__ вообще не будет вызван.

* модуль gc в CPython предоставляет интерфейс к сборщику циклических ссылок. Его можно отключить и это не повлияет на удаление объектов при достижении нуля счетчиком ссылок. Самостоятельные вызовы gc.collect() при включенном сборщике скорее всего не имеют смысла.

* удаление Python-объектов не обязано сразу уменьшить количество занимаемой процессом ОЗУ. CPython запрашивает у ОС память крупным блоками и самостоятельно в них располагает свои объекты, соответственно и возврат этих областей памяти происходит не сразу.

Дополнительные материалы:
* https://habr.com/ru/company/ruvds/blog/441568/
* https://habr.com/ru/post/417215/
* https://en.cppreference.com/w/cpp/memory/shared_ptr
* https://habr.com/ru/company/vk/blog/559794/
1.1K views09:46
Открыть/Комментировать
2022-08-25 18:48:44 Ссылки и `is`

В Python переменная - это имя, ссылающееся на какой-то объект в памяти. Каждый раз, когда вы присваиваете переменную, вы заставляете указывать её на другой объект, при этом предыдущее значение переменной продолжает жить своей жизнью. Когда же вы обращаетесь к объекту и используете специфические для него операции - они уже могут менять сам объект. Например, append меняет список, добавляя туда новую ссылку. А вот оператор +, как правило, не меняет исходный объект, а возвращает новый.

Стоит отметить, что некоторые операции могут для разных типов объектов быть как мутирующими, так и нет. В частности, оператор += является комбинацией оператора присвоения = и вызова метода __iadd__, который ведет себя по-разному для изменяемых и неизменяемых типов данных (например списков и строк). Таким образом, вызывая += вы одновременно что-то делаете с объектом и присваиваете новую ссылку.

Кроме оператора присвоения ссылки могут появляться при передаче значений в функцию, создании функции, при добавлении в коллекцию, задании атрибута, импорте и т.п. Механика работы ссылок одинаковая и не зависит от типа объекта - изменяемые и неизменяемые передаются одинаково. Отличается только логика работы самих классов.

В отличие от других языков, имеющих механику передачи по указателю или по значению, питон всегда передает объект "по указателю". Копирование объекта делается только явно, для этого если отдельный модуль copy и соответствующие дандер методы в классе __copy__ и __deepcopy__.

Для работы со ссылками в питоне есть ещё один оператор - is. Он нужен для того, чтобы удостовериться, что два его аргумента ссылаются на один и тот же объект в памяти. Его поведение невозможно переопределить в отличие от __eq__ и это помогает нам при проверках на None или значений Enum типа, которые имеют конкретные экземпляры, существующие в рамках процесса в единственном числе (так же в рамках процесса могут существовать только по одному экземпляру True и False, но проверять их при помощи оператора is не рекомендуется согласно PEP8). В отличие от Enum, многие типы не имеют фиксированного набора значений, не могут быть заранее созданы в памяти и поэтому одни и те же значения могут создаваться в памяти много раз. Для изменяемых типов это определяется логикой их работы, для неизменяемых же возможны оптимизации.
Например, если мы создали в памяти два пустых списка [] - обязаны быть разные объекты, так как предполагается, что они будут наполняться независимо. Однако, если мы создаем в памяти два числа 1, они будут постоянны и в некоторых случаях Python может создать только один объект и дать на него две ссылки. Это поведение не гарантировано, зависит от используемых чисел, способа запуска кода, интерпретатора и часто служит предметом споров, манипуляций на собеседованиях и причиной ошибок.

Оператор is актуально использовать только если ваша логика действительно требует отличать один и тот же объект и равные, либо в таких особенных случаях как проверка на None, Enum.

Дополнительные материалы:
https://docs.python.org/3/reference/datamodel.html
https://en.cppreference.com/w/cpp/memory/shared_ptr
https://docs.python.org/3/library/enum.html#comparisons
1.3K viewsedited  15:48
Открыть/Комментировать
2022-08-15 17:30:53 Pull, poll, pool, spool


Есть несколько терминов, которые для русского уха звучат одинаково:

1. pull - с английского переводится как "тянуть". Идет в паре с термином push.

Как правило, термином pull обозначают команду получения данных с сервера. Соответственно, push отправляет их на сервер.

Ещё pull может подразумевать режим, когда получатель данных сам стягивает их к себе из источника. В случае же push режима, источник данных самостоятельно засылает их получателю.

Термин применяется, например, когда мы говорим о методике сбора метрик работы приложения.

2. poll, polling - опрос.

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

В случае HTTP API клиент производит периодический опрос сервера на предмет наличия новых данных. В противоположность этому, при использовании же websockets, между клиентом и сервером есть постоянно соединение по которому передаются сообщения Если же используется webhook, то сервер сам соединяется с нашим приложением по HTTP для уведомления о наличии новых данных. В зависимости от реализации, такой запрос может содержать сами данные или только уведомление об их наличии.

Так же есть режим long polling, который от обычного polling отличается тем, что при отсутствии новых данных сервер не возвращает пустоту сразу, а ещё какое-то время держит соединение открытым.

Эти термины применяются при разработке веб приложений, платежных сервисов, телеграм-ботов и т.п.

3. pool - обычно означает паттерн "Объектный пул", когда мы не создаем объекты заново, а переиспользуем ранее созданные. см. также https://t.me/advice17/19

4. spooling — спулинг, буферизация задач.

Технология, когда мы не сразу отправляем задачи на обработку между устройствами, а сначала собираем в каком-то промежуточном буфере. Является комбинацией очереди и буфера.

Применяется, например, когда идет речь о выводе на печать.

Дополнительные материалы:

* https://man7.org/linux/man-pages/man2/poll.2.html
* https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-prsod/7262f540-dd18-46a3-b645-8ea9b59753dc
* https://git-scm.com/docs/git-pull
* https://prometheus.io/docs/introduction/faq/#why-do-you-pull-rather-than-push
2.4K views14:30
Открыть/Комментировать
2022-08-05 14:39:54 Переменные окружения и dotenv

Когда мы пишем какие-то сервисы (веб-приложения, боты, обработчики задач), им бывает необходимо передать какие-то настройки. Частыми вариантами будут: реквизиты для доступа к базе данных, токен для сторонних API и т.д.

В общем случае у нас есть два стандартных варианта передачи настроек:
* файлы конфигурации
* переменные окружения

Файлы конфигурации удобны, когда настроек много и они имеют сложную структуру. Но при запуске сервиса в некоторых окружениях, таких как AWS Lambda, Kubernetes и Heroku доставка таких файлов с настройками до работающего экземпляра приложения может быть нетривиальна.
В противовес этому, во многих случаях такие сервисы позволяют через свои способы настройки указать переменные окружения, с которыми будет запущен процесс. Да, мы не сможем передать в этом случае сложные иерархические структуры, но зачастую это не нужно.

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

Чтобы передать переменные окружения нашему приложению, мы можем:
* При использовании bash/sh/zsh сделать export этих переменных или указать их перед командой которую мы выполняем
* Так же для bash удобно подготовить один или несколько файлов с инструкциями export и применять их с помощью команды source
* При запуске через Pycharm переменные окружения можно задать в настройках конфигурации запуска. Иногда удобно иметь несколько таких конфигураций, чтобы отлаживать софт с разными настройками. Другие IDE имеют аналогичные возможности.
* Так же вы всегда можете задать переменные окружения глобально средствами вашей ОС. Но тогда для смены их возможно придется перезайти в учетную запись.
* При запуске сервиса через systemd вы можете указать переменные окружения прямо в service файле или указать из какого файла их необходимо прочитать
* При запуске через docker так же они указываются в команде запуска контейнера docker run напрямую или через --env-file. В случае`docker-compose` эти возможности сохраняются

Стоит отметить относительную популярность библиотек типа python-dotenv. Они позволяют во время работы приложения прочитать конфигурационный файл своего формата и поменять переменные окружения текущего процесса согласно этому файлу. Опасность этого подхода в том, что в момент загрузки этого файла приложение уже работает и при наличии других архитектурных проблем эти значения могут конфликтовать с уже инициализированными объектами, что потребует дополнительных усилий для поддержания работоспособности кода. Некоторые реализации библиотек для чтения .env-файлов могут искать файлы не только в текущем каталоге, но и вверх в каждом родительском каталоге, что может приводить к непредсказуемому поведению кода.

Хочу также обратить внимание, что хотя формат конфига python-dotenv похож на используемый docker, systemd или bash файл, эти все форматы не совместимы. Где-то вы можете ставить пробелы около знака =, где-то допустимо или требуется писать export, где-то невозможно задать многострочные значения и т.д.


Дополнительные материалы:
* https://12factor.net/
* https://www.freedesktop.org/software/systemd/man/systemd.exec.html
* https://docs.docker.com/compose/env-file/
* https://ru.wikipedia.org/wiki/Переменная_среды
3.2K viewsedited  11:39
Открыть/Комментировать
2022-07-05 22:38:26 Механика импорта и побочные эффекты

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

Каждый раз, когда вы импортируете новый, ранее не импортированный, модуль, питон выполняет его код. Даже если вы делаете from .. import, это все равно требует однократного выполнения исходного кода модуля.
Питон хранит в памяти все импортированные модули, поэтому код будет выполнен один раз. Они доступны через sys.modules

Фактически строка import x означает:
1. Найди модуль x в кэше, если там его нет - выполни его код и сохрани модуль в кэш;
2. Создай в текущей области видимости переменную x и присвой ей загруженный модуль в качестве значения.

Соответственно, from x import y означает:
1. Найди модуль x в кэше, если там его нет - выполни его код и сохрани модуль в кэш;
2. Создай в текущей области видимости переменную y и присвой ей в качестве значения атрибут y из модуля x.

Ну и наконец, from x import y as z означает:
1. Найди модуль x в кэше, если там его нет - выполни его код и сохрани модуль в кэш;
2. Создай в текущей области видимости переменную z и присвой ей в качестве значения атрибут y из модуля x.

То, что при этом выполняется код, позволяет нам делать различную динамику при инициализации модуля: будь то загрузка данных, инициализация констант, создание различных реализаций кода в зависимости от окружения. Однако стоит быть осторожным и не писать в глобальной области видимости код, который влияет на что-то ещё кроме текущего модуля.

Импорты изначально предназначены для получения доступа к каким-то именам (константам, функциям, классам), поэтому ожидают что среди них не будет лишних и что порядок не важен. Исходя из этого можно ввести определенные правила форматирования импортов: PEP8 рекомендует нам группировать импорты по типам, многие линтеры также просят придерживаться алфавитного порядка и удалять неиспользуемые импорты. Никто не ожидает, что порядок импортов модулей может повлиять на работу кода (есть несколько исключений, но они общеизвестны и все равно сомнительны). Никто не ожидает, что удаление импорта модуля, который не используется ниже по коду, может повлиять на работу кода.

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

Дополнительные материалы:
* https://peps.python.org/pep-0008/#imports
* https://www.flake8rules.com/rules/F401.html
* https://docs.python.org/3/reference/import.html
4.6K views19:38
Открыть/Комментировать
2022-07-01 18:03:40 Про импорты и структуру проекта

Когда вы импортируете какой-то модуль в вашем коде, питон не учитывает, в каком файле этот импорт находится, влияет только то, как был запущен код.
Если модуль не был раньше загружен, питон пытается его найти по очереди в нескольких папках, которые можно посмотреть в переменной sys.path

По умолчанию она содержит примерно такие каталоги (в некоторых ситуациях, например, при использовании embedded версии python, состав может отличаться):
* каталог, добавляемый при запуске
* каталоги указанные в переменной окружения PYTHONPATH
* каталог текущего активированного виртуального окружения
* каталог установки python

1. Если вы запускаете ваш скрипт командой python scriptname.py, то первым в списке будет тот каталог, где находится запускаемый скрипт. Текущий каталог не имеет значения.
2. Если вы запускаете ваш код командой python -m packagename, то первым в списке будет текущий каталог. При запуске питон попытается найти и импортировать packagename по общим правилам.
3. Если вы запускаете код с помощью других инструментов вроде pytest, они тоже могут сами добавлять что-то в sys.path.

Скорее всего, вам не стоит самостоятельно менять sys.path, так как алгоритм его заполнения стандартный и привычен для всех. Если по каким-то причинам вас он не устраивает, возможно у вас неверная структура проекта.

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

Иногда используемые нами фреймворки поддерживают только определенную, не всегда оптимальную, структуру проекта. В остальных случаях я могу предложить два подхода:

1. Вынести запускаемые скрипты на верхний уровень, а остальной код упаковать в пакет.

Упаковка кода в пакет с уникальным именем позволяет исключить конфликты имен. А вынесение всех запускаемых файлов на один уровень делает состав sys.path предсказуемым.

Выглядеть это будет примерно так:

├── appname
│ ├── __init__.py
│ ├── other_module.py
│ └── some_module.py
├── cli_module.py
└── requirements.txt

2. Создать распространяемый пакет (рекомендую).

В этом случае вы упаковываете весь код в пакет, что помогает исключить конфликты имен.
Для запуска команд вы можете использовать синтаксис python -m appname.cli_module или заполнить секцию entry_points в файле с описанием проекта (setup.cfg, pyproject.toml), после чего иметь свои кастомные консольные команды. В обоих случаях вы сможете запускать код, находясь в любом каталоге, без необходимости указывать полные пути к файлам.

Для удобства разработки с таким подходом удобно устанавливать пакет в editable-режиме с помощью команды типа pip install -e .

Структура будет примерно такой:

├── pyproject.toml
└── src
└── appname
├── __init__.py
├── cli_module.py
├── other_module.py
└── some_module.py

Дополнительные материалы:
* https://packaging.python.org/en/latest/
* https://docs.python.org/3/reference/import.html
* https://docs.python.org/3/library/sys.html#sys.path
* https://ru.wikipedia.org/wiki/Рабочий_каталог
8.7K viewsedited  15:03
Открыть/Комментировать
2022-06-14 13:58:18 Обработка исключений

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

Говоря о каждой конкретной строке кода можно выделить два вида исключений:
* ожидаемые (возможные) - исключения, которые мы предполагаем, что могут возникнуть в данном месте, и знаем какие действия принимать в этой ситуации
* неожиданные - исключения, которые не должны были возникнуть в этом месте, но возникли из-за неверно написанного кода и мы не имеем стратегии поведения в этой ситуации


Отсюда следуют следующие советы:
1. Всегда указывайте исключение, которое вы ловите. Если вы не знаете, что за исключение может возникнуть - вы не знаете корректно ли обрабатывать его вашим способом.
2. Указывайте максимально конкретный класс исключения.
3. Оборачивайте в try/except наименьший возможный код. Если требуется - разбивайте его на несколько выражений
4. Обрабатывайте исключения именно там, где у вас достаточно информации для принятия решения, что делать в данной ситуации.


Частой ошибкой новичков бывает написать просто except: или except Exception - не делайте так.
* Обработка всех подряд исключений, как правило, вообще не корректна, так как туда входит, например, KeyboardInterrupt, по которому ожидается как раз завершение программы. Но это допустимо, если после такой обработки вы пробросите исключение дальше.
* Обработка же Exception актуальна на уровне фреймворка, когда у нас есть стандартный способ реакции на неизвестные ошибки - выдача клиенту ответа с кодом 500, повтор обработки сообщения из очереди т.п.

Другая распространенная ошибка - поймать исключение и заменить его на возврат специального значения. В некоторых случаях это допустимо, но зачастую таким образом мы просто меняем способ доставки информации о наличии исключительной ситуации вызывающему коду, что приводит к появлению дополнительного кода, не делающего реальной работы, а только занимающегося пробросом ошибки вверх по стеку. Это применимо в других языках программирования, но в питоне обычно используется механизм исключений.

Говоря о реализации адаптеров для различных сервисов или БД хорошей идеей будет ввести свои классы исключений

* Реализуя адаптер мы хотим скрыть детали реализации. Например, мы реализовали класс, реализующий хранение определенных сущностей в БД. Затем после очередного рефакторинга мы вынесли это в отдельный микросервис. Интерфейс адаптера при этом не изменился и мы ожидаем что использующий такой адаптер код не будет меняться. И если исключения вроде OSError или ValueError достаточно нейтральны и почти не говорят о реализации, то классы исключения, принадлежащие конкретной используемой библиотеки не стоит прокидывать извне такого адаптера.

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

Дополнительные материалы:
* https://martinfowler.com/articles/replaceThrowWithNotification.html
* https://peps.python.org/pep-0008/#programming-recommendations
4.1K views10:58
Открыть/Комментировать
2022-05-27 12:50:55 БД и миграции

При работе с базами данных нам необходимо, чтобы структура данных в БД соответствовала коду:
Во-первых, мы должны изначально создать необходимые таблицы или коллекции с правильными индексами и т.п.
Во-вторых, при изменении кода приложения мы должны актуализировать эту структуру, создать новые таблицы, переложить данные по другому и т.п. Даже если сама СУБД не подразумевает фиксированной схемы лежащих в ней данных, вы можете захотеть уменьшить разнообразие вариантов хранения.

Кроме этого у нас есть дополнительные ограничения:
* Код не должен иметь права отключать проверки в СУБД, менять и создавать индексы и настраивать связи;
* Запуск нескольких копий кода одновременно (актуально для веб-приложений) не должен приводить БД в неработающее состояние;
* Приложение может быть развернуто на нескольких окружениях, которые обновляются независимо;
* Иногда должна быть возможность вернуть базу данных в предыдущее состояние из-за ошибок;
* Иногда мы хотим, чтобы несколько версий кода работали одновременно. Например, при green-blue/canary deployment.

Таким образом я бы выделил следующие подходы:
1. Состав и структура таблиц должны определяться на момент проектирования/реализации версии кода. Таблицы не должны генерироваться динамически во время работы приложения;
2. Для приведения структуры БД в нужное состояние пишутся скрипты миграции;
3. Скрипты миграции вызываются администратором при деплое приложения. Приложение не должно самостоятельно вызывать скрипты миграции при старте или в другой момент во время работы;
4. Каждый скрипт миграции должен содержать все необходимые данные для его работы. Скрипт миграции не должен обращаться к основному коду приложения, так как код будет меняться, а миграция должна оставаться работоспособной;
5. Скрипт миграции не должен редактироваться после выпуска очередной версии приложения. Если вы забыли мигрировать часть данных, придется делать ещё одну миграцию;
6. Миграции необходимо проверять/тестировать. Тестовые базы данных должны обновляться только с помощью миграций;
7. Инструменты для автоматической генерации миграций могут помочь в работе, но вы должны проверять и редактировать сгенерированный код.

Если есть требование обновления без простоя, то миграции должны сохранять структуру БД совместимой для нескольких версий приложения. Иногда это потребует разбивать миграцию на несколько частей. Например, если вам необходимо переименовать колонку БД, в одной миграции вы добавите новую колонку, а старую сделаете вычислимой. Затем, только после полной выкатки новой версии кода в прод, можно будет применить вторую миграцию, удаляющую старую колонку.

Если же вы делаете эти вещи, вероятно вы используете БД неправильно:
1. Создаете таблицы во время работы программы;
2. Вызываете meta.create_all() (или аналог для вашей ORM) для создания структур БД для ваших моделей;
3. Вызываете миграции автоматически при старте приложения;
4. Импортируете в миграциях модели или другой код из основной части проекта;
5. Меняете код миграций после того как они могли быть использованы;
6. Не запускаете миграции нигде кроме прода;
7. Не читаете код автоматически сгенерированных миграций.

Дополнительно хочу отметить, что миграции - это не обязательно простые изменения структуры, такие как добавление или удаление колонки/таблицы. Иногда вам потребуется произвести какую-то длительную работу по модификации данных (например, посчитать значение колонки для БД из миллиарда записей).

И хотя обычно эти советы дают для реляционных СУБД, так как те требуют соблюдения структуры таблиц, они также применимы и для документо-ориентированных баз данных. Вы можете обойтись без миграции для добавления nullable поля в MongoDB, но скорее всего вам потребуется её делать в том или ином виде, если вы захотите разбить колонку на две или вместо одного числа начать хранить список.

Дополнительные материалы:
* https://habr.com/ru/company/yandex/blog/511892/
* https://habr.com/ru/company/flant/blog/471620/
* https://alembic.sqlalchemy.org/en/latest/
4.1K viewsedited  09:50
Открыть/Комментировать
2022-05-17 09:52:27
Читаете ли мы дополнительные материалы по ссылкам ниже поста?
Anonymous Poll
17%
Читаю всё, там же столько интересного
9%
Открываю и откладываю на потом
22%
Читаю отдельные статьи
17%
Нет
4%
Там есть ссылки?
2%
По этим ссылкам есть что-то новое?
30%
Я оладушек
501 voters3.6K views06:52
Открыть/Комментировать
2022-05-16 21:33:16 Пулы объектов и соединений.

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

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

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

Один из важных случаев, когда такая оптимизация может быть полезна - работа с сетевыми подключениями: установка соединения по TCP занимает некоторое время, использование TLS только увеличивает его. При это многие сетевые протоколы не требуют физического разрыва соединения после завершения ваших запросов и позволяют продолжить его использовать для дальнейшего общения.


Примеры:

* requests.Session - кроме дополнительной логики по управлению куками содержит внутри пул соединений с серверами, по которыми в дальнейшем посылаются HTTP(s) запросы. Использовать requests без Session скорее всего будет плохой идеей

* aiohttp.ClientSession - аналогичный объект для асинхронного "аналога" - библиотеки aiohttp. Несмотря на то, что в примерах из документации зачастую сессия создается по месту запроса, рекомендуется инициализировать её один раз и в дальнейшем переиспользовать

* psycopg2.pool - модуль с несколькими вариантами пулов соединений с СУБД Postgresql.

* Engine из SQLAlchemy также использует пул соединений. При этом возможна настройка таких параметров как время жизни соединения, дополнительные проверки его доступности, размер пула. В том числе возможно и использование NullPool, который по факту не является пулом, но совместим с ним по интерфейсу.

Дополнительные материалы:

* https://habr.com/ru/company/otus/blog/443312/
* https://habr.com/ru/post/443378/
* https://docs.sqlalchemy.org/en/14/core/pooling.html
* https://docs.aiohttp.org/en/stable/client_reference.html
* https://docs.python-requests.org/en/latest/user/advanced/#session-objects
3.6K viewsedited  18:33
Открыть/Комментировать