Быстрая работа со списками в Python: методы и техники
Оптимизация списков в Python: ключевые приёмы
Как создать и преобразовать список быстрее всего?
Наиболее эффективным решением для генерации и модификации списков является использование list comprehensions (списковых включений). Они выполняются на уровне C, минуя медленный интерпретатор Python при каждой итерации. Это делает их в 1,5–2 раза быстрее эквивалентного цикла for с append().
# Медленно: цикл for + append
squares_slow = []
for x in range(1_000_000):
squares_slow.append(x ** 2)
# Быстро: list comprehension
squares_fast = [x ** 2 for x in range(1_000_000)]
время работы программы python (время выполнения программы на python)
Такой подход подходит для любых случаев, где новый список строится на основе существующего итератора или другого списка. Используйте его всегда, когда нужно создать список без побочных эффектов.
Типичная ошибка: применение list comprehension для слишком сложных выражений (например, с вложенными циклами и условиями) может ухудшить читаемость. В таких случаях лучше вынести логику в отдельную функцию или использовать генератор.
Проблема: при создании очень больших списков (миллионы элементов) память может быть переполнена. Альтернатива - генераторные выражения (см. раздел вариантов).
Цель: ускорение создания и фильтрации списков. Случаи использования: предобработка данных, математические вычисления, фильтрация по условию.
Как уменьшить потребление памяти при работе с большими последовательностями?
Вместо списка используйте генераторное выражение (generator expression). Оно вычисляет элементы лениво, не храня их все в памяти.
# Список занимает память
big_list = [x * 2 for x in range(10**7)]
# Генератор почти не занимает память
big_gen = (x * 2 for x in range(10**7))
скорость выполнения программы python (скорость выполнения программы на python)
Ошибка: попытка повторно пройти по генератору - он опустошается после первого прохода. Для повторного обхода нужно создавать новый генератор.
Цель: экономия памяти при обработке потоковых данных. Случаи: чтение файлов, работа с бесконечными последовательностями.
Как ускорить добавление и удаление элементов в начале списка?
Операции list.insert(0, ...) и list.pop(0) имеют сложность O(n). Для частых операций с обоими концами используйте collections.deque (двусторонняя очередь).
from collections import deque
dq = deque([1, 2, 3])
dq.appendleft(0) # O(1) вместо O(n)
dq.popleft() # O(1)
Python быстрые списки (оптимизация работы со списками в python)
Ошибка: забыть импортировать deque или пытаться индексировать его как список - индексация работает, но медленнее.
Цель: реализация очередей, стеков с доступом к обоим концам. Случаи: BFS, кэширование последних N элементов.
Как ускорить операции с числовыми списками?
Используйте модуль array для однотипных числовых данных. Он хранит элементы в компактном виде и обеспечивает быстрый доступ.
from array import array
arr = array('i', [1, 2, 3, 4]) # массив целых чисел
arr.append(5)
Для серьёзных числовых расчётов применяйте NumPy - векторизованные операции на C.
import numpy as np
np_arr = np.array([1, 2, 3])
print(np_arr * 2) # [2 4 6]
Ошибка: смешивание типов в array - модуль требует единого типа. В NumPy преобразование типов может быть неявным и дорогим.
Цель: ускорение математических операций. Случаи: научные вычисления, обработка изображений.
Как эффективно вставлять элементы в отсортированный список?
Модуль bisect поддерживает список в отсортированном состоянии через бинарный поиск (O(log n) для поиска позиции, O(n) для вставки).
import bisect
sorted_list = [1, 3, 5]
bisect.insort(sorted_list, 4) # [1, 3, 4, 5]
Ошибка: использование insort на неотсортированном списке - результат будет неопределён.
Цель: поддержание порядка при частых вставках. Случаи: задачи с необходимостью динамической сортировки.
Как ускорить проверку наличия элемента в списке?
Операция in на списке имеет сложность O(n). Если нужно многократно проверять вхождение, преобразуйте список в множество set (хэш-таблица, O(1) в среднем).
my_list = [10, 20, 30, 40]
my_set = set(my_list)
# Проверка
if 20 in my_set: # быстро
...
Ошибка: множество не хранит порядок и дубликаты. Если порядок важен, используйте OrderedDict или список + множество.
Цель: ускорение проверок членства. Случаи: фильтрация уникальных элементов, проверка дубликатов.
Расширенные примеры оптимизации списков
Ниже приведены детальные примеры с кодом и результатами, демонстрирующие продвинутые техники.
1. Сравнение скорости: list comprehension vs цикл
import timeit
# Подготовка
setup = ''
# Цикл for + append
code_loop = '''
squares = []
for i in range(1000):
squares.append(i * i)
'''
# List comprehension
code_lc = '''
squares = [i * i for i in range(1000)]
'''
print('Цикл:', timeit.timeit(code_loop, number=10000))
print('List comp:', timeit.timeit(code_lc, number=10000))
Цикл: 0.582 List comp: 0.344
List comprehension примерно в 1.7 раза быстрее.
2. Использование itertools.chain для объединения списков
from itertools import chain
list1 = [1, 2, 3]
list2 = [4, 5, 6]
list3 = [7, 8, 9]
# Обычный способ
combined = list1 + list2 + list3 # создаёт новый список, копируя все элементы
# chain (ленивое объединение)
combined_chain = list(chain(list1, list2, list3)) # тоже список, но без промежуточных копий
# Для больших списков chain быстрее, так как не создаёт временные списки
print(combined_chain)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
3. Применение slice assignment для замены подсписка
letters = ['a', 'b', 'c', 'd', 'e']
# Заменить элементы с индекса 1 по 3 (не включая 3) на новые
letters[1:3] = ['x', 'y', 'z']
print(letters)
['a', 'x', 'y', 'z', 'd', 'e']
Этот метод работает на месте и быстрее, чем цикл с присваиванием отдельных элементов.
4. Кеширование атрибутов объекта с помощью __slots__
class Point:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
# Создание списка объектов
points = [Point(i, i*2) for i in range(100000)]
# Экономия памяти: каждый объект не имеет __dict__
print('Размер первого объекта:', sys.getsizeof(points[0]))
Размер первого объекта: 56
Без __slots__ размер был бы около 72+ байт. Экономия особенно заметна при хранении миллионов объектов в списке.
5. Использование memoryview и array для двоичных данных
from array import array
arr = array('B', range(100)) # unsigned char
mv = memoryview(arr)
# Быстрое копирование без создания новых объектов
sliced = mv[10:20].tobytes()
print(sliced)
b'\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13'
Memoryview позволяет работать с буфером данных без копирования, ускоряя операции с большими объёмами.
6. Параллельная итерация с zip и оптимизация enumerate
names = ['Alice', 'Bob', 'Charlie']
scores = [85, 92, 78]
# Обычный способ с range
for i in range(len(names)):
print(names[i], scores[i])
# Быстрее: zip
for name, score in zip(names, scores):
print(name, score)
# Если нужен индекс: enumerate + zip
for i, (name, score) in enumerate(zip(names, scores)):
print(i, name, score)
Alice 85 Bob 92 Charlie 78 0 Alice 85 1 Bob 92 2 Charlie 78
Использование zip и enumerate снижает количество индексирований и делает код быстрее.
7. Фильтрация с filter и map с объяснением
nums = [1, 2, 3, 4, 5, 6]
# Фильтр: оставить чётные
filtered = list(filter(lambda x: x % 2 == 0, nums))
print('Чётные:', filtered)
# Map: увеличить в 10 раз
mapped = list(map(lambda x: x * 10, nums))
print('Умножено на 10:', mapped)
Чётные: [2, 4, 6] Умножено на 10: [10, 20, 30, 40, 50, 60]
Функции filter и map работают на уровне C и могут быть быстрее эквивалентных list comprehensions при работе с большими данными, особенно если используется встроенная функция.
8. Сортировка с key - ускорение сравнений
data = ['apple', 'Banana', 'cherry', 'Date']
# Без key (сравнение по умолчанию с учётом регистра)
sorted_default = sorted(data)
print('Без key:', sorted_default)
# С key=str.lower - регистронезависимая сортировка
sorted_lower = sorted(data, key=str.lower)
print('С key:', sorted_lower)
# С key=len - сортировка по длине
sorted_len = sorted(data, key=len)
print('По длине:', sorted_len)
Без key: ['Banana', 'Date', 'apple', 'cherry'] С key: ['apple', 'Banana', 'cherry', 'Date'] По длине: ['Date', 'apple', 'Banana', 'cherry']
Использование key ускоряет сортировку, так как функция вычисляется один раз для каждого элемента, а не при каждом сравнении.