Списки: list, методы, slice, sort vs sorted
list - самая используемая коллекция в Python. Это упорядоченная, изменяемая последовательность элементов любого типа. Под капотом - динамический массив указателей. В этом уроке - операции, срезы (slice), сортировка и типичные идиомы.
Создание списка
# Литерал
nums = [1, 2, 3]
# Конструктор list()
empty = list() # []
chars = list("hello") # ['h', 'e', 'l', 'l', 'o']
nums = list(range(5)) # [0, 1, 2, 3, 4]
# Из comprehension
squares = [x ** 2 for x in range(5)] # [0, 1, 4, 9, 16]
# Повторение
zeros = [0] * 10 # десять нулей
matrix = [[0] * 3] * 3
matrix[0][0] = 99
print(matrix) # [[99, 0, 0], [99, 0, 0], [99, 0, 0]] - все строки одно и то же!
Правильно через comprehension:
matrix = [[0] * 3 for _ in range(3)]
matrix[0][0] = 99
print(matrix) # [[99, 0, 0], [0, 0, 0], [0, 0, 0]]
Доступ и модификация
nums = [10, 20, 30, 40, 50]
# Чтение по индексу
nums[0] # 10
nums[-1] # 50 - последний
nums[-2] # 40 - предпоследний
# Запись
nums[0] = 100
nums.append(60) # добавить в конец
nums.insert(0, 5) # вставить по индексу
nums.extend([70, 80]) # добавить элементы из iterable
# Удаление
del nums[0] # по индексу
nums.remove(30) # первое вхождение значения
last = nums.pop() # последний + удалить
first = nums.pop(0) # по индексу + удалить
nums[100] для несуществующего индекса бросает IndexError. Проверь длину или используй try/except.
Срезы (slice)
list[start:stop:step] - один из самых мощных инструментов:
nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
nums[2:5] # [2, 3, 4]
nums[:3] # [0, 1, 2] - с начала
nums[7:] # [7, 8, 9] - до конца
nums[:] # копия всего списка
nums[::2] # [0, 2, 4, 6, 8] - каждый второй
nums[::-1] # [9, 8, ..., 0] - реверс
nums[1:8:2] # [1, 3, 5, 7]
Стандарт: [start:stop:step]. Все три параметра опциональны. stop не включается - полу-открытый интервал.
Срезы создают копию, не view (в отличие от NumPy):
a = [1, 2, 3, 4, 5]
b = a[1:4]
b[0] = 999
print(a) # [1, 2, 3, 4, 5] - не изменился
print(b) # [999, 3, 4]
Slice assignment
Срезы работают и для присваивания:
nums = [1, 2, 3, 4, 5]
nums[1:3] = [20, 30] # заменить
nums[1:3] = [20, 30, 40] # заменить большим - длина списка изменится
nums[1:3] = [] # удалить участок
nums[:] = [100, 200] # заменить всё содержимое (но сам объект тот же)
nums[:] = ... - идиома для модификации списка in-place, не пересоздавая. Полезно когда есть другие ссылки на этот список.
Поиск и подсчёт
nums = [1, 2, 3, 2, 4, 2]
2 in nums # True
nums.index(2) # 1 - первое вхождение
nums.index(2, 2) # 3 - первое вхождение начиная с индекса 2
nums.count(2) # 3 - сколько раз встречается
index бросает ValueError если не нашёл. Безопаснее try/except или проверка через in.
Сортировка
Два способа:
nums = [3, 1, 4, 1, 5, 9, 2, 6]
# 1. sorted() - возвращает новый список
sorted_nums = sorted(nums)
print(nums) # [3, 1, 4, ...] - не изменился
print(sorted_nums) # [1, 1, 2, 3, 4, 5, 6, 9]
# 2. list.sort() - модифицирует на месте, возвращает None
nums.sort()
print(nums) # [1, 1, 2, 3, 4, 5, 6, 9]
Параметры:
sorted(nums, reverse=True) # по убыванию
users = [{"name": "Bob", "age": 25}, {"name": "Alice", "age": 30}]
# По ключу
sorted(users, key=lambda u: u["age"])
sorted(users, key=lambda u: u["name"])
# По нескольким полям (через tuple)
sorted(users, key=lambda u: (u["age"], u["name"]))
# Stable: при равных ключах сохраняет порядок исходного
Ключ сортировки удобно задавать лямбдой или operator.itemgetter. Python использует Timsort - стабильный алгоритм со сложностью O(n log n). При повторных сортировках по разным критериям можно сортировать сначала по второстепенному, потом по главному.
reverse - обратный порядок
nums = [1, 2, 3, 4]
# reverse() - на месте
nums.reverse()
print(nums) # [4, 3, 2, 1]
# reversed() - возвращает iterator
for x in reversed([1, 2, 3]):
print(x) # 3, 2, 1
# Через срез - копия
nums[::-1] # [4, 3, 2, 1] - но создаёт новый список
reversed() - ленивая, не создаёт копию. Полезно для прохода без аллокации.
Объединение и проверки
# Конкатенация
[1, 2] + [3, 4] # [1, 2, 3, 4]
# Повторение
[1, 2] * 3 # [1, 2, 1, 2, 1, 2]
# Длина
len([1, 2, 3]) # 3
# Сумма/min/max
sum([1, 2, 3]) # 6
min([3, 1, 4]) # 1
max([3, 1, 4]) # 4
# all / any
all([True, True, True]) # True
any([False, False, True]) # True
# Проверка пустоты
if not items:
print("пустой")
if items:
print("есть элементы")
Эффективность операций
| Операция | Сложность |
|---|---|
lst[i] | O(1) |
lst[i:j] | O(j - i) |
lst.append(x) | O(1) амортизированная |
lst.insert(0, x) | O(n) - сдвиг всех |
lst.pop() | O(1) |
lst.pop(0) | O(n) - сдвиг всех |
x in lst | O(n) - линейный поиск |
lst.sort() | O(n log n) |
len(lst) | O(1) |
Запомни: вставки и удаления в начало дорогие. Если часто работаешь с обоими концами - используй collections.deque.
Распаковка
# Базовая
a, b, c = [1, 2, 3]
# С * для остатка
first, *rest = [1, 2, 3, 4] # first=1, rest=[2,3,4]
*init, last = [1, 2, 3, 4] # init=[1,2,3], last=4
first, *mid, last = [1, 2, 3, 4] # first=1, mid=[2,3], last=4
# Пропуск
_, second, _ = [1, 2, 3]
# В вложенном виде
((a, b), c) = ((1, 2), 3)
*rest собирает остаток в список. _ - конвенция для «не интересует».
Список как стек/очередь
Стек (LIFO):
stack = []
stack.append(1) # push
stack.append(2)
stack.append(3)
top = stack.pop() # pop - последний
Очередь (FIFO) - не используй list, медленно:
queue = []
queue.append(1) # enqueue
queue.append(2)
first = queue.pop(0) # dequeue - O(n)!
Для очереди используй collections.deque, а про сами структуры «стек и очередь» есть урок в треке алгоритмов.
Идиомы
Фильтрация:
positives = [x for x in nums if x > 0]
# или
positives = list(filter(lambda x: x > 0, nums))
Синтаксис [... for ... in ...] разбирали в уроке про comprehensions.
Преобразование:
doubled = [x * 2 for x in nums]
# или
doubled = list(map(lambda x: x * 2, nums))
Уникальные с сохранением порядка:
# Через dict (упорядоченный с Python 3.7)
unique = list(dict.fromkeys(nums))
# Через set - порядок не сохраняется
unique_unordered = list(set(nums))
Группировка по ключу:
from collections import defaultdict
users = [{"role": "admin", "name": "A"}, {"role": "user", "name": "B"}]
by_role = defaultdict(list)
for u in users:
by_role[u["role"]].append(u["name"])
Распространённые ошибки
1. Модификация во время итерации (повтор):
nums = [1, 2, 3, 4, 5]
for n in nums:
if n % 2 == 0:
nums.remove(n) # baddrum
# Правильно
nums = [n for n in nums if n % 2 != 0]
2. Изменение по shared reference:
default = []
a = default
b = default
a.append(1)
# b тоже [1] - они один объект
3. Сравнение sort() с возвращаемым значением:
# sort возвращает None, не отсортированный список!
result = nums.sort() # result is None
# Используй sorted() если нужен возврат
result = sorted(nums)
Сравнение с Go и PHP
В Go slice похож на Python list, но с типизацией и без удобных srезов с шагом:
nums := []int{1, 2, 3, 4, 5}
sub := nums[1:3] // [2, 3]
nums = append(nums, 6)
В PHP массивы - универсальные структуры (одновременно list и dict), что даёт гибкость, но усложняет понимание:
$arr = [1, 2, 3];
$arr[] = 4; // добавить
array_push($arr, 5);
Python list более похож на Go slice по поведению, но более выразительный за счёт slice-нотации.
Мини-задание
- Срезы и реверс:
nums = list(range(10)) # [0, 1, ..., 9]
print(nums[::-1]) # реверс
print(nums[::2]) # каждый второй
print(nums[-3:]) # последние три
print(nums[3:7]) # с индекса 3 до 6
- Сортировка по нескольким ключам:
people = [
{"name": "Alice", "age": 30, "city": "Moscow"},
{"name": "Bob", "age": 25, "city": "London"},
{"name": "Charlie", "age": 30, "city": "Paris"},
]
# Сортировка по age, при равном - по name
sorted_people = sorted(people, key=lambda p: (p["age"], p["name"]))
for p in sorted_people:
print(p)
- Уникальные с сохранением порядка:
items = [1, 2, 3, 2, 4, 1, 5, 3]
unique = list(dict.fromkeys(items))
print(unique) # [1, 2, 3, 4, 5]
Что дальше
Освоили списки. В следующем уроке - кортежи и NamedTuple: иммутабельные последовательности и их типизированные версии.