Общие принципы оптимизации кода Python
«Если необходимо, чтобы Ваш код работал быстрее,то вероятно, следует просто использовать PyPy»
Гвидо ван Россум (создатель Python).
Любой код, в первую очередь, должен быть написан обдуманно и быть рабочим и только потом можно приступать к его дополнительной оптимизации. При этом оптимизация должна проходит прямо в процессе разработки, так как потом сложно будет изменить множество мелких моментов.
Оптимизация производительности в любом языке программирования не имеет четкого алгоритма действий. Иногда даже незначительные изменения в «нужном месте» могут ускорить работу кода Python в несколько раз. Оптимизация кода — это придание важности мелочам.
Избегайте применение глобальных переменных.
Python очень медленно обрабатывает доступ к глобальным переменным, особенно внутри циклов. Также множественное применение глобальных переменных очень часто приводит к спагетти-коду, что вызывает дополнительные трудности и проблемы.
Использование сторонних модулей и библиотек.
Обращайте внимание на каком языке написан сторонний модуль или библиотека. Если есть возможность, то используйте библиотеки, написанные на С, так как они работают быстрее, а это значит, что и программа на Python будет работать немного быстрее.
Активно используйте встроенные инструменты Python.
Их применение ускоряет код за счет того, что они предварительно оптимизированы, скомпилированы и следовательно выполняются быстрее.
Примером таких инструментов может быть:
- встроенная функция map() ;
- встроенная функция str.join() ;
- встроенный модуль itertools ;
- и многое другое.
Работа над кодом.
- пишите код обдуманно и максимально лаконично;
- по возможности, внедряйте кэширование объектов;
- не создавать лишние экземпляры объектов (помните, объекты потребляют дополнительную память);
Перевод статьи словацкого разработчика Мартина Хайнца, в которой он описывает девять практических советов о том, как сделать разработку на Python лучше.
Обрабатывайте входные данные.
Чем больше размер программы, тем выше шансы пропустить уязвимость в коде. Один из способов обезопасить себя от возможных ошибок — очистка входных данных перед выполнением программы (input sanitization). В большинстве случаев при таком подходе достаточно поменять регистр символов или использовать регулярные выражения. Но для сложных случаев есть и более эффективный способ:
user_input = "This\nstring has\tsome whitespaces. \r\n" character_map = ord('\n') : ' ', ord('\t') : ' ', ord('\r') : None > user_input.translate(character_map)
Это пример заменяет «пробельные» символы \n и \t обычным пробелом и удаляет \r (все перечисленные конструкции обозначают разные виды пробелов). В зависимости от задач, можно генерировать таблицы соответствий разного размера. Задачу облегчает встроенный модуль unicodedata и функция combining() для генерации и отображения. Их можно использовать для удаления всех акцентов из строки.
Используйте итераторы со срезами.
Итератор — это инструмент для поточной обработки данных. Он отвечает за упрощение навигации по элементам: списку, словарю и так далее. Это такой объект-перечислитель, который выдаёт следующий элемент. В основном его используют в цикле for/in .
Но использовать итератор на полную мощность нужно не всегда. И тут незадача: если попытаться использовать срез итератора, то получим ошибку TypeError . Это произойдёт из-за того, что объект итератора не является подписываемым. К счастью, на такой случай есть простое решение:
import itertools s = itertools.islice(range(50), 10, 20) for val in s: .
Используя itertools.islice() , можно создать объект islice , который сам по себе является итератором, производящим нужные значения. Важно отметить, что этот объект использует все элементы генератора вплоть до начала среза, что делает itertools.islice() мощным инструментом.
Пропускайте начало итерируемого объекта.
Иногда приходится работать с файлами, которые начинаются с неизвестного количества бесполезных строк, например, с комментариев. И тут модуль itertools снова предлагает простое решение:
string_from_file = """ // Автор: . // Лицензия: . // // Дата: . """ import itertools for line in itertools.dropwhile(lambda line: line.strip().startswith('//'), string_from_file.splitlines()): print(line)
Этот фрагмент кода создаёт строки, пропуская начальные комментарии. Такой подход может быть полезен, если нужно отбросить элементы (в нашем случае строки) в начале итерируемого объекта.
Используйте kwargs .
Если при разработке программы нужно выполнить несколько похожих действий, то лучшее решение — определить функции для многократного использования в коде. Для этого создается функция с аргументами. Но что делать, если аргументы функции определены, а для ее многократного использования (унификации) нужно передавать разное количество аргументов? Для этого можно использовать ключевые аргументы функции kwargs .
Этот инструмент очень пригодится для создания функции только с именованными аргументами. Это даст возможность (или, скорее, обяжет) использовать такие функции более прозрачно:
def test(*, a, b): pass test("value for a", "value for b") # Traceback (most recent call last): # File "ad.py", line 4, in # test("value for a", "value for b") # TypeError: test() takes 0 positional arguments but 2 were given test(a="value1", b="value2") # Работает.
Как видно из примера, задачу легко решить, если поместить аргумент * перед ключевыми словами. И, конечно, можно использовать позиционные аргументы, если вставить их до аргумента * .
Используйте объекты, которые поддерживают оператор with .
Открыть файл и заблокировать фрагмент кода можно с помощью оператора with , но можно ли сделать это, пользуясь собственным методом? Да, можно реализовать протокол context manager, используя методы __enter__ и __exit__ :
class Connection: def __init__(self): . def __enter__(self): # инициализация соединения def __exit__(self, type, value, traceback): # закрытие соединения # использование with Connection() as conn: # __enter__() executes . # conn.__exit__() executes
Это распространённый вариант управления контекстом в Python, но есть и более простой способ:
from contextlib import contextmanager @contextmanager def tag(name): print (f"name>>") yield print (f"name>>") with tag("h1"): print("This is Title.")
Этот фрагмент кода реализует протокол управления контекстом, используя декоратор менеджера contextmanager . Первая часть функции tag() (до yield ) выполняется при входе в блок with , затем исполняется блок, а после него и остальная часть функции tag() .
Сохраните всё с помощью __slots__ .
Если программа создаёт большое количество инстансов какого-либо класса, то она может потребовать больше памяти. Это связано с тем, что Python использует словари для представления атрибутов инстансов классов. Это делает язык быстрым, но не очень эффективным с точки зрения оптимизации памяти. Если это становится проблемой, то поможет магический атрибут __slots__ :
class Person: __slots__ = ["first_name", "last_name", "phone"] def __init__ (self, first_name, last_name, phone): self.first_name = first_name self.last_name = last_name self.phone = phone
При определении атрибута __slots__ Python использует небольшой массив фиксированного размера для атрибутов вместо словаря. Это значительно сокращает объём памяти, необходимый для каждого инстанса.
Следует учесть, что в этом случае есть и недостатки: нельзя объявлять какие-либо новые атрибуты помимо используемых в __slots__ , а классы со __slots__ не могут использовать множественное наследование.
Ограничьте использование процессора и памяти.
Если лень оптимизировать память программы или корректировать работу процессора, то можно просто установить лимиты. К счастью, в Python для этого есть специальный модуль resource :
import signal import resource def time_exceeded(signo, frame): print("CPU exceeded. ") raise SystemExit(1) def set_max_runtime(seconds): soft, hard = resource.getrlimit(resource.RLIMIT_CPU) resource.setrlimit(resource.RLIMIT_CPU, (seconds, hard) ) signal.signal(signal.SIGXCPU, time_exceeded) def set_max_memory(size): soft, hard = resource.getrlimit(resource.RLIMIT_AS) resource.setrlimit(resource.RLIMIT_AS, (size, hard))
Здесь можно увидеть две опции: установку на максимальное процессорное время и максимальный предел используемой памяти.
При ограничении работы процессора необходимо извлечь мягкий и жёсткий лимиты для конкретного ресурса ( resource.RLIMIT_CPU ), а затем установить его значение. Для этого используется количество секунд, указанное в аргументе, и ранее полученное жёсткое ограничение. В конце нужно зарегистрировать сигнал, который будет отвечать за выход из системы, если процессорное время превышено.
Что касается памяти, то, как и в случае с процессором, устанавливаем мягкий и жёсткий лимиты. Для этого используется функция resource.setrlimit() с аргументом size и жёсткое ограничение, которое было получено.
Управляйте экспортом элементов.
Такие языки программирования, как Go, имеют механизм экспорта только для элементов (переменных, методов, интерфейсов) начинающихся с заглавной буквы. В Python подобного можно добиться с помощью переменной __all__ :
def foo(): pass def bar(): pass __all__ = ["bar"]
В данном случае, благодаря __all__ экспортирован будет не весь код, а только функция bar() . Кроме того, можно оставить переменную пустой, то при попытке импорта из этого модуля ничего не попадёт в экспорт, что приведёт к ошибке AttributeError .
Упростите использование операторов сравнения.
Использовать все операторы сравнения для одного класса может быть довольно сложно, учитывая, что их немало: __lt__ , __le__ , __gt__ или __ge__ . Но есть ли более простой способ сделать это? Здесь поможет functools.total_ordering :
from functools import total_ordering @total_ordering class Number: def __init__(self, value): self.value = value def __lt__(self, other): return self.value other.value def __eq__ (self, other): return self.value == other.value print(Number(20) > Number(3)) print(Number(1) Number(5)) print(Number(15) >= Number(15)) print(Number(10) Number(2))
Как это работает? Декоратор @total_ordering автоматически добавляет все остальные методы. В этом случае нужно только определить __lt__ и __eq__ , а все остальные пробелы за нас заполнит декоратор.
Оптимизация Python: простые способы ускорить и очистить код
Сам по себе язык программирования Python очень хорош и используется во многих сферах IT. Но у него есть один существенный минус, который тянется за ним шлейфом — это низкая производительность по сравнению с другими языками. Поэтому для программ на Python очень необходима оптимизация.
Нет смысла сейчас обсуждать чем вызвана медлительность Python, потому что на эти качества пока невозможно повлиять, так как они являются особенностью этого языка. При этом можно применить ряд инструментов и подходов при написании кода на Python, чтобы ускорить работу программ на этом языке силами самого языка.
Оптимизация Python
- Использовать специальные модули для ускорения работы программ на Питоне по типу Psyco. Но такие модули имеют слабую поддержку и непонятно сколько они еще прослужат, хотя ускоряют работу Python-программ на десятки процентов.
- Использовать Python C Extensions, чтобы переписать часть кода программы на языке С, тем самым ускорив ее производительность. Но для этого нужно обладать дополнительными знаниями, например знать тот же язык «С» и технологию C API. Помимо этого очень сильно снижается скорость разработки программ «Python+С».
- Сменить используемые алгоритмы. Но и тут иногда возникает такая ситуация, что используется достаточно «шустрый» алгоритм и заменить не на что, но при этом скорость работы программы не радует.
Оптимизация производительности Python его же инструментами
Приведем несколько рекомендаций, как проводится оптимизация Python, а конкретнее, на что нужно обращать внимание в процессе разработки, чтобы конечный скрипт работал эффективнее.
Избегать применения глобальных переменных
Чрезмерное применение глобальных переменных в любых языках программирования считается дурным тоном. Потому что такое действие может п о влечь за собой множество побочных эффектов, от которых тяжело будет избавит ь ся. Множественное применение глобальных переменных очень часто приводит к спагетти-коду, что вызывает дополнительные трудности и проблемы.
Специфика Python такова, что он очень медленно обрабатывает доступ к глобальным переменным, особенно внутри циклов. Поэтому их меньшее количество — это первый шаг ускорить работу Питона.
Внешние библиотеки
- «cPickle», а не «pickle»;
- — это библиотека для Питона, которая поддерживает функции и типы С;
- РуРу — это JIT-компилятор, который ускоряет код Python;
- пакет Pandarallel, который распараллеливает операции на несколько процессов;
- библиотеку NumPy;
- и многое другое.
Применение встроенных инструментов Python
- функцию map();
- модуль cProfile;
- «list Component», где это возможно, вместо «For Loop»;
- связанные списки;
- функции range() и xrange();
- наборы Python;
- метод join();
- и др.
Работа над кодом
- применять внутренние инструменты Python ;
- не создавать лишние глобальные переменные, тщательно подбирать библиотеки и расширения;
- по возможности внедрять код С;
- писать сам код обдуманно и максимально лаконично;
- по возможности внедрять кеширование объектов;
- не создавать лишние экземпляры объектов;
- и др.
Заключение
В программировании бытует мнение, что любой написанный код, в первую очередь, должен быть эффективным и рабочим. Только потом можно приступать к его оптимизации. При этом в Python оптимизация должна проходит прямо в процессе разработки, так как она состоит из множества мелких моментов, которые потом будет сложно изменить.
Оптимизация производительности Python не имеет четкого алгоритма действий. Над этой проблемой многие разработчики «бьются» по-своему, поэтому мест, где можно оптимизировать код Питона , очень много. Иногда даже незначительные изменения типа «вместо «**» правильно написать «*» в нужном месте» могут ускорить работу кода Python в несколько раз. Оптимизация Python — это придание важности даже мелочам, поэтому экспериментируйте.
Мы будем очень благодарны
если под понравившемся материалом Вы нажмёте одну из кнопок социальных сетей и поделитесь с друзьями.
Cython: более чем 30-кратное ускорение Python-кода
Python — это язык, который любят многие программисты. Этим языком невероятно легко пользоваться. Всё дело в том, что код, написанный на Python, отличается интуитивной понятностью и хорошей читабельностью. Однако в разговорах о Python часто можно слышать одну и ту же жалобу на этот язык. Особенно тогда, когда о Python говорят знатоки языка C. Вот как она звучит: «Python — это медленно». И те, кто так говорят, не грешат против истины.
В сравнении со многими другими языками программирования Python — это, и правда, медленно. Вот результаты испытаний, в ходе которых сопоставляется производительность разных языков программирования при решении различных задач.

