3 простых способа ускорить код на Python
Если вы разрабатываете на Python, наверняка задумывались о том, как ускорить код. В статье разбираем три способа, которые помогут в этом.
Всем добрый день, меня зовут Даниил, я backend-разработчик из департамента IT Kokoc Group.
Даниил Громов
Backend-разработчик в департаменте IT Kokoc Group
Python — это мощный и гибкий язык программирования. Однако из-за динамической типизации и интерпретируемости его производительность оставляет желать лучшего. И наверное, каждый, кто разрабатывает на нём продолжительное время, задавался вопросом, как ускорить код на Python. В этой статье рассмотрим три способа на примере одного кейса.
Кейс для ускорения кода на Python
Рассмотрим простую функцию, которая вычисляет сумму квадратов элементов большого списка чисел.
В этом примере мы используем стандартный подход с помощью встроенной функции sum и генератора списка
А теперь применим техники ускорения кода и понаблюдаем, как каждая из них отразится на скорости выполнения программы.
1. List comprehension
List comprehension — эффективный способ создавать и обрабатывать списки в одной строке. В Python он работает быстрее, чем традиционный цикл for , потому что весь процесс в нём оптимизирован и протекает внутри интерпретатора.
List comprehensions обычно вызывают меньше методов, чем for. Второй для каждой итерации вызывает метод append, что создаёт дополнительную нагрузку и обновляет состояние переменной на каждой итерации. А list comprehension создаёт новый список сразу, такой подход может быть более эффективным.
Важно отметить, что, хотя list comprehension обычно быстрее, его не всегда следует использовать. Если вы пытаетесь сделать что-то сложнее обычного преобразования данных, он может быть тяжеловат для понимания. Ясный и читаемый цикл for может быть лучшим выбором.
2. Map() и lambda-функции
map() — встроенная функция, которая всегда используется в комбинации с итерируемой последовательностью, в данном случае со списком. Она идёт по каждому элементу последовательности (то есть к каждому объекту из списка) и применяет к нему функцию, которую приняла первым позиционным аргументом.
map() реализована на более быстром Си. И, как и list comprehension, оптимизирована на уровне интерпретатора Python. То есть при её использовании вы также передаёте всю работу интерпретатору, который оптимизирует выполнение операции.
Кроме того, map() в Python возвращает итератор, а не список. А итераторы — более эффективный способ обработки больших объёмов данных. Они не требуют загрузки всего набора данных в память сразу, а генерируют каждый элемент по мере необходимости. Это значительно снижает потребление памяти, особенно при работе с большими наборами данных.
Это также связано с принципом «ленивого вычисления»: итератор, возвращённый функцией map(), вычисляет каждый элемент «на лету» и только при необходимости.
Lambda-функции в Python — это анонимные функции, которые можно определить прямо в месте использования. То есть вместо того чтобы задавать отдельную именованную функцию через def , можно написать короткую лямбду — прямо в вызове map() .
3. NumPy и Pandas
NumPy — это мощная библиотека, которая предоставляет объекты массивов N-мерного массива с эффективными операциями. При работе с большими массивами данных NumPy может быть намного эффективнее стандартных списков Python благодаря оптимизированным и векторизованным операциям.
Pandas — библиотека, предоставляющая эффективные структуры данных и инструменты анализа данных.
Фактически это надстройка над NumPy. Так что для ускорения кода логично использовать ту библиотеку, которая уже инсталлирована в проекте.
В основном NumPy и Pandas используют для работы с таблицами, когда нужно, допустим, выгрузить Excel-таблицу или рассортировать ячейки. Другие сферы — компьютерное зрение, векторная геометрия и так далее.
Код, в котором используют NumPy и Pandas, часто работает быстрее, чем аналогичный, но написанный с использованием стандартных функций и структур данных Python. И вот по каким причинам:
- Большая часть библиотек NumPy и Pandas написана на Cи и C++. Благодаря этому скорость выполнения выше.
- Векторизованные операции выполняются сразу над массивами данных, а не поэлементно. Это обеспечивает значительное увеличение производительности, особенно при работе с большими объёмами данных.
- Эффективное использование памяти. NumPy и Pandas используют эффективные структуры данных, которые занимают меньше памяти и позволяют выполнять операции быстрее.
- Функции оптимизированы для быстрого выполнения на больших объёмах данных.
Результаты
Время выполнения каждого подхода будет зависеть от машины и конкретного случая, но ориентировочно можно представить следующие результаты:
- стандартный подход: 0.25 секунды;
- map и lambda: 0.18 секунды;
- list comprehension: 0.17 секунды;
- NumPy: 0.006 секунды;
- Pandas: 0.006 секунды.
Как видно, оптимизация может значительно ускорить код на Python. Однако не стоит забывать, что оптимальность любого подхода зависит от конкретного случая, и не всегда нужно стремиться к максимальной скорости исполнения в ущерб читаемости и поддерживаемости (list comprehension, внутри которого ещё два list comprehension, map и лямбда-функции — и всё это в одну строку, конечно, ускорит код, но прочитать его будет сложно).
В большинстве случаев выбор должен основываться на том, какой вариант делает код более понятным и поддерживаемым.
Ускорь Python-код с помощью Microsoft Pyjion
Pyjion — проект с открытым исходным кодом, предназначенный для повышения производительности кода Python за счет оптимизированных возможностей JIT-компиляции пакета .NET. С помощью Pyjion можно добиться значительного повышения скорости работы приложений на Python. В этой статье разбираемся, как оптимизировать и ускорить Python код.
Денис Расулев · Mar 20, 2023 ·

