Python ускорение работы кода

2. sort() vs sorted()

Если нам просто нужен отсортированный список, при этом неважно, что будет с оригиналом, sort() будет работать немного быстрее, чем sorted() . Это справедливо для базовой сортировки:

# 1. Дефолтная сортировка с использованием sorted() %%time sorted(a_long_list) # Вывод в консоли: # CPU times: user 12 ms, sys: 2.51 ms, total: 14.5 ms # Wall time: 14.2 ms # 2. Дефолтная сортировка с использованием sort() %%time a_long_list.sort() # Вывод в консоли: # CPU times: user 8.52 ms, sys: 82 μs, total: 8.6 ms # Wall time: 10 ms 

Справедливо и для сортировки с использованием ключа – параметра key , который определяет сортировочную функцию:

# 1. Сортировка с ключом с использованием sorted() %%time str_list1 = "Although both functions can sort list, there are small differences".split() result = sorted(str_list1, key=str.lower) print(result) # Вывод в консоли: # ['Although', 'are', 'both', 'can', 'differences', 'functions', 'list,', 'small', 'sort', 'there'] # CPU times: user 29 μs, sys: 0 ns, total: 29 μs # Wall time: 32.9 μs # 2. Сортировка с ключом с использованием sort() %%time str_list2 = "Although both functions can sort list, there are small differences".split() str_list2.sort(key=str.lower) print(str_list2) # Вывод в консоли: # ['Although', 'are', 'both', 'can', 'differences', 'functions', 'list,', 'small', 'sort', 'there'] # CPU times: user 26 μs, sys: 0 ns, total: 26 μs # Wall time: 29.8 μs # 3. Сортировка с ключом (лямбда) с использованием sorted() %%time str_list1 = "Although both functions can sort list, there are small differences".split() result = sorted(str_list1, key=lambda str: len(str)) print(result) # Вывод в консоли: # ['can', 'are', 'both', 'sort', 'list,', 'there', 'small', 'Although', 'functions', 'differences'] # CPU times: user 61 μs, sys: 3 μs, total: 64 μs # Wall time: 59.8 μs # 4. Сортировка с ключом (лямбда) с использованием sort() %%time str_list2 = "Although both functions can sort list, there are small differences".split() str_list2.sort(key=lambda str: len(str)) print(str_list2) # Вывод в консоли: # ['can', 'are', 'both', 'sort', 'list,', 'there', 'small', 'Although', 'functions', 'differences'] # CPU times: user 36 μs, sys: 0 ns, total: 36 μs # Wall time: 38.9 μs 

Так происходит потому, что метод sort() изменяет список прямо на месте, в то время как sorted() создает новый отсортированный список, сохраняя исходный нетронутым. Другими словами, порядок значений внутри a_long_list фактически уже изменился.

Однако функция sorted() более универсальна. Она может работать с любой итерируемой структурой. Поэтому, если нужно отсортировать, например, словарь (по ключам или по значениям), придется использовать sorted() :

a_dict = # 1. Дефолтная сортировка по ключам %%time result = sorted(a_dict) print(result) # Вывод в консоли: # ['A', 'B', 'C', 'D', 'E'] # CPU times: user 4 μs, sys: 0 ns, total: 4 μs # Wall time: 6.91 μs # 2. Cортировка по значениям, результат в виде списка кортежей %%time result = sorted(a_dict.items(), key=lambda item: item[1]) print(result) # Вывод в консоли: # [('A', 1), ('C', 2), ('B', 3), ('D', 4), ('E', 5)] # CPU times: user 7 μs, sys: 0 ns, total: 7 μs # Wall time: 8.82 μs # 3. Сортировка по значениям, результат в виде словаря %%time result = print(result) # Вывод в консоли: # # CPU times: user 8 μs, sys: 0 ns, total: 8 μs # Wall time: 11.2 μs 

3. Литералы вместо функций

Когда нужен пустой словарь или список, вместо dict() или list() , можно напрямую вызвать <> и [] (для пустого множества все еще нужна функция set() ). Этот прием не обязательно ускорит ваш код, но сделает его более «pythonic».

