Метод iterator() в Django ORM
В Django ORM загрузка объектов модели из БД осуществляется через один из её менеджеров. У менеджера, а также у
При выполнении SELECT-запроса
Метод
Для
Примечание 1: при использовании
Примечание 2: при использовании пулов соединений с БД серверные курсоры нужно отключать (см. параметр
Совет: при обходе
#django #оптимизация
В Django ORM загрузка объектов модели из БД осуществляется через один из её менеджеров. У менеджера, а также у
QuerySet, есть метод iterator(). О нем и пойдет речь.При выполнении SELECT-запроса
QuerySet по умолчанию загружает ВСЕ записи (строки) и для КАЖДОЙ из них создает экземпляр модели (кеширует). В большинстве случаев результаты выполнения запроса обрабатываются в цикле и каждый из созданных объектов используется только на одной итерации цикла. Очевидно, что при этом необходимости хранить в памяти экземпляры модели для всех записей нет. Это особенно актуально, когда запрос возвращает много записей, каждая из которых занимает память.Метод
iterator() позволяет отключить такое кэширование и оптимизировать использование памяти при обработке данных. А в Django 1.11+ для Oracle и PostgreSQL также используются серверные курсоры, что позволяет еще лучше оптимизировать использование памяти приложением, т.к. данные с сервера БД будут загружаться порциями по 100 записей (см. GET_ITERATOR_CHUNK_SIZE).for obj in TestModel.objects.filter(...).iterator():
...
Для
values() и values_list() также можно отключать кеширование.Примечание 1: при использовании
iterator() игнорируется prefetch_related().Примечание 2: при использовании пулов соединений с БД серверные курсоры нужно отключать (см. параметр
DISABLE_SERVER_SIDE_CURSORS).Совет: при обходе
QuerySet-ов в циклах, где не нужно повторное использование экземпляров моделей, отключайте их кэширование с помощью метода iterator().#django #оптимизация
Django ORM: on_commit
Иногда появляется необходимость выполнить какие-то действия сразу после коммита. Одним из примеров такой ситуации является удаление записей с полями на основе
В документации Django сказано, что разработчикам нужно самостоятельно позаботиться об удалении файлов. Также там предлагается использовать для этих целей management-команды и их периодический запуск (например, через cron). Начиная с Django 1.9 и выше доступен более оптимальный способ для удаления файлов, на которые уже нет ссылок в БД. Речь о функции
Почему неправильно удалять файл сразу в обработчике
Еще один пример — это отправка пользователям уведомлений о выполнении каких-либо операций. Отправлять письмо о том, что в системе была выполнена какая-либо операция, нужно только после того, как будут сохранены в БД соответствующие изменения, т.е. после коммита. Иначе после отправки письма дальнейшие операции могут привести к ошибке и откату транзакции в БД, но письмо уже не отменишь.
Совет: при взаимодействии с внешними относительно СУБД системами (файловые системы, почтовые серверы и т.п.) помните о возможности отката транзакции и откладывайте все действия до её подтверждения с помощью
#django
Ссылки:
- функция
Иногда появляется необходимость выполнить какие-то действия сразу после коммита. Одним из примеров такой ситуации является удаление записей с полями на основе
FileField (в т.ч. ImageField). Эти поля предназначены для хранения файлов, но в БД хранится только путь к файлу, а сам файл хранится в файловой системе или другом хранилище. При удалении такой записи Django ORM НЕ удаляет файл. Поэтому при интенсивном добавлении/удалении записей в такой модели будет как минимум нерационально использоваться дисковое пространство.В документации Django сказано, что разработчикам нужно самостоятельно позаботиться об удалении файлов. Также там предлагается использовать для этих целей management-команды и их периодический запуск (например, через cron). Начиная с Django 1.9 и выше доступен более оптимальный способ для удаления файлов, на которые уже нет ссылок в БД. Речь о функции
on_commit в модуле django.db.transaction. Её первый аргумент — callable-объект, которая будет вызвана сразу после успешного коммита текущей транзакции.@receiver(post_delete)
def delete_files(sender, instance, using, **kwargs):
for field in sender._meta.get_fields():
if isinstance(field, FileField):
file = getattr(instance, field.name)
if file:
on_commit(file.delete, using)
Почему неправильно удалять файл сразу в обработчике
post_delete? Дело в том, что обработчики сигналов вызываются внутри транзакции, а значит по тем или иным причинам может произойти откат транзакции, т.е. данные в БД будут возвращены к исходному состоянию. В итоге ссылка на файл останется в БД, а вот самого файла уже не будет.Еще один пример — это отправка пользователям уведомлений о выполнении каких-либо операций. Отправлять письмо о том, что в системе была выполнена какая-либо операция, нужно только после того, как будут сохранены в БД соответствующие изменения, т.е. после коммита. Иначе после отправки письма дальнейшие операции могут привести к ошибке и откату транзакции в БД, но письмо уже не отменишь.
Совет: при взаимодействии с внешними относительно СУБД системами (файловые системы, почтовые серверы и т.п.) помните о возможности отката транзакции и откладывайте все действия до её подтверждения с помощью
on_commit.#django
Ссылки:
- функция
on_commit: https://docs.djangoproject.com/en/2.0/topics/db/transactions/#django.db.transaction.on_commit👍1
Django ORM: Миграции как "снимки" Системы
(речь пойдет о миграциях в Django 1.7 и выше)
Для начала некоторые факты о миграциях:
● Миграции предназначены для программирования изменений в базах данных проекта. Это могут быть как изменения в схеме данных (изменение структуры таблиц, ограничений целостности, индексов и т.п.), так и изменения в самих данных (исправление ошибочных данных, загрузка новых данных и т.п.).
● Каждая миграция применяется для каждой БД проекта (баз данных может быть несколько).
● Миграция состоит из т.н. операций. Операции могут быть как встроенные, например, создание таблицы для модели, добавление/изменение/удаление/переименование поля в таблице и т.п., так и реализованные в рамках сторонних библиотек, либо проекта.
● Операция имеет "направление" выполнения. Прямое (forwards) реализуется всегда, обратное (backwards) при наличии такой возможности/необходимости.
● Выполнение или пропуск операции определяется через роутеры в методе
● Перед и после выполнения миграций каждого django-приложения отправляются сигналы
● Каждая миграция по умолчанию выполняется в отдельной транзакции. Поэтому надо учитывать некоторые особенности, обусловенные этим. Например, внутри транзакций нельзя выполнять некоторые операции (зависит от СУБД и её версии). Это определяется атрибутом
● Для удобства в каждом приложении миграции нумеруются. Но порядок применения миграций определяется через зависимости, а не их номерами. Например, зависимости можно описать так, что сначала будет применена миграция 0001, затем 0004, потом 0002 и только после этого 0003.
● Зависимости могут быть определены через атрибуты
Миграции — это снимки (snapshot) Системы
При необходимости выполнения в операциях миграций действий с моделями (CRUD) в Django используются т.н. исторические модели. Иными словами, это снимки моделей Системы на момент реализации миграции. Это обусловлено тем, что модели Системы не только могут быть изменены в процессе её развития, но и вовсе удалены, однако операция должна всё равно выполняться при применении миграций с нуля (например, при запуске автотестов).
Отсюда следует другой не всегда очевидный факт: помимо моделей в Системе могут меняться и другие её составные части, например, константы, функции, классы и т.п. Поэтому при написании нестандартных миграций целесообразно также делать "снимок" используемых в миграции объектов, которые в будущем могут быть изменены, т.е. копипастить. Пожалуй, это единственный случай, когда программирование копипастом не является антипаттерном 🙂
Исторические модели — это самостоятельные классы!
При написании миграций следует помнить, что историческая модель не имеет ничего общего с моделью Системы, кроме, разве что, имени. Это означает, что атрибуты и методы, имеющиеся в моделях Системы, отсутствуют в исторических моделях. Обработчики сигналов, подключенные с параметром
(речь пойдет о миграциях в Django 1.7 и выше)
Для начала некоторые факты о миграциях:
● Миграции предназначены для программирования изменений в базах данных проекта. Это могут быть как изменения в схеме данных (изменение структуры таблиц, ограничений целостности, индексов и т.п.), так и изменения в самих данных (исправление ошибочных данных, загрузка новых данных и т.п.).
● Каждая миграция применяется для каждой БД проекта (баз данных может быть несколько).
● Миграция состоит из т.н. операций. Операции могут быть как встроенные, например, создание таблицы для модели, добавление/изменение/удаление/переименование поля в таблице и т.п., так и реализованные в рамках сторонних библиотек, либо проекта.
● Операция имеет "направление" выполнения. Прямое (forwards) реализуется всегда, обратное (backwards) при наличии такой возможности/необходимости.
● Выполнение или пропуск операции определяется через роутеры в методе
allow_migrate.● Перед и после выполнения миграций каждого django-приложения отправляются сигналы
pre_migrate и post_migrate.● Каждая миграция по умолчанию выполняется в отдельной транзакции. Поэтому надо учитывать некоторые особенности, обусловенные этим. Например, внутри транзакций нельзя выполнять некоторые операции (зависит от СУБД и её версии). Это определяется атрибутом
atomic в классе миграции.● Для удобства в каждом приложении миграции нумеруются. Но порядок применения миграций определяется через зависимости, а не их номерами. Например, зависимости можно описать так, что сначала будет применена миграция 0001, затем 0004, потом 0002 и только после этого 0003.
● Зависимости могут быть определены через атрибуты
dependencies и run_before. В них указываются миграции, которые нужно выполнить до/после данной миграции соответственно.Миграции — это снимки (snapshot) Системы
При необходимости выполнения в операциях миграций действий с моделями (CRUD) в Django используются т.н. исторические модели. Иными словами, это снимки моделей Системы на момент реализации миграции. Это обусловлено тем, что модели Системы не только могут быть изменены в процессе её развития, но и вовсе удалены, однако операция должна всё равно выполняться при применении миграций с нуля (например, при запуске автотестов).
Отсюда следует другой не всегда очевидный факт: помимо моделей в Системе могут меняться и другие её составные части, например, константы, функции, классы и т.п. Поэтому при написании нестандартных миграций целесообразно также делать "снимок" используемых в миграции объектов, которые в будущем могут быть изменены, т.е. копипастить. Пожалуй, это единственный случай, когда программирование копипастом не является антипаттерном 🙂
Исторические модели — это самостоятельные классы!
При написании миграций следует помнить, что историческая модель не имеет ничего общего с моделью Системы, кроме, разве что, имени. Это означает, что атрибуты и методы, имеющиеся в моделях Системы, отсутствуют в исторических моделях. Обработчики сигналов, подключенные с параметром
sender также не будут срабатывать на исторические модели.Wikipedia
CRUD
Основные операции при работе с базами данных
👍1
Загрузка данных (fixtures)
До появления в Django встроенных миграций задача внесения в БД изменений решалась с помощью South. У этого инструмента была одна особенность: при каждом запуске команды
Совет: при реализации миграций помните о том, что вся работа в ней должна осуществляться с объектами, которые не изменят своё значение или поведение в будущем при развитии проекта.
#django
До появления в Django встроенных миграций задача внесения в БД изменений решалась с помощью South. У этого инструмента была одна особенность: при каждом запуске команды
migrate загружались данные из файла fixtures/initial_data.*. Недостающие записи добавлялись, существующие перезаписывались. При необходимости дополнительной загрузки данных предлагалось использовать команду loaddata. В Django 1.7+ такая особенность использования файла fixtures/initial_data.* не была реализована, а необходимость загрузки данных в БД осталась. Скорее всего по привычке, в сети Интернет стали предлагать в качестве решения использовать команду loaddata. И эти решения стали часто применяться на практике. Но это работает до первого несовместимого изменения в соответствующих моделях Системы. Всё дело в том, что management-команды Django работают с моделями Системы, а не со снимком Системы, который доступен в миграции. Поэтому для загрузки данных в БД рекомендуется использовать свои реализации операций, благо делаются они не сложно.Совет: при реализации миграций помните о том, что вся работа в ней должна осуществляться с объектами, которые не изменят своё значение или поведение в будущем при развитии проекта.
#django
👍1
Порядок указания декораторов
При использовании сразу нескольких декораторов порядок их следования, в зависимости от их реализации, может иметь значение.
Напомню, что объявление функции вида
1. Будет создана функция (для условности буду называть её "исходная").
2. В декораторе
3. Т.к. декоратор
4.
5. Этот объект будет сохранен в атрибуте
В указанном выше примере допущена ошибка, т.к. при срабатывании сигнала будет вызыватся исходная функция, а не функция, обёрнутая декоратором
Правильным здесь будет такой порядок:
При использовании сразу нескольких декораторов порядок их следования, в зависимости от их реализации, может иметь значение.
Напомню, что объявление функции вида
@d1аналогично такой записи:
@d2
def f():
pass
def f():Так вот в зависимости от того, в каком порядке указываются декораторы, результат может быть различным (а может и не быть 🙂). Для примера рассмотрим реализацию обработчика сигнала в Django:
pass
f = d1(d2(f))
@atomicВот в каком порядке будет происходить создание функции:
@receiver(post_save)
def handler(**kwargs):
....
1. Будет создана функция (для условности буду называть её "исходная").
2. В декораторе
receiver исходная функция будет добавлена к обработчикам сигнала post_save.3. Т.к. декоратор
receiver возвращает оборачиваемую функцию без изменений, она будет передана в декоратор atomic.4.
atomic создаст новый callable-объект, который будет выполнять исходную функцию внутри транзакции.5. Этот объект будет сохранен в атрибуте
handler модуля, в котором была реализована функция.В указанном выше примере допущена ошибка, т.к. при срабатывании сигнала будет вызыватся исходная функция, а не функция, обёрнутая декоратором
atomic, ведь именно она подключалась к сигналу.Правильным здесь будет такой порядок:
@receiver(post_save)
@atomic
def handler(**kwargs):
....👍1
Команда
Например, обновить pip:
allvirtualenv из virtualenvwrapper
Если есть необходимость выполнить какую-либо команду во всех виртуальных окружениях, созданных с помощью virtualenvwrapper, то для этого подойдет команда allvirtualenv.Например, обновить pip:
allvirtualenv pip install pip -U
Или посмотреть версии интерпретатора во всех окружениях:allvirtualenv python -V👍1
Django:
Если при использовании
1. Список идентификаторов в условии напрямую зависит от количества записей в таблице БД (актуально и связей *-к-одному). Из-за этого по мере наполнения БД такие SQL-запросы могут содержать очень много идентификаторов в условии
2. Для связей многие-к-одному в тех случаях, когда на один и тот же объект приходится более одной ссылки,
Совет 1: используйте
Совет 2: не используйте
select_related и prefetch_related
Часто приходится слышать о том, что select_related предназначен для связей <один/многие>-к-одному (ForeignKey, OneToOneField), а prefetch_related — только для связей <один/многие>-ко-многим (ManyToOneRel, ManyToManyField). Вторая часть этого утверждения верная, но не полная, т.к. prefetch_related может использоваться для тех же связей, что и select_related, только на уровне SQL подгрузка связанных объектов будет выглядеть иначе.Если при использовании
select_related для загрузки связанных объектов используются SELECT-запросы с JOIN-ами, то prefetch_related в случае связей *-к-одному будет также формировать отдельные SELECT-запросы вида SELECT ... FROM ... WHERE id in (1, 4, 6, 8, ...). Отсюда следуют две особенности:1. Список идентификаторов в условии напрямую зависит от количества записей в таблице БД (актуально и связей *-к-одному). Из-за этого по мере наполнения БД такие SQL-запросы могут содержать очень много идентификаторов в условии
WHERE id in (десятки, сотни тысяч и более). Такие SQL-запросы будут работать тем медленнее, чем больше идентификаторов в них.2. Для связей многие-к-одному в тех случаях, когда на один и тот же объект приходится более одной ссылки,
prefetch_related создаст по одному экземпляру модели, а select_related создаст дубликаты.Совет 1: используйте
prefetch_related вместо select_related со связями *-к-одному в тех случаях, когда на одну запись в связанной таблице приходится несколько ссылок из основной таблицы — это более оптимально за счет отсутствия дубликатов. При этом нужно помнить о том, что prefetch_related не будет работать совместно с методом iterator ☝🏻Совет 2: не используйте
prefetch_related с запросами, в которых рост количества получаемых записей не ограничен, это может привести к обратному эффекту.👍1
Python: функции
Почти всегда это делается подобным образом:
#python
attrgetter, itemgetter и methodcaller
Часто возникает необходимость применения к последовательности объектов функции, извлекающей атрибут объекта или вызывающей его метод, либо возвращающей элемент массива или словаря. Например, в функциях filter, sorted, map и др.Почти всегда это делается подобным образом:
map(В модуле operator стандартной библиотеки Python есть функции
lambda day: (day.year, day.month, day.day),
dates
)
attrgetter, itemgetter и methodcaller, с помощью которых можно решать подобные задачи более наглядным и лаконичным образом:map(Совет: для повышения читаемости кода используйте функции
attrgetter('year', 'month', 'day'),
dates
)
attrgetter, itemgetter, methodcaller. Описание функций здесь.#python
👍1
Django ORM: проверка на prefetch_related
Метод
При обходе в цикле доступ к связанным объектам через
Метод
prefetch_related позволяет загрузить связанные через обратную связь объекты модели.class Person(models.Model):Для таких моделей при финализации QuerySet-а
name = models.CharField(...)
class Employee(models.Model):
person = models.ForeignKey(
Person,
related_name='employees',
)
job = models.CharField(...)
persons = Person.objects.prefetch_related(будет выполнено два SELECT-запроса.
'employees'
)
При обходе в цикле доступ к связанным объектам через
all() не потребует выполнения дополнительных SELECT-запросов:for person in persons:Совет: для контроля использования
print(person.employees.all())
prefetch_related и отсутствия "проблемы N+1" рекомендую использовать assert:for person in query:#django
assert (
'employees' in person._prefetched_objects_cache
)
print(person.employees.all())
👍1
Python Tips via @like
itertools.groupby()
В модуле
Функция возвращает итерируемый объект, который на каждой итерации возвращает значение ключа и итератор по элементам последовательности, соответствующим этому ключу.
Предположим, например, что нужно подсчитать сумму окладов сотрудников по отделам какой-либо организации. Данные указаны в csv-файле в формате ("Наименование подразделения","ФИО сотрудника",Оклад) и упорядочены по наименованию отдела:
Совет: используйте функцию
Ссылки:
- функция groupby.
#python
В модуле
itertools есть полезная в некоторых ситуациях функция groupby(). Она позволяет сгруппировать последовательности элементов по какому-либо признаку (ключу). При этом функция работает как с коллекциями (кортежи, списки и др.), так и с итераторами/генераторами. Следует отметить, что элементы в исходной последовательности должны быть упорядочены по ключу ☝🏻 Для формирования значений ключа в функцию передается callable-объект с одним аргументом, возвращающий значение ключа.Функция возвращает итерируемый объект, который на каждой итерации возвращает значение ключа и итератор по элементам последовательности, соответствующим этому ключу.
Предположим, например, что нужно подсчитать сумму окладов сотрудников по отделам какой-либо организации. Данные указаны в csv-файле в формате ("Наименование подразделения","ФИО сотрудника",Оклад) и упорядочены по наименованию отдела:
with open('filename.csv') as csvfile:
groupped_data = groupby(
csv.reader(csvfile), itemgetter(0)
)
salaries_by_department = {
department: sum(int(row[2]) for row in rows)
for department, rows in groupped_data
}
Использование итераторов позволяет оптимизировать потребление памяти, т.к. не требуется хранения промежуточных результатов (map() из Python 2 уже не учитываем). Еще это даёт возможность работать с потоками данных (файлы, курсоры БД, сокеты и т.п.).Совет: используйте функцию
groupby() в сочетании с итераторами для получения оптимальных результатов.Ссылки:
- функция groupby.
#python
👍1
Python Tips via @like
Модуль textwrap
В стандартной библиотеке Python есть модуль textwrap, содержащий простые, но в то же время полезные инструменты для работы с текстом. Приведу здесь их краткое описание:
Также в модуле есть класс
Совет: используйте инструментарий из модуля
Ссылки:
- Модуль textwrap.
#python
В стандартной библиотеке Python есть модуль textwrap, содержащий простые, но в то же время полезные инструменты для работы с текстом. Приведу здесь их краткое описание:
wrap(text, width=70, **kwargs)Разбивает строку
text так, чтобы в полученном в результате списке строк длина каждой из них не превышала width символов.fill(text, width=70, **kwargs)Делает то же самое, что и
wrap, только результат возвращается в виде строки, а не списка строк.shorten(text, width, **kwargs)Сокращает строку
text до width символов, попутно удаляя лишние пробельные символы (пробел, табуляция, перевод строки и т.п.).>>> shorten('Beautiful is better than ugly.', 20)
'Beautiful is [...]'
indent(text, prefix, predicate=None)
Добавляет prefix слева в каждую строку параграфа.>>> print(indent(json.dumps({'qwe': 1}, indent=4), '.'*8))
........{
........ "qwe": 1
........}
dedent(text)
Удаляет пробельные символы в начале строк параграфа.Также в модуле есть класс
TextWrapper, объединяющий в себе перечисленные выше возможности. Его методы wrap и fill обрабатывают текст согласно настроек экземпляра класса и возвращают результат обработки в виде списка или строки соответственно.Совет: используйте инструментарий из модуля
textwrap для форматирования текстовых строк. В консольных приложениях, в лог-файлах и т.п. это повысит читаемость вывода приложения. Также это повысит читаемость кода, т.к. он не будет перегружен конструкциями, форматирующими текст.Ссылки:
- Модуль textwrap.
#python
👍1
Документирование кода
Многим python-разработчикам знакомо соглашение о стиле кодирования PEP-8. Для проверки кода на соответствие этому соглашению созданы статические анализаторы (например, pycodestyle). Также есть и ПО для автоматического форматирования кода (black и др.).
О написании строк документации в PEP-8 сказано лишь то, что строки документации нужно писать для всех публичных модулей, функций, классов и методов, а также об использовании тройных кавычек
В следующих публикациях расскажу про описанные в PEP-257 соглашения об оформлении однострочной и многострочной документации, а также использовании генератора документации Sphinx.
Ну а совет очевиден: документируйте свой код — это сэкономит время другим разработчикам, а значит и повысит эффективность команды в целом, ведь код читается чаще, чем пишется.
Многим python-разработчикам знакомо соглашение о стиле кодирования PEP-8. Для проверки кода на соответствие этому соглашению созданы статические анализаторы (например, pycodestyle). Также есть и ПО для автоматического форматирования кода (black и др.).
О написании строк документации в PEP-8 сказано лишь то, что строки документации нужно писать для всех публичных модулей, функций, классов и методов, а также об использовании тройных кавычек
""". Остальные соглашения о стиле оформления строк документации описаны в PEP-257.В следующих публикациях расскажу про описанные в PEP-257 соглашения об оформлении однострочной и многострочной документации, а также использовании генератора документации Sphinx.
Ну а совет очевиден: документируйте свой код — это сэкономит время другим разработчикам, а значит и повысит эффективность команды в целом, ведь код читается чаще, чем пишется.
Python Enhancement Proposals (PEPs)
PEP 8 – Style Guide for Python Code | peps.python.org
This document gives coding conventions for the Python code comprising the standard library in the main Python distribution. Please see the companion informational PEP describing style guidelines for the C code in the C implementation of Python.
👍1
Python Tips
Документирование кода Многим python-разработчикам знакомо соглашение о стиле кодирования PEP-8. Для проверки кода на соответствие этому соглашению созданы статические анализаторы (например, pycodestyle). Также есть и ПО для автоматического форматирования…
Однострочная документация
К однострочной документации предъявляются следующие требования:
1. Должна находиться на одной строке, без пустых строк до и после текста.
2. Нужно использовать тройные кавычки (
3. Должна быть фраза c точкой в конце.
4. Не надо дублировать объявление функции/класса/метода.
Приведу примеры документирования, нарушающие эти соглашения:
1. Лишние переводы строк:
К однострочной документации предъявляются следующие требования:
1. Должна находиться на одной строке, без пустых строк до и после текста.
2. Нужно использовать тройные кавычки (
""").3. Должна быть фраза c точкой в конце.
4. Не надо дублировать объявление функции/класса/метода.
Приведу примеры документирования, нарушающие эти соглашения:
1. Лишние переводы строк:
def random():2. Нет точки в конце + лишние пробелы:
"""
Возвращает случайное число.
"""
def random():3. Дублирование объявления функции + одинарные кавычки:
""" Возвращает случайное число """
def random():Правильный вариант:
"random() -> int"
def random():
"""Возвращает случайное число."""👍1
Многострочная документация
1. Первая строка содержит краткое описание модуля, класса, функции и др., начинается сразу после
2. Отделена от следующих абзацев пустой строкой.
3. Закрывающие кавычки на отдельной строке.
В многострочной документации в т.ч. описываются аргументы функции/метода, исключения и условия, при которых они возникают, возвращаемые значения и т.п. Для их описания есть несколько форматов, каждый определяется используемой системой генерации документации.
Например, генератор документации Sphinx (CPython, Django, Celery) при генерации справочников по API извлекает необходимую информацию из строк документации в форматах reStructuredText или Markdown, в которых используется такой синтаксис:
Система генерации документации MkDocs (FastAPI, Pydantic), помимо упомянутого выше Sphinx-style, поддерживает другие форматы.
Google-style:
Numpydoc:
Совет: придерживайтесь правил стилевого оформления строк документации (PEP-8, PEP-257) и используйте единый формат, даже если не используете системы генерации документации, т.к. IDE тоже умеют их распознавать и отображать во всплывающих подсказках. Единообразие стилевого оформления кода делает его чтение проще, а значит и работу ваших коллег, в т.ч. и будущих, легче 😎
1. Первая строка содержит краткое описание модуля, класса, функции и др., начинается сразу после
""", заканчивается точкой.2. Отделена от следующих абзацев пустой строкой.
3. Закрывающие кавычки на отдельной строке.
В многострочной документации в т.ч. описываются аргументы функции/метода, исключения и условия, при которых они возникают, возвращаемые значения и т.п. Для их описания есть несколько форматов, каждый определяется используемой системой генерации документации.
Например, генератор документации Sphinx (CPython, Django, Celery) при генерации справочников по API извлекает необходимую информацию из строк документации в форматах reStructuredText или Markdown, в которых используется такой синтаксис:
def get_files(day: date) -> list[str]:
"""Возвращает имена файлов на указанную дату.
:param day: дата, на основе которой формируется путь для поиска файлов.
:type day: date
:raises FileNotFoundError: если ни одного файла не найдено.
"""
Система генерации документации MkDocs (FastAPI, Pydantic), помимо упомянутого выше Sphinx-style, поддерживает другие форматы.
Google-style:
def get_files(day: date) -> list[str]:
"""Возвращает имена файлов на указанную дату.
Args:
day (date): дата, на основе которой формируется путь для поиска
файлов.
Raises:
FileNotFoundError: если ни одного файла не найдено.
"""
Numpydoc:
def get_files(day: date) -> list[str]:
"""Возвращает имена файлов на указанную дату.
Parameters
----------
day : date
Дата, на основе которой формируется путь для поиска
файлов.
Raises
------
FileNotFoundError
Если ни одного файла не найдено.
"""
Совет: придерживайтесь правил стилевого оформления строк документации (PEP-8, PEP-257) и используйте единый формат, даже если не используете системы генерации документации, т.к. IDE тоже умеют их распознавать и отображать во всплывающих подсказках. Единообразие стилевого оформления кода делает его чтение проще, а значит и работу ваших коллег, в т.ч. и будущих, легче 😎
👍5
Функция iter
Встроенная функция
Но у неё есть вариант вызова с двумя аргументами:
Это может быть удобно тогда, когда объект не поддерживает итерирование, например
Обычно для получения данных из очереди используется цикл
С помощью
#python
Встроенная функция
iter() используется для создания итераторов и всем знакома. Под капотом она вызывает метод __iter__() объекта и возвращает результат.Но у неё есть вариант вызова с двумя аргументами:
callable и sentinel. В этом случае она вернёт итератор, который на каждой итерации будет вызывать функцию callable пока она не вернёт sentinel. Это может быть удобно тогда, когда объект не поддерживает итерирование, например
queue.Queue:from queue import Queue
queue = Queue()
for n in (1, 2, 3, 4, 5, None):
queue.put(n)
Обычно для получения данных из очереди используется цикл
while:items = []
while (item := queue.get()) is not None:
items.append(item)
С помощью
iter() то же самое можно сделать так:items = list(iter(queue.get, None))
#python
👍7
Когда много менеджеров контекста
(Под "менеджерами контекста" здесь подразумеваются экземпляры классов с реализованными методами
Бывает так, что нужно использовать несколько менеджеров контекста, например, открыть несколько файлов, подключений к БД и курсоров. Не всегда их можно создать в одном блоке
Это в т.ч. усложняет структуру кода и, как следствие, ухудшает его читаемость, т.к. визуально он структурирован, а фактически выполняется линейно.
В модуле
Также в
Асинхронная версия:
Советы:
- всегда используйте менеджеры контекста для корректного освобождения ресурсов, т.к. они будут работать даже при возникновении непредвиденных ошибок;
- ознакомьтесь с инструментами модуля
- создавайте свои менеджеры контекста с помощью декораторов
Ссылки:
- описание протокола менеджеров контекста;
- документация модуля contextlib;
- документация ExitStack;
- примеры использования.
#python
(Под "менеджерами контекста" здесь подразумеваются экземпляры классов с реализованными методами
__enter__ и __exit__)Бывает так, что нужно использовать несколько менеджеров контекста, например, открыть несколько файлов, подключений к БД и курсоров. Не всегда их можно создать в одном блоке
with и приходится делать вложенные:with open('config.json', 'r') as file:
config = json.load(file)
with (
psycopg2.connect(dbname=config['database']) as dbc,
dbc.cursor() as cursor
):
cursor.execute('INSERT INTO ...')
Это в т.ч. усложняет структуру кода и, как следствие, ухудшает его читаемость, т.к. визуально он структурирован, а фактически выполняется линейно.
В модуле
contextlib стандартной библиотеки есть класс ExitStack, с помощью которого можно объединять несколько менеджеров контекста:with ExitStack() as es:
file = es.enter_context(open('config.json', 'r'))
config = json.load(file)
dbc = es.enter_context(psycopg2.connect(dbname=config['database']))
cursor = es.enter_context(dbc.cursor())
cursor.execute('INSERT INTO ...')
Также в
ExitStack есть метод callback(), который позволяет добавить вызов функции на выходе из контекста (из блока with). with ExitStack() as es:
connection_pool = redis.ConnectionPool.from_url(
'redis://localhost:6379/0'
)
es.callback(collection_pool.close)
ExitStack может быть полезен и тогда, когда количество менеджеров контекста не известно заранее. В этом примере кода все открытые файлы будут закрыты при выходе из блока with:with ExitStack() as es:
files = [
es.enter_context(open(file_path, 'r'))
for file_path in file_paths
]
Асинхронная версия:
AsyncExitStack.Советы:
- всегда используйте менеджеры контекста для корректного освобождения ресурсов, т.к. они будут работать даже при возникновении непредвиденных ошибок;
- ознакомьтесь с инструментами модуля
contextlib и используйте функцию closing() / aclosing() для объектов, имеющих метод close(), но не поддерживающих протокол менеджера контекста;- создавайте свои менеджеры контекста с помощью декораторов
@contextmanager / @asynccontextmanager и базового класса ContextDecorator / AsyncContextDecorator.Ссылки:
- описание протокола менеджеров контекста;
- документация модуля contextlib;
- документация ExitStack;
- примеры использования.
#python
👍11