Pyjion, JIT-расширение для CPython для компиляции кода Python в нативный CIL
Что такое Pyjion
Самый распространенный способ ускорить код на Python — использовать CPython, официальный интерпретатор Python. Он компилирует код в промежуточный язык, называемый байткодом , и делает это каждый раз, когда выполняется ваш код.
Это обеспечивает большую гибкость, но имеет и обратную сторону. Простые вычисления, в которых процессоры очень эффективны, такие как арифметика целых чисел и чисел с плавающей запятой, в CPython выполняются значительно медленнее, чем в других интерпретаторах.
На практике разработчики Python используют такие инструменты, как Cython, чтобы сделать числовые операции более эффективными, компилируя код заранее, используя расширения Си. Это эффективное решение для повышения производительности, но не идеальное для быстрых итераций или экспериментов, особенно в Jupyter.
Именно здесь на помощь приходит Pyjion. Pyjion — это пакет для Python 3.10, который анализирует код Python и оптимизирует его на лету, используя компилятор JIT, или “Just-in-Time” (Точно в Нужное Время). Его можно использовать в программе, просто импортировав библиотеку Pyjion и включив ее. Все, что выполняется после этого утверждения, компилируется JIT.
Как произноситcя
Cлово произносится как “пиджион”, от английского “pigeon” (голубь). @DinoV хотел придумать имя, в котором было бы что-то с “Python” — часть “Py” — и что-то с “JIT” — часть “JI” — и чтобы оно было произносимым.
Основные особенности
- JIT-компилятор с поддержкой профилей
- Встроенная 64-битная поддержка плавающих и целых чисел
- Маленький, быстрый компилятор
- Windows, macOS и Linux
- Поддержка процессоров Intel и ARM
- Встроенный IL и ASM дизассемблер
- Поддержка встроенных инструментов отладки и профилирования
Технология
Pyjion компилирует нативный байткод CPython в машинный код. Без Pyjion CPython использует основной цикл оценки (называемый frame evaluation loop ) для итерации опкодов. Компилятор Pyjion состоит из 3 основных этапов:
- Построение “таблицы стека” абстрактных типов в каждой позиции опкода
- Компилирование опкодов CPython в инструкции IL (ECMA335 CIL).
- Передача кодов операций CIL в компилятор .NET EE для преобразования в родной машинный код/ассемблер
Бенчмарки
Тесты показывают, что Python + Pyjion примерно в два-три раза быстрее обычного Python для реальной работы.