# 1. Создание пустого словаря с помощью dict() %%time sorted_dict1 = dict() for key, value in sorted(a_dict.items(), key=lambda item:item[1]): sorted_dict1Python ускорение работы кода = value # Вывод в консоли: # CPU times: user 10 μs, sys: 0 ns, total: 10 μs # Wall time: 12.2 μs # 2. Создание пустого словаря с помощью литерала словаря %%time sorted_dict2 = <> for key, value in sorted(a_dict.items(), key=lambda item:item[1]): sorted_dict2Python ускорение работы кода = value # Вывод в консоли: # CPU times: user 9 μs, sys: 0 ns, total: 9 μs # Wall time: 11 μs # 3. Создание пустого списка с помощью list() %%time list() # Вывод в консоли: # CPU times: user 3 μs, sys: 0 ns, total: 3 μs # Wall time: 3.81 μs # 4. Создание пустого списка с помощью литерала списка %%time [] # Вывод в консоли: # CPU times: user 2 μs, sys: 0 ns, total: 2 μs # Wall time: 3.1 μs 

4. Генераторы списков

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

Читайте также:  Hashcode and equals override in java

Например, отберём все чётные числа из списка another_long_list :

even_num = [] for number in another_long_list: if number % 2 == 0: even_num.append(number) 

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

import random random.seed(666) another_long_list = [random.randint(0,500) for i in range(1000000)] # 1. Создание нового списка с помощью цикла for %%time even_num = [] for number in another_long_list: if number % 2 == 0: even_num.append(number) # Вывод в консоли: # CPU times: user 113 ms, sys: 3.55 ms, total: 117 ms # Wall time: 117 ms # 2. Создание нового списка с помощью генератора списка %%time even_num = [number for number in another_long_list if number % 2 == 0] # Вывод в консоли: # CPU times: user 56.6 ms, sys: 3.73 ms, total: 60.3 ms # Wall time: 64.8 ms 

Сочетая это правило с Правилом #3 (использование литералов), мы легко можем превратить список в словарь или множество, просто изменив скобки:

a_dict = sorted_dict3 = print(sorted_dict3) # Вывод в консоли: #
  • Выражение sorted(a_dict.items(), key=lambda item: item[1]) возвращает список кортежей [(‘A’, 1), (‘C’, 2), (‘B’, 3), (‘D’, 4), (‘E’, 5)] .
  • Далее мы распаковываем кортежи и присваиваем первый элемент каждого кортежа в переменную key , а второй – в переменную value .
  • Наконец, сохраняем каждую пару key — value в словаре.

5. enumerate() для значения и индекса

Иногда при переборе списка нужны и значения, и их индексы. Чтобы вдвое ускорить код используйте enumerate() для превращения списка в пары индекс-значение:

import random random.seed(666) a_short_list = [random.randint(0,500) for i in range(5)] # 1. Получение индексов с помощью использования длины списка %%time for i in range(len(a_short_list)): print(f'number is ') # Вывод в консоли: # number 0 is 233 # number 1 is 462 # number 2 is 193 # number 3 is 222 # number 4 is 145 # CPU times: user 189 μs, sys: 123 μs, total: 312 μs # Wall time: 214 μs # 2. Получение индексов с помощью enumerate() for i, number in enumerate(a_short_list): print(f'number is ') # Вывод в консоли: # number 0 is 233 # number 1 is 462 # number 2 is 193 # number 3 is 222 # number 4 is 145 # CPU times: user 72 μs, sys: 15 μs, total: 87 μs # Wall time: 90.1 μs 

6. zip() для перебора нескольких списков

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

list1 = ['a', 'b', 'c', 'd', 'e'] list2 = ['1', '2', '3', '4', '5'] pairs_list = [pair for pair in zip(list1, list2)] print(pairs_list) # Вывод в консоли: [('a', '1'), ('b', '2'), ('c', '3'), ('d', '4'), ('e', '5')] 

Обратите внимание, списки должны быть одинаковой длины, так как функция zip() останавливается, когда заканчивается более короткий список.

Читайте также:  Php executable path setting

И наоборот, чтобы получить доступ к элементам каждого кортежа, мы можем распаковать список кортежей, добавив звездочку ( * ) и используя множественное присваивание:

# 1. Распаковка списка кортежей с помощью zip() %%time letters1, numbers1 = zip(*pairs_list) print(letters1, numbers1) # Вывод в консоли: ('a', 'b', 'c', 'd', 'e') ('1', '2', '3', '4', '5') # CPU times: user 5 μs, sys: 1e+03 ns, total: 6 μs # Wall time: 6.91 μs # 2. Распаковка списка кортежей простым перебором letters2 = [pair[0] for pair in pairs_list] numbers2 = [pair[1] for pair in pairs_list] print(letters2, numbers2) # Вывод в консоли: ['a', 'b', 'c', 'd', 'e'] ['1', '2', '3', '4', '5'] # CPU times: user 5 μs, sys: 1e+03 ns, total: 6 μs # Wall time: 7.87 μs 

