Часто в литературе, сети Интернет и в коде реальных проектов можно встретить такое использование генераторных выражений:
Такой код хоть и правильный с точки зрения синтаксиса, тем не менее не является оптимальным. В большинстве случаев правильнее писать так:
Разница в том, что в первом случае в функцию
Вывод: создавайте коллекции (кортежи, списки и т.п.) на основе генераторов только там, где это действительно нужно.
#python #оптимизация
total = sum([x for x in sequence])
Такой код хоть и правильный с точки зрения синтаксиса, тем не менее не является оптимальным. В большинстве случаев правильнее писать так:
total = sum(x for x in sequence)
Разница в том, что в первом случае в функцию
sum передается список, созданный на основе генератора, а во втором — генератор. Для создания списка нужно выполнить дополнительные операции, а также выделить дополнительную память. При этом результат работы всего кода будет тот же самый.Вывод: создавайте коллекции (кортежи, списки и т.п.) на основе генераторов только там, где это действительно нужно.
#python #оптимизация
Периодически на ревью вижу примерно такое использование функций
Скорее всего это делается для повышения читаемости кода, но это только моё предположение. С точки зрения логики программы это почти всегда рабочий код. Но вот с точки зрения оптимальности есть проблемы.
Дело в том, что этом случае будут выполнены все проверки вне зависимости от результатов каждой из них. Также будет создан дополнительный кортеж, который потребует, хоть и несущественного, но всё же выделения памяти и процессорного времени.
Более оптимальный код выглядит так:
При использовании логических операторов будет вычисляться только необходимый минимум операндов. В примере выше если проверка 1 пройдет, то остальные проверки выполняться не будут.
Использование функций
Описание функций
#python #оптимизация
all и any:if any((
is_check1_passed(),
is_check2_passed(),
...
)):
pass
Скорее всего это делается для повышения читаемости кода, но это только моё предположение. С точки зрения логики программы это почти всегда рабочий код. Но вот с точки зрения оптимальности есть проблемы.
Дело в том, что этом случае будут выполнены все проверки вне зависимости от результатов каждой из них. Также будет создан дополнительный кортеж, который потребует, хоть и несущественного, но всё же выделения памяти и процессорного времени.
Более оптимальный код выглядит так:
if (
is_check1_passed() or
is_check2_passed() or
...
):
pass
При использовании логических операторов будет вычисляться только необходимый минимум операндов. В примере выше если проверка 1 пройдет, то остальные проверки выполняться не будут.
Использование функций
all и any актуально в первую очередь при использовании генераторов, т.к. за счет отложенности вычисляться будет только необходимый минимум операндов.Описание функций
all и any: https://docs.python.org/3.6/library/functions.html#all#python #оптимизация
В Python есть такие структуры данных, как кортежи (
При хранении одних и тех же данных списки занимают больше места, чем кортежи. Например, на 64-хразрядной системе для хранения двух элементов в списке нужно 80 байт, а в кортеже — 64 (на 16 байт меньше). Для 1000 элементов отличие будет уже на 1064 байта. Это несущественно на небольших проектах, но становится актуальным для больших информационных систем, работающих с большим количеством данных, особенно в совокупности с другими способами оптимизации.
Совет: там, где нет потребности в изменении содержимого коллекции, используйте кортежи вместо списков.
#python #оптимизация
tuple) и списки (list). В книгах, статьях, в реальных проектах чаще встречается использование списков вместо кортежей там, где в этом нет необходимости:gender = models.PositiveIntegerField(
'Пол',
choices=[GENDER_MALE, GENDER_FEMALE],
)
При хранении одних и тех же данных списки занимают больше места, чем кортежи. Например, на 64-хразрядной системе для хранения двух элементов в списке нужно 80 байт, а в кортеже — 64 (на 16 байт меньше). Для 1000 элементов отличие будет уже на 1064 байта. Это несущественно на небольших проектах, но становится актуальным для больших информационных систем, работающих с большим количеством данных, особенно в совокупности с другими способами оптимизации.
Совет: там, где нет потребности в изменении содержимого коллекции, используйте кортежи вместо списков.
#python #оптимизация
👍1
В отличие от кортежей и списков, множества (
Однако у словарей и множеств есть важное преимущество: скорость поиска элемента в коллекции (оператор
Для множества из почти миллиона целых чисел время поиска случайного числа составляет в среднем 1.86 µs. Теперь сделаем замеры для множества из ста целых чисел, т.е. в 10 000 раз меньше:
Из этих замеров видно, что время поиска элемента практически не зависит от количества элементов в коллекции, т.е. равно O(1). Поиск ключа в словаре работает с аналогичной скоростью.
Совет: для решения задач, в которых интенсивно используется поиск элемента в коллекции, используйте словари и множества.
#python #оптимизация
set, frozenset) и словари (dict) занимают больше места в памяти. Например, для множества из двух элементов понадобится 224 байта (кортеж — 64). Для 1000 элементов — 32992 и 8048 соответственно.Однако у словарей и множеств есть важное преимущество: скорость поиска элемента в коллекции (оператор
in). Если для кортежей и списков сложность данной операции равна O(n), то для множеств это O(1). Сравните:In [2]: a = {randint(1, 10000000) for _ in range(1000000)}
In [3]: len(a)
Out[3]: 951498
In [4]: %timeit randint(1, 10000000) in a
1.86 µs ± 19.1 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)Для множества из почти миллиона целых чисел время поиска случайного числа составляет в среднем 1.86 µs. Теперь сделаем замеры для множества из ста целых чисел, т.е. в 10 000 раз меньше:
In [5]: a = {randint(1, 10000000) for _ in range(100)}
In [6]: len(a)
Out[6]: 100
In [7]: %timeit randint(1, 10000000) in a
1.75 µs ± 13.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)Из этих замеров видно, что время поиска элемента практически не зависит от количества элементов в коллекции, т.е. равно O(1). Поиск ключа в словаре работает с аналогичной скоростью.
Совет: для решения задач, в которых интенсивно используется поиск элемента в коллекции, используйте словари и множества.
#python #оптимизация
В двух предыдущих сообщениях я приводил размеры коллекций в байтах, при этом не делал оговорок по поводу того, какой тип данных хранится в коллекции. Это не ошибка. В коллекциях хранятся только ссылки на объекты (указатели, адреса), а размер указателя определяется разрядностью интерпретатора: в 32-хразрадном — 4 байта, в 64-хразрадном — 8 байт. Поэтому размер коллекции по сути не зависит от типа данных, содержащихся в ней.
#python
#python
Как известно, в Python поддерживается множественное наследование. С учетом этого классы можно разделить на три вида: обычные классы, абстрактные базовые классы и классы-примеси.
Обычные классы предоставляют доступ к своему функционалу через свои экземпляры.
Абстрактные базовые классы, наоборот, не предполагают создания экземпляров, т.к. требуют реализации абстрактных методов в потомках. В ООП есть еще интерфейсы, но в языках с поддержкой множественного наследования они не выделяются в отдельную конструкцию, а реализуются через абстрактные базовые классы, состоящие только из абстрактных методов.
Классы-примеси (mixins) содержат только часть реализации, "подмешиваемой" к другим классам.
Рассмотрим вышесказанное в контексте MRO (Method Resolution Order). При использовании в классах-потомках доступ к методам базовых классов осуществляется через
Исходя из этого список базовых классов нужно формировать в следующем порядке:
1. Классы-примеси.
Т.к. их смысл в первую очередь в том, чтобы расширять другие классы, они по определению должны содержать вызов
2. Обычные классы.
В конце иерархии наследования обычных классов всегда есть метод, на котором заканчивается цепочка вызовов. Прежде всего это важно при переопределении абстрактных методов. Именно поэтому наличие перед абстрактными базовыми классами в списке наследования обычного класса гарантирует отсутствие ошибок при вызове методов.
3. Абстрактные базовые классы.
Как было сказано выше, вызов абстрактного метода возможен через
Совет: в списке наследования сначала указывайте классы-примеси, затем обычные классы и только потом абстрактные базовые классы.
#python #ООП
Ссылки:
- описание модуля
Обычные классы предоставляют доступ к своему функционалу через свои экземпляры.
Абстрактные базовые классы, наоборот, не предполагают создания экземпляров, т.к. требуют реализации абстрактных методов в потомках. В ООП есть еще интерфейсы, но в языках с поддержкой множественного наследования они не выделяются в отдельную конструкцию, а реализуются через абстрактные базовые классы, состоящие только из абстрактных методов.
Классы-примеси (mixins) содержат только часть реализации, "подмешиваемой" к другим классам.
Рассмотрим вышесказанное в контексте MRO (Method Resolution Order). При использовании в классах-потомках доступ к методам базовых классов осуществляется через
super. Если иерархия наследования включает несколько уровней, то цепочка вызовов super пройдет как раз в соответствии с MRO. Если коротко, то последовательность вызовов в иерархии будет идти слева направо, снизу вверх. Также стоит отметить, что если в методе не вызывается super, то цепочка вызовов завершается на таком методе.Исходя из этого список базовых классов нужно формировать в следующем порядке:
1. Классы-примеси.
Т.к. их смысл в первую очередь в том, чтобы расширять другие классы, они по определению должны содержать вызов
super. Это означает, что цепочка вызовов не прервется в классе-примеси и функционал основного класса будет достижим. Если же указать класс-примесь после обычного класса, то при наличии абстрактных классов в иерархии наследования может быть вызван абстрактный метод, что в Python приводит к ошибке.2. Обычные классы.
В конце иерархии наследования обычных классов всегда есть метод, на котором заканчивается цепочка вызовов. Прежде всего это важно при переопределении абстрактных методов. Именно поэтому наличие перед абстрактными базовыми классами в списке наследования обычного класса гарантирует отсутствие ошибок при вызове методов.
3. Абстрактные базовые классы.
Как было сказано выше, вызов абстрактного метода возможен через
super и это приводит к ошибке. Поэтому абстрактные классы в списке классов-предков нужно указывать последними, тогда цепочка вызовов завершится на обычном классе. Но при этом останется доступен функционал абстрактных классов в части контрля обязательности переопределения всех абстрактных методов и свойств (в т.ч. контроль реализации интерфейсов).Совет: в списке наследования сначала указывайте классы-примеси, затем обычные классы и только потом абстрактные базовые классы.
#python #ООП
Ссылки:
- описание модуля
abc https://docs.python.org/3/library/abc.htmlВ таких языках, как C++, Java, C#, PHP и др. реализованы т.н. модификаторы доступа:
В Python таких модификаторов доступа нет и все члены класса доступны отовсюду. При этом есть соглашение (https://docs.python.org/3/tutorial/classes.html#private-variables) по именованию членов класса, согласно которому они разделяются на три типа, у которых имя:
1. НЕ начинается с символа подчеркивания (
2. начинается с одного символа подчеркивания (
2. начинается с двух символов подчеркивания (
Отдельную группу составляют члены классов со специальными именами, начинающимися и заканчивающимися с двойного подчеркивания, но сейчас речь не о них.
Аналогия между модификаторами доступа и именами в Python следующая:
1.
2.
3.
Совет 1: при проектировании классов уделяйте должное внимание сокрытию его членов, служебные атрибуты и методы, не предназначенные для использования извне, делайте закрытыми.
Совет 2: Не нарушайте соглашения по именованию членов класса, т.к. в будущих версиях этого класса его внутреннее устройство может измениться, что нарушит работоспособность вашей программы.
#python #ООП
public, protected и private. С их помощью можно указать степень сокрытия членов класса. Если коротко, то означают они следующее: public — члены класса доступны отовсюду, protected — из методов самого класса и его потомков, private — только из методов класса.В Python таких модификаторов доступа нет и все члены класса доступны отовсюду. При этом есть соглашение (https://docs.python.org/3/tutorial/classes.html#private-variables) по именованию членов класса, согласно которому они разделяются на три типа, у которых имя:
1. НЕ начинается с символа подчеркивания (
foo);2. начинается с одного символа подчеркивания (
_bar);2. начинается с двух символов подчеркивания (
__baz).Отдельную группу составляют члены классов со специальными именами, начинающимися и заканчивающимися с двойного подчеркивания, но сейчас речь не о них.
Аналогия между модификаторами доступа и именами в Python следующая:
1.
public — нет подчеркивания, можно использовать отовсюду;2.
protected — один символ подчеркивания, можно использовать только в методах класса и его потомках;3.
private — два символа подчеркивания, можно использовать только в методах класса.Совет 1: при проектировании классов уделяйте должное внимание сокрытию его членов, служебные атрибуты и методы, не предназначенные для использования извне, делайте закрытыми.
Совет 2: Не нарушайте соглашения по именованию членов класса, т.к. в будущих версиях этого класса его внутреннее устройство может измениться, что нарушит работоспособность вашей программы.
#python #ООП
Все имена в Python являются ссылками на объекты. Имена переменных, классов, функций и т.д. — это только адрес объекта в памяти, но не сам объект. Убедиться в этом можно с помощью функции
Ссылаться на объект можно не только из переменных, но и из элементов коллекций (
Однако в Python есть возможность ссылаться на объекты не увеличивая счетчик ссылок. Делается это с помощью слабых ссылок (weak references). Инструментарий для работы со слабыми ссылками расположен в модуле стандартной библиотеки
- класс
- словарь
- словарь
- множество
и другое.
В коллекциях
Например, недавно я использовал
За счет того, что записи в таком словаре существуют только пока существует объект, на который ссылается ключ, удаление кешированных объектов происходит вместе с основным объектом и нет необходимости заботиться об очистке кэша.
Совет: ознакомьтесь с содержимым модуля
#python
Ссылки:
- модуль
id, которая возвращает уникальный идентификатор объекта (в CPython -- адрес объекта в памяти):>>> import datetime
>>> id(datetime)
139662440951544
>>> a = datetime
>>> id(a)
139662440951544
Ссылаться на объект можно не только из переменных, но и из элементов коллекций (
tuple, 'list и т.д.), атрибутов классов. При этом у каждого объекта есть счетчик ссылок. При создании ссылки на объект счетчик ссылок увеличивается, а при удалении ссылки — уменьшается. Когда счетчик ссылок равен 0, объект удаляется из памяти.Однако в Python есть возможность ссылаться на объекты не увеличивая счетчик ссылок. Делается это с помощью слабых ссылок (weak references). Инструментарий для работы со слабыми ссылками расположен в модуле стандартной библиотеки
weakref:- класс
ref для создания слабой ссылки на объект;- словарь
WeakKeyDictionary с ключами, являющимися слабыми ссылками;- словарь
WeakValueDictionary со значениями, являющимися слабыми ссылками;- множество
WeakSetи другое.
В коллекциях
WeakKeyDictionary, WeakValueDictionary и WeakSet элементы существуют до тех пор, пока сущестует объект, на который ссылается слабая ссылка:>>> d = WeakKeyDictionary()
>>> class C:
pass
>>> c = C()
>>> d[c] = 'qwerty'
>>> len(d)
1
>>> del c
>>> len(d)
0
Например, недавно я использовал
WeakKeyDictionary для оптимизации работы функции get_original_object, которая по объекту модели Django возвращает его исходное состояние, заново загружая его из БД. Это нужно для того, чтобы сравнить, какие поля в объекте изменились и выполнить соответствующие действия. Особенность использования функции была в том, что она могла вызываться для одного и того же экземпляра модели много раз в разных частях системы. Для того, чтобы предотвратить повторную загрузку объектов в этой функции, был задействован словарь WeakKeyDictionary для организации кэша исходных объектов:_get_original_object_cache = WeakKeyDictionary()
def get_original_object(obj):
if obj in _get_original_object_cache:
return _get_original_object_cache[obj]
...
За счет того, что записи в таком словаре существуют только пока существует объект, на который ссылается ключ, удаление кешированных объектов происходит вместе с основным объектом и нет необходимости заботиться об очистке кэша.
Совет: ознакомьтесь с содержимым модуля
weakref и возможностями слабых ссылок, они могут быть полезны для решения некоторых задач, в т.ч. кеширования.#python
Ссылки:
- модуль
weakref: https://docs.python.org/3.6/library/weakref.htmlМетод 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