Некоторые из оптимизаций позволяют ускорить работу до 10 раз. Обычная арифметика, которая всегда является хорошим кандидатом для JIT-оптимизации, может быть на порядок быстрее.
Ограничения
Некоторые возможности Python, такие как операторы with или async/await , еще не реализованы. Обе эти функции находятся в дорожной карте. Pyjion включает промежуточный слой, который позволяет приложениям WSGI работать на Pyjion. Долго работающие приложения, такие как веб-приложения, являются хорошими кандидатами для JIT-ускорения.
Установка
После выполнения шагов по установке Pyjion его можно использовать как обычный модуль Python, который можно импортировать в среду разработки:
python -m pip install pyjion
После импорта Pyjion включите JIT с помощью функции enable :
import pyjion pyjion.enable()
Любой код Python, который вы напишете или импортируете после включения Pyjion, будет скомпилирован JIT. Вам не нужно выполнять функции в каком-либо специальном API, все полностью прозрачно:
1 2 3 4
>>> def half(x): . return x/2 >>> half(2) 1.0
Вы также можете запустить любой скрипт или модуль через Pyjion:
pyjion my_script.py
Или, для существующего модуля Python:
pyjion -m calendar
Сравнение
Сравнение Pyjion с:
PyPy
PyPy — это реализация Python со своим собственным JIT. Самым большим отличием от Pyjion является то, что PyPy не поддерживает все модули расширения C без модификации, если только они не используют CFFI или не работают с избранным подмножеством CPython C API, которое PyPy поддерживает. Pyjion также стремится поддерживать множество JIT-компиляторов, в то время как PyPy поддерживает только свой собственный JIT-компилятор.
Pyston
Pyston — это реализация Python, использующая LLVM в качестве JIT-компилятора. По сравнению с Pyjion, Pyston имеет частичную поддержку CPython C API, но не полную. Pyston также поддерживает только LLVM в качестве JIT-компилятора.
Numba
Numba — это JIT-компилятор для “ориентированного на массивы и математически тяжелого кода Python”. Это означает, что Numba ориентирован на научные вычисления, в то время как Pyjion пытается оптимизировать весь код Python. Numba также поддерживает только LLVM.
IronPython
IronPython — это реализация Python, реализованная с помощью .NET. В то время как IronPython пытается быть пригодным для использования из .NET, Pyjion не имеет истории совместимости с .NET. Это также означает, что IronPython не может использовать модули расширения C, в то время как Pyjion может.
Nuitka, Shedskin
И Nuitka, и Shedskin являются транспиляторами Python в C++, что означает, что они переводят код Python в эквивалентный код C++. Будучи JIT, Pyjion не является транспилятором.
Полезные ссылки
- Официальная страница: https://www.trypyjion.com/
- GitHub репозиторий: https://github.com/tonybaloney/pyjion
- Документация: https://pyjion.readthedocs.io/en/latest/
- Живое тестирование: https://live.trypyjion.com/
- Скачать: https://pypi.org/project/pyjion/
Скорость вычислений в Python
Я обучаю олимпиадному программированию на языках Python и C++. Сейчас занимаюсь поиском способов ускорить долгие вычисления на Python. Я тестировал одинаковые алгоритмы вычислений в двумерных массивах в C++ и Python и в отдельных случаях Python работал медленнее более чем в 50 раз, а для олимпиадного программирования очень важна скорость, т. к. на задачах стоит ограничение на общую длительность работы программы. Вопрос: какие есть способы повышения скорости обработки двумерных списков в Python используя только встроенные библиотеки?
Отслеживать
10.4k 21 21 золотой знак 25 25 серебряных знаков 53 53 бронзовых знака
задан 28 ноя 2019 в 10:25
67 6 6 бронзовых знаков
А можете привести в вопросе пример алгоритма на с++ и питоне, который при одном результате имеет такую разницу по выполнению?
28 ноя 2019 в 10:31
А разве на олимпиадах не дают разные ограничения по времени для разных языков?
28 ноя 2019 в 10:51
В Питоне есть режим компиляции в нативный код. Полученный в результате компиляции код должен быть не сильно медленнее С++ кода. Во всяком случае не в 50 раз медленнее.
28 ноя 2019 в 11:41
@gil9red, вот вам пример: a = np.random.rand(1000, 1000); %timeit a.sum(); %timeit sum(i for x in a for i in x) — Numpy написан на C . У меня разница в ~215 раз 😉
28 ноя 2019 в 12:13
@pepsicoca1 а как включить этот режим?
28 ноя 2019 в 18:08
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
В чем проблема
Python в его стандартной реализации CPython при запуске и работе переживает компиляцию и интерпретацию.
Компиляция
При запуске Python кода происходит обработка кода интерпретатором, после чего преобразуется в байт-код. Такой код не обрабатывается процессором напрямую, но меньше весит. Он сохраняется в pyc или оперативной памяти. После чего начинается следующий этап.
Интерпретация
Получившийся байт код поступает на выполнение в виртуальную машину CPython. А уже данный инструмент в свою очередь взаимодействует с процессором. То есть, байт код переходит в нативный, который может быть напрямую обработан процессором.
Из-за такой системы обработки затрудняются оптимизации кода, код на ходу интерпретируется и потому работает медленнее.
Решение
JIT компиляция
JIT компиляция — вид компиляции, при которой часто выполняемые куски кода компилируются в машинный код и кэшируются. Можно не внося изменения в код, ускорить его работу такими оптимизациями в интерпретаторе.
PyPy
У питона есть множество реализаций и даже семейств реализаций. Во многих из них используется другой механизм обработки кода. Одна реализаций Python — PyPy. Эта реализация поддерживает JIT компиляцию. Я не очень разбираюсь в тонкостях, но знаю, что данная реализация универсальна, поскольку работает не с конкретным языком программирования, а байт кодом. То есть автоматически добавляет jit к любому компилятору.
PyPy сочетает компилятор семейства RPython с статической типизацией и JIT.
Если нужно попроще
Существует библиотека Numba. С помощью нее можно компилировать в машинный код отдельные фрагменты кода и ускорить многократные повторения. Подробное описание и документацию можете загуглить.
«Дергание» C из Python
Библитека numpy, написанная на C и C++ может многократно ускорить работу с массивами. Нужно лишь конвертировать соответствующий массив в numpy.array. Подробную документацию также можете загуглить.
Итог
Ускорить питон можно как использованием других реализаций (PyPy и другие), так и с помощью библиотек (numba, numpy), что вам и требовалось.
2. sort() vs sorted()
Если нам просто нужен отсортированный список, при этом неважно, что будет с оригиналом, sort() будет работать немного быстрее, чем sorted() . Это справедливо для базовой сортировки:
# 1. Дефолтная сортировка с использованием sorted() %%time sorted(a_long_list) # Вывод в консоли: # CPU times: user 12 ms, sys: 2.51 ms, total: 14.5 ms # Wall time: 14.2 ms # 2. Дефолтная сортировка с использованием sort() %%time a_long_list.sort() # Вывод в консоли: # CPU times: user 8.52 ms, sys: 82 μs, total: 8.6 ms # Wall time: 10 ms
Справедливо и для сортировки с использованием ключа – параметра key , который определяет сортировочную функцию:
# 1. Сортировка с ключом с использованием sorted() %%time str_list1 = "Although both functions can sort list, there are small differences".split() result = sorted(str_list1, key=str.lower) print(result) # Вывод в консоли: # ['Although', 'are', 'both', 'can', 'differences', 'functions', 'list,', 'small', 'sort', 'there'] # CPU times: user 29 μs, sys: 0 ns, total: 29 μs # Wall time: 32.9 μs # 2. Сортировка с ключом с использованием sort() %%time str_list2 = "Although both functions can sort list, there are small differences".split() str_list2.sort(key=str.lower) print(str_list2) # Вывод в консоли: # ['Although', 'are', 'both', 'can', 'differences', 'functions', 'list,', 'small', 'sort', 'there'] # CPU times: user 26 μs, sys: 0 ns, total: 26 μs # Wall time: 29.8 μs # 3. Сортировка с ключом (лямбда) с использованием sorted() %%time str_list1 = "Although both functions can sort list, there are small differences".split() result = sorted(str_list1, key=lambda str: len(str)) print(result) # Вывод в консоли: # ['can', 'are', 'both', 'sort', 'list,', 'there', 'small', 'Although', 'functions', 'differences'] # CPU times: user 61 μs, sys: 3 μs, total: 64 μs # Wall time: 59.8 μs # 4. Сортировка с ключом (лямбда) с использованием sort() %%time str_list2 = "Although both functions can sort list, there are small differences".split() str_list2.sort(key=lambda str: len(str)) print(str_list2) # Вывод в консоли: # ['can', 'are', 'both', 'sort', 'list,', 'there', 'small', 'Although', 'functions', 'differences'] # CPU times: user 36 μs, sys: 0 ns, total: 36 μs # Wall time: 38.9 μs
Так происходит потому, что метод sort() изменяет список прямо на месте, в то время как sorted() создает новый отсортированный список, сохраняя исходный нетронутым. Другими словами, порядок значений внутри a_long_list фактически уже изменился.
Однако функция sorted() более универсальна. Она может работать с любой итерируемой структурой. Поэтому, если нужно отсортировать, например, словарь (по ключам или по значениям), придется использовать sorted() :
a_dict = # 1. Дефолтная сортировка по ключам %%time result = sorted(a_dict) print(result) # Вывод в консоли: # ['A', 'B', 'C', 'D', 'E'] # CPU times: user 4 μs, sys: 0 ns, total: 4 μs # Wall time: 6.91 μs # 2. Cортировка по значениям, результат в виде списка кортежей %%time result = sorted(a_dict.items(), key=lambda item: item[1]) print(result) # Вывод в консоли: # [('A', 1), ('C', 2), ('B', 3), ('D', 4), ('E', 5)] # CPU times: user 7 μs, sys: 0 ns, total: 7 μs # Wall time: 8.82 μs # 3. Сортировка по значениям, результат в виде словаря %%time result = print(result) # Вывод в консоли: # # CPU times: user 8 μs, sys: 0 ns, total: 8 μs # Wall time: 11.2 μs
3. Литералы вместо функций
Когда нужен пустой словарь или список, вместо dict() или list() , можно напрямую вызвать <> и [] (для пустого множества все еще нужна функция set() ). Этот прием не обязательно ускорит ваш код, но сделает его более «pythonic».
# 1. Создание пустого словаря с помощью dict() %%time sorted_dict1 = dict() for key, value in sorted(a_dict.items(), key=lambda item:item[1]): sorted_dict1[key] = value # Вывод в консоли: # CPU times: user 10 μs, sys: 0 ns, total: 10 μs # Wall time: 12.2 μs # 2. Создание пустого словаря с помощью литерала словаря %%time sorted_dict2 = <> for key, value in sorted(a_dict.items(), key=lambda item:item[1]): sorted_dict2[key] = value # Вывод в консоли: # CPU times: user 9 μs, sys: 0 ns, total: 9 μs # Wall time: 11 μs # 3. Создание пустого списка с помощью list() %%time list() # Вывод в консоли: # CPU times: user 3 μs, sys: 0 ns, total: 3 μs # Wall time: 3.81 μs # 4. Создание пустого списка с помощью литерала списка %%time [] # Вывод в консоли: # CPU times: user 2 μs, sys: 0 ns, total: 2 μs # Wall time: 3.1 μs
4. Генераторы списков
Обычно, когда требуется создать новый список из старого на основе определенных условий, мы используем цикл for – итерируем все значения и сохраняем нужные в новом списке.
Например, отберём все чётные числа из списка another_long_list :
even_num = [] for number in another_long_list: if number % 2 == 0: even_num.append(number)
Но есть более лаконичный и элегантный способ сделать то же самое. Код цикла for можно сократить до одной-единственной строки с помощью генератора списка, выиграв при этом в скорости почти в два раза:
import random random.seed(666) another_long_list = [random.randint(0,500) for i in range(1000000)] # 1. Создание нового списка с помощью цикла for %%time even_num = [] for number in another_long_list: if number % 2 == 0: even_num.append(number) # Вывод в консоли: # CPU times: user 113 ms, sys: 3.55 ms, total: 117 ms # Wall time: 117 ms # 2. Создание нового списка с помощью генератора списка %%time even_num = [number for number in another_long_list if number % 2 == 0] # Вывод в консоли: # CPU times: user 56.6 ms, sys: 3.73 ms, total: 60.3 ms # Wall time: 64.8 ms
Сочетая это правило с Правилом #3 (использование литералов), мы легко можем превратить список в словарь или множество, просто изменив скобки:
a_dict = sorted_dict3 = print(sorted_dict3) # Вывод в консоли: #
Разберёмся в коде:
- Выражение sorted(a_dict.items(), key=lambda item: item[1]) возвращает список кортежей [(‘A’, 1), (‘C’, 2), (‘B’, 3), (‘D’, 4), (‘E’, 5)] .
- Далее мы распаковываем кортежи и присваиваем первый элемент каждого кортежа в переменную key , а второй – в переменную value .
- Наконец, сохраняем каждую пару key — value в словаре.
5. enumerate() для значения и индекса
Иногда при переборе списка нужны и значения, и их индексы. Чтобы вдвое ускорить код используйте enumerate() для превращения списка в пары индекс-значение:
import random random.seed(666) a_short_list = [random.randint(0,500) for i in range(5)] # 1. Получение индексов с помощью использования длины списка %%time for i in range(len(a_short_list)): print(f'number is ') # Вывод в консоли: # number 0 is 233 # number 1 is 462 # number 2 is 193 # number 3 is 222 # number 4 is 145 # CPU times: user 189 μs, sys: 123 μs, total: 312 μs # Wall time: 214 μs # 2. Получение индексов с помощью enumerate() for i, number in enumerate(a_short_list): print(f'number is ') # Вывод в консоли: # number 0 is 233 # number 1 is 462 # number 2 is 193 # number 3 is 222 # number 4 is 145 # CPU times: user 72 μs, sys: 15 μs, total: 87 μs # Wall time: 90.1 μs
6. zip() для перебора нескольких списков
В некоторых случаях приходится перебирать более одного списка. Для ускорения операции рекомендуется использовать функцию zip() , которая преобразует их в общий итератор кортежей:
list1 = ['a', 'b', 'c', 'd', 'e'] list2 = ['1', '2', '3', '4', '5'] pairs_list = [pair for pair in zip(list1, list2)] print(pairs_list) # Вывод в консоли: [('a', '1'), ('b', '2'), ('c', '3'), ('d', '4'), ('e', '5')]
Обратите внимание, списки должны быть одинаковой длины, так как функция zip() останавливается, когда заканчивается более короткий список.
И наоборот, чтобы получить доступ к элементам каждого кортежа, мы можем распаковать список кортежей, добавив звездочку ( * ) и используя множественное присваивание:
# 1. Распаковка списка кортежей с помощью zip() %%time letters1, numbers1 = zip(*pairs_list) print(letters1, numbers1) # Вывод в консоли: ('a', 'b', 'c', 'd', 'e') ('1', '2', '3', '4', '5') # CPU times: user 5 μs, sys: 1e+03 ns, total: 6 μs # Wall time: 6.91 μs # 2. Распаковка списка кортежей простым перебором letters2 = [pair[0] for pair in pairs_list] numbers2 = [pair[1] for pair in pairs_list] print(letters2, numbers2) # Вывод в консоли: ['a', 'b', 'c', 'd', 'e'] ['1', '2', '3', '4', '5'] # CPU times: user 5 μs, sys: 1e+03 ns, total: 6 μs # Wall time: 7.87 μs
7. Комбинация set() и in
Если нужно проверить, содержит ли список некоторое значение, можно написать такую неуклюжую функцию:
import random random.seed(666) another_long_list = [random.randint(0,500) for i in range(1000000)] def check_membership(n): for element in another_long_list: if element == n: return True return False
Однако есть более характерный для Python способ сделать это – использовать оператор in :
# 1. Проверка наличия значения в списке перебором элементов %%time check_membership(900) # Вывод в консоль # CPU times: user 29.7 ms, sys: 847 μs, total: 30.5 ms # Wall time: 30.2 ms # 2. Проверка наличия значения в списке с помощью in 900 in another_long_list # Вывод в консоль # CPU times: user 10.2 ms, sys: 79 μs, total: 10.3 ms # Wall time: 10.3 ms
Повысить эффективность можно предварительным удалением из списка дубликатов с помощью set . Таким образом, мы сократим количество элементов для проверки. Кроме того, оператор in очень быстро работает с множествами.
# Убираем дубликаты check_list = set(another_long_list) # Вывод в консоль # CPU times: user 19.8 ms, sys: 204 μs, total: 20 ms # Wall time: 20 ms # Проверяем наличие значения в списке 900 in check_list # Вывод в консоль # CPU times: user 2 μs, sys: 0 ns, total: 2 μs # Wall time: 5.25 μs
Преобразование списка в множество заняло 20 мс. Но это одноразовые затраты. Зато сама проверка заняла 5 мкс – то есть в 2 тыс. раз меньше, что становится важным при частых обращениях.
8. Проверка на True
Практически в любой программе необходимо проверять, являются ли переменные/списки/словари/. пустыми. На этих проверках тоже можно немножко сэкономить.
Не следует явно указывать == True или is True в условии if , достаточно указать имя проверяемой переменной. Это экономит ресурсы, которые использует «магическая» функция __eq__ для сравнения значений.
string_returned_from_function = 'Hello World' # 1. Явная проверка на равенство %%time if string_returned_from_function == True: pass # Вывод в консоль # CPU times: user 3 μs, sys: 0 ns, total: 3 μs # Wall time: 5.01 μs # 2. Явная проверка с использованием оператора is %%time if string_returned_from_function is True: pass # Вывод в консоль # CPU times: user 2 μs, sys: 1 ns, total: 3 μs # Wall time: 4.05 μs # 3. Неявное равенство %%time if string_returned_from_function: pass # Вывод в консоль # CPU times: user 3 μs, sys: 0 ns, total: 3 μs # Wall time: 4.05 μs
Аналогично можно проверять обратное условие, добавив оператор not :
if not string_returned_from_function: pass
9. Подсчет уникальных значений с Counter()
Если нам необходимо подсчитать количество уникальных значений в списке, можно, например, создать словарь, в котором ключи – это значения списка, а значения – счетчик встречаемости.
%%time num_counts = <> for num in a_long_list: if num in num_counts: num_counts[num] += 1 else: num_counts[num] = 1 # Вывод в консоль # CPU times: user 448 ms, sys: 1.77 ms, total: 450 ms # Wall time: 450 ms
Однако более эффективный способ для решения этой задачи – использование Counter() из модуля collections. Весь код при этом уместится в одной строчке:
%%time num_counts2 = Counter(a_long_list) # Вывод в консоль # CPU times: user 40.7 ms, sys: 329 μs, total: 41 ms # Wall time: 41.2 ms
Этот фрагмент будет работать примерно в 10 раз быстрее, чем предыдущий.
У Counter также есть удобный метод most_common , позволяющий получить самые часто встречающиеся значения:
for number, count in num_counts2.most_common(10): print(number, count) # Вывод в консоль 29 19831 47 19811 7 19800 36 19794 14 19761 39 19748 32 19747 16 19737 34 19729 33 19729
Одним словом, collections – это замечательный модуль, который должен быть в базовом наборе инструментов любого Python-разработчика. Не поленитесь прочитать наше руководство по применению модуля.
10. Цикл for внутри функции
Представьте, что вы создаете функцию, которую нужно повторить некоторое количество раз. Очевидный способ решения этой задачи – помещение функции внутрь цикла for .
def compute_cubic1(number): return number**3 %%time new_list_cubic1 = [compute_cubic1(number) for number in a_long_list] # Вывод в консоль # CPU times: user 335 ms, sys: 14.3 ms, total: 349 ms # Wall time: 354 ms
Однако правильнее будет перевернуть конструкцию – и поместить цикл внутрь функции.
def compute_cubic2(): return [number**3 for number in a_long_list] %%time new_list_cubic2 = compute_cubic2() # Вывод в консоль # CPU times: user 261 ms, sys: 15.7 ms, total: 277 ms # Wall time: 277 ms
В данном примере для миллиона итераций (длина a_long_list ) мы сэкономили около 22% времени.
Будем рады, если вы поделитесь в комментариях своими подходами к ускорению кода в Python. Вот ещё несколько статей, которые могут вас заинтересовать:
- Не изобретать велосипед, или Обзор модуля collections в Python
- Назад в будущее: практическое руководство по путешествию во времени с Python
- Как написать код, который полюбят все
