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

0
62
views

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

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

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

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

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 вы вероятно получили совет сразу, в начале программы импортировать все модули, которые вы в дальнейшем будете использовать. Возможно, вы их даже сортируете по алфавиту.

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

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

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 быстрей извлекает локальную переменную, нежели глобальную. Таким образом, избегайте глобальных переменных насколько это возможно.

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.

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

11. Вовремя обновляйте ваш Python

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

12. Для бесконечных циклов используйте while 1

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

13. Попробуйте другой способ

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

14. Ранний выход

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

if positive_case:
    if particular_example: 
        do_something
else:
    raise exception

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

if not positive_case:
    raise exception
if not particular_example:
    raise exception
do_something 

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

15. Изучите библиотеку itertools

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

Обязательно изучите ее документацию, чтобы максимально использовать все возможности данной библиотеки. Вот например, функция перестановок. Допустим, вы хотите сгенерировать все перестановки списка  [“Alice”, “Bob”, “Carol”].

import itertools
iter = itertools.permutations(["Alice", "Bob", "Carol"])
list(iter)

Эта функция вернет все возможные перестановки:

[('Alice', 'Bob', 'Carol'),
('Alice', 'Carol', 'Bob'),
('Bob', 'Alice', 'Carol'),
('Bob', 'Carol', 'Alice'),
('Carol', 'Alice', 'Bob'),
('Carol', 'Bob', 'Alice')]

Это действительно полезно и невероятно быстро!

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

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

Попробуйте сами вычислить сотое число в последовательности Фибоначчи. Если вы никогда раньше не сталкивались с этой последовательностью, то знайте, что каждое число в ней является результатом суммы двух предыдущих членов этой последовательности. Фибоначчи был итальянским математиком, который обнаружил, что эти числа всплывают в совершенно разных местах. От числа лепестков на цветке до ножек насекомых или веток на дереве — эти числа весьма распространены в природе. Первые пять чисел данной последовательности — 1, 1, 2, 3, 5.

Вот один из алгоритмов для вычисления членов последовательности Фибоначчи:

def fibonacci(n):
    if n == 0: # There is no 0'th number
        return 0
    elif n == 1: # We define the first number as 1
        return 1
    return fibonacci(n - 1) + fibonacci(n-2)

Когда я использовал этот алгоритм для вычисления 36 числа Фибоначчи, fibonacci(36), мой компьютер загудел, как будто он собрался взлететь! Вычисления длились пять секунд и результат равнялся (если вам любопытно!) 14,930,352.

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

import functools
@functools.lru_cache(maxsize=128)
def fibonacci(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    return fibonacci(n - 1) + fibonacci(n-2)

В Python декораторы, сами являющиеся функциями, принимают в качестве аргумента другую функцию и расширяют ее функциональность. Для обозначение декоратора используется символ «@«. В данном примере мы использовали в качестве декоратора функцию functools.lru_cache, которая находится в модуле functools. В качестве аргумента мы передали в нее максимальное число членов последовательности, которые должны храниться в кэше. Здесь возможно применить и другие декораторы, в том числе и написанные вами лично, но эта функция уже имеется в стандартной библиотеке и работает весьма быстро. Насколько быстро? Вычисления были произведены за 0.7 секунды и, разумеется, результат был тот же самый.

17. Используйте ключи для сортировки

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

import operator
my_list = [("Josh", "Grobin", "Singer"), ("Marco", "Polo", "General"), ("Ada", "Lovelace", "Scientist")]
my_list.sort(key=operator.itemgetter(0))
my_list

Этот код отсортирует список по первому элементу кортежей, входящих в список:

[('Ada', 'Lovelace', 'Scientist'),
('Josh', 'Grobin', 'Singer'),
('Marco', 'Polo', 'General')]

Можно так же легко отсортировать и по второму элементу:

my_list.sort(key=operator.itemgetter(1))
my_list

Этот код вернет следующий список:

[('Josh', 'Grobin', 'Singer'),
('Ada', 'Lovelace', 'Scientist'),
('Marco', 'Polo', 'General')]

Можно легко заметить, что он отсортирован по второму элементу кортежей.

В каждом случае список будет сортироваться в соответствии с индексом, который передается в качестве ключевого аргумента. Данный подход также применим к числам и строкам. И он легко читаем и быстр.

18. Не создавайте множества внутри условного оператора

Возможно, когда-нибудь вы решите оптимизировать ваш код вот таким образом:

if animal in set(animals):

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

if animal in animals:

Даже если окажется, что в списке значительно больше зверей для проверки, интерпретатор настроен таким образом, что применение функции set только замедлит выполнение. Проверка при помощи оператора in как правило быстрее без использования функции set.

19. Используйте связанные списки

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

Массивы требуют выделения памяти заранее. А это может быть затратным и бесполезным, особенно если мы не знаем размер массива.

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

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

20. Используйте облачные средства отладки

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

Заключение

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

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

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here