10 приёмов профессионала для ускорения кода на Python. bisect.. bisect. itertools.. bisect. itertools. python.. bisect. itertools. python. sets.. bisect. itertools. python. sets. оптимизация.. bisect. itertools. python. sets. оптимизация. оптимизация кода.. bisect. itertools. python. sets. оптимизация. оптимизация кода. память.. bisect. itertools. python. sets. оптимизация. оптимизация кода. память. Программирование.. bisect. itertools. python. sets. оптимизация. оптимизация кода. память. Программирование. производительность.. bisect. itertools. python. sets. оптимизация. оптимизация кода. память. Программирование. производительность. скорость.. bisect. itertools. python. sets. оптимизация. оптимизация кода. память. Программирование. производительность. скорость. циклы.

Команда Python for Devs подготовила перевод статьи о том, как делать код на Python быстрее без переписывания проектов с нуля. В статье 10 практичных приёмов — от sets и bisect до локальных функций и предвыделения памяти — которые дают реальный прирост скорости в типовых сценариях.


В быстро меняющемся мире разработки Python прочно занял место одного из ведущих языков благодаря своей простоте, читаемости и универсальности. Он лежит в основе огромного числа приложений — от веб-разработки до искусственного интеллекта и data engineering. Однако под его элегантным синтаксисом скрывается сложность: узкие места производительности, способные превратить вполне рабочие скрипты в откровенно медленные процессы.

Будь то обработка больших датасетов, создание систем реального времени или повышение вычислительной эффективности — оптимизация питонячего кода под скорость нередко становится ключевым фактором для получения выдающихся результатов.

В этом гайде собраны 10 тщательно проверенных стратегий ускорения. Используя встроенные возможности Python, эффективные структуры данных и низкоуровневые техники оптимизации, он предлагает практичные методы повышения скорости выполнения без ущерба для присущей языку ясности и выразительности. Подкреплённые реальными бенчмарками и показателями, а также наглядными примерами кода, эти приёмы демонстрируют, как небольшие улучшения способны дать значительный прирост производительности — помогая разработчикам пройти путь от уверенного владения Python до уровня настоящих мастеров высокопроизводительного программирования.

Поехали — прокачаем твоё питонячье мастерство!

Приём 1: Используйте sets для проверки принадлежности

Когда нужно узнать, содержится ли элемент в наборе данных, использование списка может быть неэффективным — особенно по мере его роста. Проверка принадлежности в списке (x in some_list) требует последовательного обхода всех элементов, что даёт линейную сложность по времени (O(n)):

big_list = list(range(1000000))
big_set = set(big_list)
start = time.time()
print(999999 in big_list)
print(f"List lookup: {time.time() - start:.6f}s")
start = time.time()
print(999999 in big_set)
print(f"Set lookup: {time.time() - start:.6f}s")

Измеренное время:

  • List lookup: ~0.015000s

  • Set lookup: ~0.000020s

В отличие от списков, sets в Python реализованы как хеш-таблицы, что обеспечивает в среднем постоянное время поиска (O(1)). Поэтому проверка того, существует ли значение в set, значительно быстрее, особенно при работе с большими объемами данных.

Для задач вроде удаления дублей, валидации входных данных или сопоставления элементов между коллекциями sets куда эффективнее списков. Они ускоряют не только проверку принадлежности, но и такие операции, как объединение, пересечение и разность — делая их и быстрее, и выразительнее.

Заменив списки на sets в местах, где важна производительность проверки принадлежности, можно получить ощутимый прирост скорости, практически не меняя логику программы.

Приём 2: Избегайте лишних копий

Копирование крупных объектов — таких как списки, словари или массивы — может быть дорогим как по времени, так и по памяти. Каждая копия создаёт новый объект в памяти, что приводит к заметным накладным расходам, особенно при работе с большими наборами данных или внутри плотных циклов.

Когда это возможно, изменяйте объекты на месте вместо создания дубликатов. Это сокращает потребление памяти и повышает производительность, поскольку не требует выделения и заполнения новых структур. Многие встроенные структуры данных Python имеют методы для изменения на месте (sort, append, update), что позволяет обходиться без копий.

