Часто в литературе, сети Интернет и в коде реальных проектов можно встретить такое использование генераторных выражений:
Такой код хоть и правильный с точки зрения синтаксиса, тем не менее не является оптимальным. В большинстве случаев правильнее писать так:
Разница в том, что в первом случае в функцию
Вывод: создавайте коллекции (кортежи, списки и т.п.) на основе генераторов только там, где это действительно нужно.
#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.htmlPython: функции
Почти всегда это делается подобным образом:
#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
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
Функция 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