Есть несколько способов ускорения Python-программ. Например, можно применять библиотеки, рассчитанные на использование нескольких ядер процессора. Тем, кто работает с Numpy, Pandas или Scikit-Learn, можно посоветовать взглянуть на программный комплекс Rapids, позволяющий задействовать GPU при проведении научных расчётов.
Все эти методики ускорения работы хороши в тех случаях, когда решаемые с помощью Python задачи могут быть распараллелены. Например — это задачи по предварительной обработке данных или операции с матрицами.
Но как быть в том случае, если ваш код — это чистый Python? Что если у вас есть большой цикл for , который вам совершенно необходимо использовать, и выполнение которого просто нельзя распараллелить из-за того, что обрабатываемые в нём данные должны обрабатываться последовательно? Можно ли как-то ускорить сам Python?
Ответ на этот вопрос даёт Cython — проект, используя который можно значительно ускорить код, написанный на Python.
Что такое Cython?
Cython, по своей сути, это промежуточный слой между Python и C/C++. Cython позволяет писать обычный Python-код с некоторыми незначительными модификациями, который затем напрямую транслируется в C-код.
Единственное изменение Python-кода при этом заключается в добавлении к каждой переменной информации об её типе. При написании обычного кода на Python переменную можно объявить так:
x = 0.5
При использовании Cython при объявлении переменной нужно указать её тип:
cdef float x = 0.5
Эта конструкция сообщает Cython о том, что переменная представляет собой число с плавающей точкой. По такому же принципу объявляют переменные и в C. При использовании обычного Python типы переменных определяются динамически. Явное объявление типов, применяемое в Cython — это то, что делает возможным преобразование Python-кода в C-код. Дело в том, что в C необходимо явное объявление типов переменных.
Установка Cython предельно проста:
pip install cython
Типы в Cython
При использовании Cython можно выделить два набора типов. Один — для переменных, второй — для функций.
Если речь идёт о переменных, то тут нам доступны следующие типы:
- cdef int a, b, c
- cdef char *s
- cdef float x = 0.5 (число одинарной точности)
- cdef double x = 63.4 (число двойной точности)
- cdef list names
- cdef dict goals_for_each_play
- cdef object card_deck
При работе с функциями нам доступны следующие типы:
- def — обычная Python-функция, вызывается только из Python.
- cdef — Cython-функция, которую нельзя вызвать из обычного Python-кода. Такие функции можно вызывать только в пределах Cython-кода.
- cpdef — Функция, доступ к которой можно получить и из C, и из Python.
Ускорение кода с использованием Cython
Начнём с создания Python-бенчмарка. Это будет цикл for , в котором выполняется вычисление факториала числа. Соответствующий код на чистом Python будет выглядеть так:
def test(x): y = 1 for i in range(1, x+1): y *= i return y
Cython-эквивалент этой функции очень похож на её исходный вариант. Соответствующий код нужно поместить в файл с расширением .pyx . Единственное изменение, которое нужно внести в код, заключается в добавлении в него сведений о типах переменных и функции:
cpdef int test(int x): cdef int y = 1 cdef int i for i in range(1, x+1): y *= i return y
Обратите внимание на то, что перед функцией стоит ключевое слово cpdef . Это позволяет вызывать данную функцию из Python. Кроме того, тип назначен и переменной i , играющей роль счётчика цикла. Не будем забывать о том, что типизировать нужно все переменные, объявленные в функции. Это позволит компилятору C узнать о том, какие именно типы ему использовать.
Теперь создадим файл setup.py , который поможет нам преобразовать Cython-код в C-код:
from distutils.core import setup from Cython.Build import cythonize setup(ext_modules = cythonize('run_cython.pyx'))
python setup.py build_ext --inplace
Теперь С-код готов к использованию.
Если взглянуть в папку, в которой находится Cython-код, там можно будет найти все файлы, необходимые для запуска C-кода, включая файл run_cython.c . Если вам интересно — откройте этот файл и посмотрите на то, какой С-код сгенерировал Cython.
Теперь всё готово к тестированию нашего сверхбыстрого C-кода. Ниже приведён код, используемый для тестирования и сравнения двух вариантов программы.
import run_python import run_cython import time number = 10 start = time.time() run_python.test(number) end = time.time() py_time = end - start print("Python time = <>".format(py_time)) start = time.time() run_cython.test(number) end = time.time() cy_time = end - start print("Cython time = <>".format(cy_time)) print("Speedup = <>".format(py_time / cy_time))
Код этот устроен очень просто. Мы импортируем необходимые файлы — так же, как импортируются обычные Python-файлы, после чего вызываем соответствующие функции, делая это так же, как если бы мы всё время работали бы с обычными Python-функциями.
Взгляните на следующую таблицу. Можно заметить, что Cython-версия программы оказывается быстрей её Python-версии во всех случаях. Чем масштабнее задача — тем больше и ускорение, которое даёт использование Cython.
| Число | Показатель Python Time | Показатель Cython Time | Показатель Speedup |
| 10 | 1.6689300537109375e-06 | 4.76837158203125e-07 | 3.5 |
| 100 | 3.337860107421875e-06 | 4.76837158203125e-07 | 7.0 |
| 1000 | 2.193450927734375e-05 | 9.5367431640625e-07 | 23.0 |
| 10000 | 0.0002090930938720703 | 6.4373016357421875e-06 | 32.481 |
| 100000 | 0.0021562576293945312 | 6.008148193359375e-05 | 35.89 |
| 1000000 | 0.02128767967224121 | 0.0005953311920166016 | 35.75 |
| 10000000 | 0.2148280143737793 | 0.00594782829284668 | 36.1187317112278 |
Итоги
Использование Cython позволяет значительно ускорить практически любой код, написанный на Python, не прилагая к этому особенных усилий. Чем больше в программе циклов и чем больше данных она обрабатывает — тем лучших результатов можно ждать от применения Cython.
Уважаемые читатели! Используете ли вы Cython в своих проектах?
Как я могу ускорить выполнение кода?
У меня есть код, который решает некую задачу, аля бинарный поиск, как я могу сделать сложность n+m или n*m? Код:
n = list(map(int, input().split())) m = list(map(int, input().split())) def f(a, p): left = 0 right = 0 for i in range(p): if a[i] == a[p]: left += 1 for i in range(p+1, len(a)): if a[i] != a[p]: right += 1 return left * right for x in m: max_val = -1 max_pos = -1 for i in range(len(n)+1): val = f(n[:i] + [x] + n[i:], i) if val > max_val: max_val = val max_pos = i print(max_val, end=' ')
Код все тесты проходит, но по времени нет, как можно ускорить данный код на пару секунд по выполнению? Входные данные:
1 1 2 2 2 2 1 1 1 1 2 1
8 12 8
Задача: В шеренгу друг за другом стоят n человек, рост i-го из них равен ai условных единиц. Вы тоже собираетесь встать в эту шеренгу, при чем вам хочется встать на такую позицию p, чтобы f(p) = [количество людей левее вас того же роста, что и вы] умножить на [количество людей правее вас ростом, не равным росту вас] было максимально. Для этого вы можете встать в начало шеренги, в её конец, или между любыми 2мя соседними людьми. К сожалению вы не можете точно вспомнить ваш рост, у вас есть только m предположений о том, каким он может быть, и для каждого из них вы хотели бы знать оптимальную позицию, на которую вам стоило бы встать.