7. Комбинация set() и in

Если нужно проверить, содержит ли список некоторое значение, можно написать такую неуклюжую функцию:

import random random.seed(666) another_long_list = [random.randint(0,500) for i in range(1000000)] def check_membership(n): for element in another_long_list: if element == n: return True return False 

Однако есть более характерный для Python способ сделать это – использовать оператор in :

# 1. Проверка наличия значения в списке перебором элементов %%time check_membership(900) # Вывод в консоль # CPU times: user 29.7 ms, sys: 847 μs, total: 30.5 ms # Wall time: 30.2 ms # 2. Проверка наличия значения в списке с помощью in 900 in another_long_list # Вывод в консоль # CPU times: user 10.2 ms, sys: 79 μs, total: 10.3 ms # Wall time: 10.3 ms 

Повысить эффективность можно предварительным удалением из списка дубликатов с помощью set . Таким образом, мы сократим количество элементов для проверки. Кроме того, оператор in очень быстро работает с множествами.

# Убираем дубликаты check_list = set(another_long_list) # Вывод в консоль # CPU times: user 19.8 ms, sys: 204 μs, total: 20 ms # Wall time: 20 ms # Проверяем наличие значения в списке 900 in check_list # Вывод в консоль # CPU times: user 2 μs, sys: 0 ns, total: 2 μs # Wall time: 5.25 μs 

Преобразование списка в множество заняло 20 мс. Но это одноразовые затраты. Зато сама проверка заняла 5 мкс – то есть в 2 тыс. раз меньше, что становится важным при частых обращениях.

Читайте также:  Python магический метод print

8. Проверка на True

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

Не следует явно указывать == True или is True в условии if , достаточно указать имя проверяемой переменной. Это экономит ресурсы, которые использует «магическая» функция __eq__ для сравнения значений.

string_returned_from_function = 'Hello World' # 1. Явная проверка на равенство %%time if string_returned_from_function == True: pass # Вывод в консоль # CPU times: user 3 μs, sys: 0 ns, total: 3 μs # Wall time: 5.01 μs # 2. Явная проверка с использованием оператора is %%time if string_returned_from_function is True: pass # Вывод в консоль # CPU times: user 2 μs, sys: 1 ns, total: 3 μs # Wall time: 4.05 μs # 3. Неявное равенство %%time if string_returned_from_function: pass # Вывод в консоль # CPU times: user 3 μs, sys: 0 ns, total: 3 μs # Wall time: 4.05 μs 

Аналогично можно проверять обратное условие, добавив оператор not :

if not string_returned_from_function: pass 

9. Подсчет уникальных значений с Counter()

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

%%time num_counts = <> for num in a_long_list: if num in num_counts: num_counts[num] += 1 else: num_counts[num] = 1 # Вывод в консоль # CPU times: user 448 ms, sys: 1.77 ms, total: 450 ms # Wall time: 450 ms 

Однако более эффективный способ для решения этой задачи – использование Counter() из модуля collections. Весь код при этом уместится в одной строчке:

%%time num_counts2 = Counter(a_long_list) # Вывод в консоль # CPU times: user 40.7 ms, sys: 329 μs, total: 41 ms # Wall time: 41.2 ms 

Этот фрагмент будет работать примерно в 10 раз быстрее, чем предыдущий.

У Counter также есть удобный метод most_common , позволяющий получить самые часто встречающиеся значения:

for number, count in num_counts2.most_common(10): print(number, count) # Вывод в консоль 29 19831 47 19811 7 19800 36 19794 14 19761 39 19748 32 19747 16 19737 34 19729 33 19729 

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

10. Цикл for внутри функции

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

def compute_cubic1(number): return number**3 %%time new_list_cubic1 = [compute_cubic1(number) for number in a_long_list] # Вывод в консоль # CPU times: user 335 ms, sys: 14.3 ms, total: 349 ms # Wall time: 354 ms 

Однако правильнее будет перевернуть конструкцию – и поместить цикл внутрь функции.

def compute_cubic2(): return [number**3 for number in a_long_list] %%time new_list_cubic2 = compute_cubic2() # Вывод в консоль # CPU times: user 261 ms, sys: 15.7 ms, total: 277 ms # Wall time: 277 ms 

В данном примере для миллиона итераций (длина a_long_list ) мы сэкономили около 22% времени.

Будем рады, если вы поделитесь в комментариях своими подходами к ускорению кода в Python. Вот ещё несколько статей, которые могут вас заинтересовать:

Источники

Источник

Оцените статью