20 простых советов по настройке производительности Python. Часть I

Python является гибким и мощным высокоуровневым языком программирования. Разрабатываете ли вы веб-приложение или решаете задачи машинного обучения, этого языка вам будет достаточно.

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

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

Не все наши советы одинаковы. Что-то вы можете уже знать, и тогда это покажется вам очевидным. Зато другие вещи, ранее вам незнакомые, могут очень пригодиться в дальнейшей работе. При этом одни практики могут сильно увеличить производительность, другие — в меньшей степени. Поэтому пройдитесь по нашему списку и добавьте эту страницу в ваши закладки.

производительность программ на Python, 20 простых советов по настройке производительности Python. Часть I

1. Используйте представления списков

Работая в Python, вы часто сталкиваетесь с циклами. И, вероятно, вы уже встречались с представлениями списков. Это лаконичный и быстрый способ для создания новых списков.

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

cube_numbers = []
    for n in range(0,10):
        if n % 2 == 1:
            cube_numbers.append(n**3)

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

cube_numbers = [n**3 for n in range(1,10) if n%2 == 1]

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

2. Не забывайте про встроенные функции

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

3. Используйте xrange() вместо range() (совет для работы с Python 2)

Для последовательного перебора при помощи циклов Python 2 использует функции range() и xrange(). Первая из этих функций сохраняет все числа из заданного диапазона непосредственно в память, в результате чего потребление памяти линейно возрастает. Вторая функция, xrange(), возвращает объект типа генератор. При итерации данного объекта числа из данного диапазона попадают в память последовательно, только по мере необходимости.

import sys
numbers = range(1, 1000000)
print(sys.getsizeof(numbers))

Данный код вернет значение 8000064, в то время как тот же код с функцией xrange() вернет 40. Если ваше приложение написано на Python 2, то замена range() на xrange() вызовет ощутимую экономию памяти. Правда, в Python 3 функционал xrange() полностью реализован в функции range(), а функция xrange() отсутствует вовсе.

4. Рассмотрите возможность написания собственного генератора

Предыдущий совет наводит нас на мысль об общем шаблоне оптимизации: при возможности надо использовать генераторы. Они позволяют возвращать значения итерируемой последовательности по одному, а не все разом. Как уже говорилось, в Python 2 генератором является функция xrange(), а в Python 3 — range().

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

Вот пример, который вы можете использовать при работе с веб-страницами:

import requests
import re

def get_pages(link):
    pages_to_visit = []
    pages_to_visit.append(link)
    pattern = re.compile('https?')
    while pages_to_visit:
        current_page = pages_to_visit.pop(0)
        page = requests.get(current_page)
        for url in re.findall('<a href="([^"]+)">', str(page.content)):
            if url[0] == '/':
                url = current_page + url[1:]
            if pattern.match(url):
                pages_to_visit.append(url)
        yield current_page
webpage = get_pages('http://www.example.com')
for result in webpage:
  print(result)

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

5. Используйте по возможности ключевое слово in

Для проверки вхождения элемента в список как правило быстрее использовать ключевое слово in.

for name in member_list:
    print('{} is a member'.format(name))

Не торопитесь загружать модули

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

Такой подход помогает отслеживать все имеющиеся в вашей программе зависимости. Но у такого подхода есть и недостаток: все загружается при старте программы.

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

производительность программ на Python, 20 простых советов по настройке производительности Python. Часть I

7. Используйте множества и их объединения

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

Допустим, вам нужно найти пересекающиеся значения в двух списках. Вы конечно можете это сделать, используя вложенные списки, например вот так:

a = [1,2,3,4,5]
b = [2,3,4,5,6]

overlaps = []
for x in a:
    for y in b:
        if x==y:
            overlaps.append(x)

print(overlaps)

В результате мы получим следующий список: [2, 3, 4, 5]. Количество сравнений здесь будет очень большое и оно будет расти очень быстро.

Другой подход может быть следующим:

a = [1,2,3,4,5]
b = [2,3,4,5,6]

overlaps = set(a) & set(b)

print(overlaps)

Результатом будет множество {2, 3, 4, 5}. Опираясь на встроенные функции, вы получаете выигрыш в скорости и экономию памяти.

8. Не забывайте использовать множественные присваивания

В Python есть элегантный способ присваивать значения многим переменным.

first_name, last_name, city = "Kevin", "Cunningham", "Brighton"

Это можно использовать, чтобы поменять переменные местами:

x, y = y, x

Это гораздо быстрей и чище, чем такой способ:

temp = x 
x = y
y = temp

9. Старайтесь не использовать глобальные переменные

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

производительность программ на Python, 20 простых советов по настройке производительности Python. Часть I

10. Для конкатенации строк используйте join()

В Python вы можете конкатенировать строки при помощи оператора "+". Однако, строки в Python являются неизменяемым объектом, и операция сложения ("+") влечет за собой создание нового объекта и копирование в него старого содержимого. Более эффективный подход связан с использованием массива, чтобы модифицировать отдельные символы и потом заново создать строку при помощи функции join().

new = "This" + "is" + "going" + "to" + "require" + "a" + "new" + "string" + "for" + "every" + "word"
print(new)

Данный код выведет следующую строку:

Thisisgoingtorequireanewstringforeveryword

С другой стороны, этот код

new = " ".join(["This", "will", "only", "create", "one", "string", "and", "we", "can", "add", "spaces."])
print(new)

выведет:

This will only create one string and we can add spaces.

Это чище, элегантней и быстрей.

Смотрите продолжение в следующей статье.