Списки (list) в Python: полный гид по созданию, изменениям и эффективной работе
Список в Python — универсальная коллекция общего назначения. Он поддерживает произвольные типы, изменяется «на месте», легко комбинируется с циклами, генераторами и функциями стандартной библиотеки. Ниже — системный разбор: от базовых операций до нюансов сортировки, копирования и производительности.
Ключевая идея
Список — упорядоченная и изменяемая последовательность. Это означает:
- Порядок элементов сохраняется.
- Можно добавлять, удалять и заменять элементы без пересоздания объекта.
- Поддерживаются смешанные типы и вложенные структуры.
empty = []
mix = [1, "a", 3.14, True]
grid = [[1, 2], [3, 4]] # вложенные списки
mix[0] = 42 # изменение «на месте»
Важно: переменные хранят ссылку на объект. Присваивание создаёт вторую ссылку, а не копию.
a = [1, 2, 3]
b = a
b.append(99)
print(a) # [1, 2, 3, 99]
Создание списков
- Литерал:
[] - Конструктор:
list(iterable) - Преобразование из других коллекций: tuple, set, range
- Генератор списка (list comprehension)
L1 = [1, 2, 3]
L2 = list("abc") # ['a', 'b', 'c']
L3 = list(range(5)) # [0, 1, 2, 3, 4]
tpl = (10, 20)
L4 = list(tpl) # [10, 20]
squares = [x * x for x in range(6)]
bad = [[0] * 3] * 2
bad[0][0] = 9
print(bad) # [[9, 0, 0], [9, 0, 0]]
good = [[0] * 3 for _ in range(2)] # независимые строки
Индексация и срезы
Индексация с нуля. Отрицательные индексы отсчитываются с конца. Срезы создают новый список; присваивание срезу изменяет исходный.
L = ["a", "b", "c", "d", "e"]
L[0], L[-1] # 'a', 'e'
L[1:4] # ['b', 'c', 'd']
L[::2] # ['a', 'c', 'e']
copy1 = L[:] # поверхностная копия
L[1:3] = ["B", "C"] # ['a', 'B', 'C', 'd', 'e']
Операции со списками
- len(L) — размер;
- in/not in — проверка наличия;
- + — конкатенация; * — повторение;
- лексикографическое сравнение по элементам.
nums = [1, 2, 3]
len(nums) # 3
2 in nums # True
nums + [4, 5] # [1, 2, 3, 4, 5]
["a"] * 3 # ['a', 'a', 'a']
Методы списков: практичный справочник
Добавление
append(x) — один элемент в конец; extend(iterable) — добавить элементы итерируемого; insert(i, x) — вставка по индексу.
L = [1, 2]
L.append(3) # [1, 2, 3]
L.extend([4, 5]) # [1, 2, 3, 4, 5]
L.insert(1, 100) # [1, 100, 2, 3, 4, 5]
# типичная путаница:
L = [1, 2]; L.append([3, 4]) # [1, 2, [3, 4]] (один элемент-список)
Удаление и извлечение
remove(x) — удаляет первое вхождение; pop(i=-1) — возвращает и удаляет элемент по индексу; clear() — очищает список.
L = ["a", "b", "b", "c"]
L.remove("b") # ["a", "b", "c"]
last = L.pop() # "c", теперь ["a", "b"]
first = L.pop(0) # "a", теперь ["b"]
L.clear() # []
Поиск и подсчёт
index(x[, start[, end]]) — индекс первого вхождения; count(x) — количество элементов, равных x.
L = [10, 20, 10, 30]
L.index(10) # 0
L.index(10, 1) # 2
L.count(10) # 2
Порядок элементов
sort(key=None, reverse=False) — сортирует «на месте»; reverse() — разворачивает список; функция sorted() возвращает новый список.
L = ["Bob", "alice", "Carol"]
L.sort() # ['Bob', 'Carol', 'alice'] (Unicode-порядок)
L.sort(key=str.lower) # ['alice', 'Bob', 'Carol'] (без регистра)
L.reverse() # ['Carol', 'Bob', 'alice']
nums = [3, 1, 2]
sorted(nums) # [1, 2, 3]; исходник не меняется
Копирование
Поверхностная копия: L.copy() или L[:]. Для вложенных структур — copy.deepcopy.
import copy
L = [[1], [2]]
shallow = L.copy()
deep = copy.deepcopy(L)
L[0].append(99)
print(shallow) # [[1, 99], [2]]
print(deep) # [[1], [2]]
Итерация и распаковка
Для доступа к индексам используйте enumerate, для параллельного обхода нескольких коллекций — zip. Удобна «звёздная распаковка».
colors = ["red", "green", "blue"]
for i, c in enumerate(colors, start=1):
print(i, c)
xs = [1, 2, 3]; ys = [4, 5, 6]
for x, y in zip(xs, ys):
print(x, y)
head, *middle, tail = [10, 20, 30, 40]
print(head, middle, tail) # 10 [20, 30] 40
Генераторы списков
Лаконичный синтаксис для построения коллекций с преобразованием и фильтрацией. Когда логика становится громоздкой, лучше перейти к обычному циклу.
evens = [x for x in range(10) if x % 2 == 0]
pairs = [(x, y) for x in range(3) for y in range(2)]
labels = ["even" if x % 2 == 0 else "odd" for x in range(5)]
# эквивалент через цикл для сложных сценариев
res = []
for x in range(10):
if x % 2 == 0:
res.append(x)
Вложенные списки (таблицы/матрицы)
«Список списков» — удобная модель двумерных данных. Следите за независимостью строк и осторожно копируйте.
M = [[1, 2, 3], [4, 5, 6]]
M[1][2] # 6
rows, cols = 2, 3
M = [[0] * cols for _ in range(rows)]
Стандартные функции, которые «любят» списки
- sum, min, max — агрегирование;
- any, all — логические проверки с коротким замыканием;
- sorted — сортировка без изменения исходника;
- map, filter — преобразование/фильтрация (часто читаемее через генераторы).
nums = [3, 5, 1, 4]
sum(nums), min(nums), max(nums) # 13, 1, 5
any(n % 2 == 0 for n in nums) # есть ли чётные
all(n > 0 for n in nums) # все ли положительные
data = [{"name": "bob", "age": 30}, {"name": "ann", "age": 20}]
sorted(data, key=lambda d: d["age"])
Производительность: что важно знать
- append — амортизированно O(1).
- Вставки/удаления в середине — O(n).
- Частые операции «в начале» удобнее переносить на collections.deque.
- Не конкатенируйте списки в цикле через
+; собирайте элементы и применяйте extend один раз.
Полезные паттерны
- Стек:
append/pop(). - Очередь: используйте
collections.dequeдляpopleft. - Удаление по условию: создавайте новый список, а не меняйте текущий в процессе обхода.
- Сохранить порядок и удалить дубликаты — через «множество виденных».
seen, out = set(), []
for x in [1, 2, 2, 3, 1, 4]:
if x not in seen:
seen.add(x); out.append(x)
print(out) # [1, 2, 3, 4]
Антипаттерны и частые ошибки
- Путаница append/extend.
- Ссылки вместо копий (нужны copy, срез или deepcopy для вложенных структур).
[[0]*N]*Mдля матриц — общие ссылки на строки.- Изменение списка во время итерации по нему.
- Сравнение значений через
isвместо==.
L = [1, 2, 3, 4, 5]
L = [x for x in L if x % 2 == 0]
Практикум
# 1) чётные квадраты
nums = [1, 2, 3, 4, 5, 6]
print([n * n for n in nums if n % 2 == 0]) # [4, 16, 36]
# 2) разворачивание вложенного списка
nested = [[1, 2], [3, 4, 5]]
flat = [x for sub in nested for x in sub]
print(flat)
# 3) частоты элементов
data = ["a", "b", "a", "c", "b", "a"]
freq = {}
for x in data:
freq[x] = freq.get(x, 0) + 1
print(freq)
# 4) сортировка по второму элементу кортежа (по убыванию)
items = [("a", 3), ("b", 1), ("c", 2)]
print(sorted(items, key=lambda t: t[1], reverse=True))
FAQ
Чем отличаются append и extend?
append(x) добавляет один объект как единую сущность; extend(iterable) добавляет элементы итерируемого по одному.
L = [1, 2]; L.append([3, 4]) # [1, 2, [3, 4]]
L = [1, 2]; L.extend([3, 4]) # [1, 2, 3, 4]
Когда использовать sort, а когда sorted?
list.sort() меняет список «на месте» и возвращает None; sorted(iterable) создаёт новый список, исходник сохраняется.
Почему умножение списка с вложенными данными даёт неожиданный результат?
Повторяются ссылки на один и тот же вложенный объект. Для независимых строк используйте генератор списков.
Как копировать списки корректно?
Поверхностно: L.copy() или L[:]. Глубоко: copy.deepcopy для вложенных структур.
Как развернуть список?
reverse() меняет порядок «на месте», срез L[::-1] возвращает новую перевёрнутую копию.
Итоги
Списки — фундаментальный инструмент Python. Освойте создание и срезы, грамотно используйте методы добавления/удаления, помните о стабильной сортировке и различии поверхностных и глубоких копий. Учитывайте стоимость операций и выбирайте подходящую структуру данных под задачу. Регулярная практика быстро превращает эти приёмы в автоматизм. 💪
