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

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

Ошибка 1. Писать циклы так, будто сейчас 1999 год

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

Пример — сложение нескольких чисел

Представьте, что вам нужно просуммировать квадраты огромного списка чисел.

Вот способ с циклом:

numbers = [1, 2, 3, 4, 5, ... , 10000]  # Большой список
total = 0
for number in numbers:
    squared = number * number
    total += squared

Кажется безобидным, но «под капотом» Python выполняет массу отдельных вычислений для каждого элемента.

Фикс — NumPy спешит на помощь!

Именно здесь NumPy пролетает как супергерой. Всё дело в векторизации — выполнении операций над целыми массивами одновременно.

Перепишем этот пример:

import numpy as np

numbers = np.array([1, 2, 3, 4, 5, ... , 10000])  
squared = numbers * numbers  # Векторизованный квадрат числа!
total = squared.sum()

Вместо того чтобы обрабатывать элемент за элементом, NumPy выполняет все вычисления одним махом.

Бонус — понятные списковые включения

Списковые включения похоже на невидимую золотую середину:

total = sum(number * number for number in numbers)

Они зачастую быстрее обычных циклов, но могут не сравняться с NumPy по эффективности интенсивных числовых расчётов.

Ошибка 2. Не тот инструмент для работы

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

Пример — где мой номер телефона?

Допустим, у вас есть такой список контактов:

contacts = [
    {"name": "Alice", "phone": "123-4567"},
    {"name": "Bob", "phone": "789-0123"},
    # больше контактов
]

Чтобы найти номер Боба, нужно просмотреть список, потенциально проверив каждый контакт.

Фикс — структуры данных с особыми способностями

  • Словари — ваш помощник в быстром поиске Если вы ищете по ключу (например, name), словари — ваш спаситель.
contacts_dict = {
    "Alice": "123-4567",
    "Bob": "789-0123",
    # ... больше контактов
}
bobs_number = contacts_dict["Bob"]  # Мгновенный доступ!
  • Множества — соблюдение уникальности. Хотите отслеживать уникальных посетителей веб-сайта? Множества автоматически удаляют дубликаты.
unique_visitors = set()
unique_visitors.add("192.168.1.100")
unique_visitors.add("124.58.23.5")
unique_visitors.add("192.168.1.100")  # Дубликатов нет

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

Ошибка 3. Оптимизация вслепую

Вам знакомо ощущение, когда вы убеждены, что ваш код работает медленно, но не знаете, в чём причина. Это всё равно, что пытаться починить течь в потолке без фонарика. Раздражает! Вот тут-то и приходят на помощь профайлеры.

Пример — неожиданный виновник

Предположим, у вас есть сложная функция вычисления чисел Фибоначчи. Вы вложили свою душу в переработку математики, но она остаётся медленной. Оказывается, узким местом может быть что-то скрытое, например, то, как вы записываете результаты в файл.

Фикс — cProfile

Встроенный модуль Python cProfile — ваш детектор производительности. Вот как его использовать:

import cProfile

def my_function():
    # Ваш код для профилирования

cProfile.run('my_function()')

Он генерирует кучу статистики. Ключевые вещи, на которые стоит обратить внимание:

  • ncalls — сколько раз вызывалась функция;
  • tottime — общее время, проведённое в функции;
  • cumtime — аналогично tottime, но включает время, потраченное на все функции, вызываемые внутри него.

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

Ошибка 4. Ловушка «сделай сам»

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

Пример — сортировка

Нужно отсортировать список чисел? Вы можете написать реализацию пузырьковой сортировки… или использовать sorted() в Python:

my_list = [5, 3, 1, 4, 2]

# Долгий способ (возможно, довольно медленный)
def my_bubble_sort(list):
   # ... здесь ваш код сортировки

# Способ в Python
sorted_list = sorted(my_list)

Скорее всего, ваш алгоритм сортировки даже близко не подойдёт к эффективности встроенного.

Фикс — стандартная библиотека

Стандартная библиотека Python — лучший друг разработчика. Познакомьтесь с этими электрическими машинами:

  • itertools ускоряет работу с помощью итераторов (для повышения эффективности подумайте о расширенных циклах).
  • heapq управляет кучами (приоритетные очереди?)
  • bisect молниеносно сохраняет порядок в отсортированных списках.

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

Ошибка 5. Слишком много болтовни с жёстким диском

Считайте память компьютера (ОЗУ) своим сверхбыстрым рабочим местом, а жёсткий диск — хранилищем данных по всему городу. Каждый раз, когда вы получаете доступ к файлу или изменяете его, это похоже на отправку посыльного туда и обратно. Слишком много поездок, и код начинает ощущать ожидание.

Пример — замедление из-за построчного чтения

Допустим, вы обрабатываете файл большого лога:

with open("huge_log.txt", "r") as file:
    for line in file:
        # Каждая строка обрабатывается медленно

Каждое чтение line означает отдельное извлечение с жёсткого диска.

Фикс — работать с умом, а не с трудом

  • Прочитать всё сразу, если это подходит. Для файлов меньшего размера иногда быстрее всего загрузить всё в оперативную память:
with open("huge_log.txt", "r") as file:
    contents = file.read()
    # Обработка содержимого в оперативной памяти
  • Буферизация. Когда вам нужен детальный контроль, положение спасает буферизация:
with open("huge_log.txt", "r") as file:
    while True:
        chunk = file.read(4096)  # Чтение блоками
        if not chunk:
            break
        # Обработка блока

Думайте блоками, а не байтами. Сведение поездок на «склад» к минимуму имеет огромное значение.

Заключение — ускорьте ваш Python

Вспомним виновников снижения скорости:

  1. Перегрузка цикла. Используйте векторизацию при помощи NumPy.
  2. Неподходящие инструменты. Словари — для поиска, множества — для уникальности… выбирайте с умом!
  3. Слепая оптимизация. Профиль с помощью cProfile для выявления истинных узких мест.
  4. Мания «сделай сам». Встроенные модули Python ваши друзья — используйте их!
  5. Слишком много болтовни с диском. Читайте стратегически, с умом используйте буфер.

Помните, что производительность — это решение не на один раз. Думайте о ней как о подготовке к марафону: профилируйте свой код, оптимизируйте горячие точки, а затем повторяйте процесс. Вскоре у вас появятся скрипты Python, которые будут работать с грацией гепарда.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Builescu Daniel: 5 Python Coding Errors That Are Killing Your Speed (And How to Fix Them Today)

Предыдущая статьяДилемма побочного проекта: почему разработчики упускают возможность монетизации
Следующая статьяКомпонентный подход: реализация экранов с помощью библиотеки Decompose. Часть 2