numbers = list(range(1000000))
def modify_list(lst):
    lst[0] = 999
    return lst
start = time.time()
result = modify_list(numbers)
print(f"In-place: {time.time() - start:.4f}s")
def copy_list(lst):
    new_lst = lst.copy()
    new_lst[0] = 999
    return new_lst
start = time.time()
result = copy_list(numbers)
print(f"Copy: {time.time() - start:.4f}s")

Измеренное время:

  • In-place: ~0.0001s

  • Copy: ~0.0100s

В производительно критичном коде важно осознавать, когда и как создаются копии объектов — это может заметно повлиять на скорость. Используя ссылки и операции “на месте”, вы можете писать более быстрый и экономный по памяти код, особенно при работе с крупными или сложными структурами данных.

Приём 3: Используйте slots для экономии памяти

По умолчанию экземпляры классов в Python хранят свои атрибуты в динамическом словаре (__dict__). Это даёт гибкость, но сопровождается лишними расходами памяти и чуть более медленным доступом к атрибутам.

slots позволяет явно задать фиксированный набор атрибутов для класса. В этом случае dict не создаётся, что снижает потребление памяти — особенно заметно при создании большого количества экземпляров. Кроме того, за счёт упрощённой внутренней структуры доступ к атрибутам становится немного быстрее.

Хотя slots ограничивает возможность динамического добавления атрибутов, в условиях ограниченной памяти или требований к производительности эта жертва себя оправдывает. Для “лёгких” классов или контейнеров данных использование slots — простой способ сделать код эффективнее.

class Point:
    __slots__ = ('x', 'y')
    def __init__(self, x, y):
        self.x = x
        self.y = y
start = time.time()
points = [Point(i, i+1) for i in range(1000000)]
print(f"With slots: {time.time() - start:.4f}s")

Измеренное время:

  • With slots: ~0.1200s

  • Without slots: ~0.1500s

With __slots__: ~0.1200s  
Without __slots__: ~0.1500s

Приём 4: Используйте функции модуля math вместо операторов

Для численных вычислений модуль math предлагает функции, реализованные на C, — они работают быстрее и точнее, чем аналогичные операции, написанные на чистом Python.

Например, math.sqrt() обычно быстрее и точнее, чем возведение числа в степень 0.5 через оператор **. Точно так же функции math.sin(), math.exp(), math.log() и другие сильно оптимизированы по скорости и надёжности.

Эти преимущества особенно заметны в плотных циклах или при вычислениях в больших масштабах. Используя модуль math для тяжёлых численных операций, вы получаете и более быструю работу, и более стабильные результаты — что делает его предпочтительным выбором для научных вычислений, симуляций и любого кода, насыщенного математикой.

10 приёмов профессионала для ускорения кода на Python - 1

Используйте функции math вместо операторов. PyCharm ещё больше упрощает использование модуля math благодаря умному автодополнению. Стоит ввести math., и вы получите выпадающий список доступных математических функций и констант — таких как sqrt(), sin(), cos(), log(), pi и множество других — с встроенной документацией.

Это ускоряет разработку: не нужно помнить точные названия функций, а также появляется стимул использовать оптимизированные встроенные реализации вместо самописных или основанных на операторах. Благодаря таким подсказкам разработчики быстрее осваивают возможности модуля и пишут более чистый и быстрый численный код.

import math
numbers = list(range(10000000))
start = time.time()
roots = [math.sqrt(n) for n in numbers]
print(f"Math sqrt: {time.time() - start:.4f}s")
start = time.time()
roots = [n ** 0.5 for n in numbers]
print(f"Operator: {time.time() - start:.4f}s")

Измеренное время:

  • math.sqrt: ~0.2000s

  • Operator: ~0.2500s

Приём 5: Предварительно выделяйте память, если известен размер

Когда вы динамически формируете списки или массивы, Python по мере их роста автоматически перераспределяет память. Это удобно, но такое перераспределение требует выделения памяти и копирования данных — а значит, добавляет накладные расходы, особенно в больших или производительно критичных циклах.

