Анатомия кода – написание высокопроизводительного кода на Python

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

Исходя из описания функциональности сниппета, мы можем наивно написать его следующим образом:

def difference(a, b):
  return [item for item in a if item not in b]
Войти в полноэкранный режим Выйти из полноэкранного режима

Эта реализация может работать достаточно хорошо, но она не учитывает дубликаты в b. Из-за этого в случаях с большим количеством дубликатов во втором списке код займет больше времени, чем необходимо. Для решения этой проблемы можно воспользоваться методом set(), который сохранит только уникальные значения в списке:

def difference(a, b):
  return [item for item in a if item not in set(b)]
Вход в полноэкранный режим Выйти из полноэкранного режима

Эта версия, хотя и кажется улучшенной, на самом деле может быть медленнее предыдущей. Если вы внимательно посмотрите, то увидите, что set() вызывается для каждого item в a, заставляя результат set(b) оцениваться каждый раз. Вот пример, в котором мы обернули set() другим методом, чтобы лучше продемонстрировать проблему:

def difference(a, b):
  return [item for item in a if item not in make_set(b)]

def make_set(itr):
  print('Making set...')
  return set(itr)

print(difference([1, 2, 3], [1, 2, 4]))
# Making set...
# Making set...
# Making set...
# [3]
Вход в полноэкранный режим Выход из полноэкранного режима

Решением этой проблемы является вызов set() один раз перед вычислением списка и сохранение результата для ускорения процесса:

def difference(a, b):
  _b = set(b)
  return [item for item in a if item not in _b]
Вход в полноэкранный режим Выход из полноэкранного режима

Еще один вариант, который стоит упомянуть с точки зрения производительности, – это использование понимания списка в сравнении с filter() и list(). Реализация того же кода с использованием последнего варианта приведет к следующему результату:

def difference(a, b):
  _b = set(b)
  return list(filter(lambda item: item not in _b, a))
Войти в полноэкранный режим Выход из полноэкранного режима

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

Это в значительной степени относится к большинству сниппетов математических операций со списками, таких как difference, symmetric_difference и intersection.


Вам нравятся короткие, качественные фрагменты кода и статьи? Мы тоже! Посетите сайт 30 seconds of code, чтобы найти больше статей, подобных этой, или следите за нами в Twitter, чтобы получать ежедневные фрагменты JavaScript, React и Python! 👨💻

Оцените статью
Procodings.ru
Добавить комментарий