Если конечный размер структуры данных известен заранее, предварительное выделение памяти может заметно улучшить производительность. Инициализировав список или массив фиксированной длины, вы избегаете повторных перераспределений и позволяете Python (или таким библиотекам, как NumPy) эффективнее управлять памятью.

Этот приём особенно полезен в численных расчётах, симуляциях и масштабной обработке данных, где даже небольшие оптимизации суммируются. Предварительное выделение снижает фрагментацию, улучшает локальность данных в кэше и делает выполнение более предсказуемым.

start = time.time()
result = [0] * 1000000
for i in range(1000000):
    result[i] = i
print(f"Pre-allocated: {time.time() - start:.4f}s")
start = time.time()
result = []
for i in range(1000000):
    result.append(i)
print(f"Dynamic: {time.time() - start:.4f}s")

Измеренное время:

  • Pre-allocated: ~0.0300s

  • Dynamic: ~0.0400s

Приём 6: Избегайте обработки исключений в горячих циклах

Механизм исключений в Python мощный и чистый, он отлично подходит для обработки неожиданных ситуаций. Но он не предназначен для использования в высокочастотных участках кода. Генерация и перехват исключений требует разворачивания стека и переключения контекста, что является довольно дорогой операцией.

В горячих циклах — участках кода, которые выполняются многократно или обрабатывают большие объёмы данных — использование исключений в качестве управляющей логики может серьёзно замедлить выполнение. Вместо этого лучше использовать условные проверки (if, in, is и т.п.), чтобы предотвратить ошибку до её возникновения. Такой подход гораздо быстрее и работает предсказуемее.

Оставляя исключения для действительно исключительных случаев, а не для нормального потока управления, вы пишете более чистый и более быстрый код — особенно в плотных циклах и системах реального времени, где производительность критична.

numbers = list(range(10000000))
start = time.time()
total = 0
for i in numbers:
    if i % 2 != 0:
        total += i // 2
    else:
        total += i
print(f"Conditional: {time.time() - start:.4f}s")
start = time.time()
total = 0
for i in numbers:
    try:
        total += i / (i % 2)
    except ZeroDivisionError:
        total += i
print(f"Exception: {time.time() - start:.4f}s")

Измеренное время:

  • Conditional: ~0.3000s

  • Exception: ~0.6000s

Приём 7: Используйте локальные функции для повторяющейся логики

Когда какой-то фрагмент логики многократно используется внутри одной функции, имеет смысл оформить его как локальную (вложенную) функцию — или, иначе, замыкание. Локальные функции выигрывают в производительности за счёт более быстрого поиска имён: Python обращается к локальным областям видимости быстрее, чем к глобальной.

Помимо прироста скорости, локальные функции помогают инкапсулировать логику, делают код чище и более модульным. Они могут захватывать переменные из внешней области видимости, что позволяет писать более гибкую и переиспользуемую внутреннюю логику без передачи лишних аргументов.

Этот приём особенно полезен в функциях, где одна и та же операция выполняется много раз — в циклах, при трансформации данных или в рекурсивных процессах. Держа часто используемую логику рядом (локально), вы уменьшаете и накладные расходы при выполнении, и когнитивную нагрузку при чтении кода.

def outer():
    def add_pair(a, b):
        return a + b
    result = 0
    for i in range(10000000):
        result = add_pair(result, i)
    return result
start = time.time()
result = outer()
print(f"Local function: {time.time() - start:.4f}s")
def add_pair(a, b):
    return a + b
start = time.time()
result = 0
for i in range(10000000):
    result = add_pair(result, i)
print(f"Global function: {time.time() - start:.4f}s")

Измеренное время:

  • Local function: ~0.4000s

  • Global function: ~0.4500s

Приём 8: Используйте itertools для комбинаторных операций

Когда нужно работать с перестановками, комбинациями, декартовым произведением и другими задачами, основанными на итераторах, модуль itertools в Python предоставляет набор очень эффективных инструментов, оптимизированных на C специально под такие случаи.

Функции вроде product(), permutations(), combinations() и combinations_with_replacement() генерируют элементы лениво — то есть не хранят весь результат в памяти. Это позволяет работать с большими или даже бесконечными последовательностями без тех накладных расходов по памяти и скорости, которые возникают у ручных реализаций.

Помимо высокой скорости, функции itertools хорошо сочетаются друг с другом и экономно расходуют память, что делает их отличным выбором для сложной обработки данных, разработки алгоритмов и решения задач, встречающихся в симуляциях, поисковых алгоритмах или спортивном программировании. Когда важны производительность и масштабируемость, itertools — одно из первых мест, куда стоит обратиться.

from itertools import product
items = [1, 2, 3] * 10
start = time.time()
result = list(product(items, repeat=2))
print(f"Itertools: {time.time() - start:.4f}s")
start = time.time()
result = []
for x in items:
    for y in items:
        result.append((x, y))
print(f"Loops: {time.time() - start:.4f}s")

Измеренное время:

  • itertools: ~0.0005s

  • Loops: ~0.0020s

Приём 9: Используйте bisect для работы с отсортированными списками

При работе с отсортированными списками линейный поиск или ручная логика вставки могут быть неэффективны — особенно по мере роста списка. Модуль bisect в Python предоставляет быстрые и удобные инструменты для поддержания отсортированного порядка на базе двоичного поиска.

С помощью функций bisect_left(), bisect_right() и insort() можно выполнять вставки и поиск за O(log n), в отличие от O(n) при простом последовательном обходе. Это особенно полезно в задачах вроде поддержки таблиц лидеров, временных линий событий или реализации эффективных диапазонных запросов.

Используя bisect, вы избегаете повторной сортировки после каждого изменения и получаете заметный прирост производительности при работе с динамическими отсортированными данными. Это лёгкий и мощный инструмент, который приносит алгоритмическую эффективность в повседневные операции со списками.

import bisect
numbers = sorted(list(range(0, 1000000, 2)))
start = time.time()
bisect.insort(numbers, 75432)
print(f"Bisect: {time.time() - start:.4f}s")
start = time.time()
for i, num in enumerate(numbers):
    if num > 75432:
        numbers.insert(i, 75432)
        break
print(f"Loop: {time.time() - start:.4f}s")

Измеренное время:

  • bisect: ~0.0001s

  • Loop: ~0.0100s

Приём 10: Избегайте повторных вызовов функции в циклах

Если одна и та же функция вызывается много раз внутри цикла — особенно если она дорогая по вычислениям или каждый раз даёт один и тот же результат, — это создаёт лишние накладные расходы. Даже относительно быстрые функции при миллионах вызовов могут заметно замедлить код.

Чтобы оптимизировать, вычислите значение один раз до входа в цикл и сохраните его в локальную переменную. Это уменьшает накладные расходы на вызовы функции и ускоряет выполнение, особенно в критичных по производительности участках.

Приём очень простой, но эффективный. Он не только ускоряет выполнение, но и делает код понятнее — сразу видно, что значение внутри цикла постоянно. Кэширование результата функции — один из самых лёгких способов убрать лишние вычисления и сделать код эффективнее.

def expensive_operation():
    time.sleep(0.001)
    return 42
start = time.time()
cached_value = expensive_operation()
result = 0
for i in range(1000):
    result += cached_value
print(f"Cached: {time.time() - start:.4f}s")
start = time.time()
result = 0
for i in range(1000):
    result += expensive_operation()
print(f"Repeated: {time.time() - start:.4f}s")

Измеренное время:

  • Cached: ~0.0010s

  • Repeated: ~1.0000s

Русскоязычное сообщество про Python

10 приёмов профессионала для ускорения кода на Python - 2

Друзья! Эту статью подготовила команда Python for Devs — канала, где каждый день выходят самые свежие и полезные материалы о Python и его экосистеме. Подписывайтесь, чтобы ничего не пропустить!

Автор: python_leader

Источник

Rambler's Top100