Списки
Создание
empty = []
nums = [3, 1, 4, 1, 5]
mixed = [1, "два", 3.0, None, [5, 6]] # элементы любых типов
chars = list("abc") # из любого итерируемого
zeros = [0] * 5
squares = [x * x for x in range(6)] # списковое включение
print(empty, nums, mixed, chars, zeros, squares, sep="\n")
print(len(nums), sum(nums), min(nums), max(nums))
Список — упорядоченная изменяемая коллекция. Внутри это динамический массив ссылок на объекты.
Индексы и срезы
Работают как у строк, но список можно изменять, в том числе через срезы:
a = [0, 1, 2, 3, 4, 5]
a[0] = 100 # замена элемента
a[1:3] = ["x", "y", "z"] # замена среза — длина может отличаться
print(a)
a[::2] = [0, 0, 0, 0] # срез с шагом — длина должна совпадать
print(a)
del a[1:3] # удаление среза
print(a)
Добавление и удаление
items = ["a", "b"]
items.append("c") # в конец один элемент
items.extend(["d", "e"]) # в конец все элементы итерируемого
items.insert(1, "X") # вставить по индексу
items += ["f"] # то же, что extend
print(items)
last = items.pop() # удалить и вернуть последний
first = items.pop(0) # удалить и вернуть по индексу
items.remove("X") # удалить первое вхождение значения (ValueError, если нет)
del items[0] # удалить по индексу
print(last, first, items)
items.clear()
print(items)
| Метод | Действие | Возвращает |
|---|---|---|
append(x) |
добавить x в конец | None |
extend(it) |
добавить все элементы it | None |
insert(i, x) |
вставить перед индексом i | None |
pop([i]) |
удалить по индексу (по умолчанию последний) | элемент |
remove(x) |
удалить первое вхождение x | None |
index(x) |
индекс первого вхождения | int |
count(x) |
число вхождений | int |
sort(), reverse() |
на месте | None |
Методы, изменяющие список, возвращают None
a = a.sort() — ошибка: теперь a is None. Изменяющие методы (append, sort, reverse, extend) работают на месте и возвращают None. Для нового списка — функции sorted(a) и reversed(a).
Поиск и проверка
fruits = ["яблоко", "груша", "слива", "груша"]
print("груша" in fruits, "банан" not in fruits)
print(fruits.index("груша"), fruits.count("груша"))
print(fruits.index("груша", 2)) # искать начиная с индекса 2
Сортировка
nums = [5, 2, 9, 1]
print(sorted(nums), nums) # sorted — новый список
nums.sort(reverse=True) # sort — на месте
print(nums)
words = ["банан", "Яблоко", "арбуз"]
print(sorted(words), sorted(words, key=str.lower), sorted(words, key=len))
Подробно про key — в уроке «Сортировка».
Копирование
a = [1, 2, [3, 4]]
b = a # не копия: то же самое
c = a[:] # поверхностная копия (также a.copy(), list(a))
c.append(5)
c[2].append(99) # вложенный список общий!
print(a, c)
Нажмите 👣 Пошагово — визуализатор покажет, что a и b — один объект, c — другой, но внутренний список [3, 4] у них общий. Подробнее — в уроке «Поверхностное и глубокое копирование».
Вложенные списки и ловушка умножения
bad = [[0] * 3] * 3 # три ссылки на ОДИН список
bad[0][0] = 1
print(bad)
good = [[0] * 3 for _ in range(3)] # три разных списка
good[0][0] = 1
print(good)
Запустите 👣 Пошагово — в bad все три элемента указывают на один объект.
Распаковка и полезные функции
first, *middle, last = [1, 2, 3, 4, 5]
print(first, middle, last)
nums = [3, 8, 1]
print(list(reversed(nums)), list(enumerate(nums)), any(x > 5 for x in nums), all(x > 0 for x in nums))
print([1, 2] + [3], [0] * 3, [1, 2] == [1, 2], [1, 2] < [1, 3])
Как устроен список и сложность операций
Список хранит массив указателей на объекты и резервирует место с запасом. Когда место кончается, выделяется массив побольше (примерно в 1,125 раза + константа) и указатели копируются. Поэтому append — амортизированно O(1).
import sys
a = []
prev = sys.getsizeof(a)
for i in range(20):
a.append(i)
size = sys.getsizeof(a)
if size != prev:
print(f"len={len(a):2} размер={size} байт (выделено место с запасом)")
prev = size
| Операция | Сложность |
|---|---|
a[i], a[i] = x, len(a) |
O(1) |
append, pop() |
O(1) амортизированно |
insert(0, x), pop(0), del a[0] |
O(n) |
x in a, index, count, remove |
O(n) |
sort |
O(n log n) |
срез a[i:j] |
O(j − i) |
Итог
- Список — изменяемый динамический массив ссылок.
append/extend/insert,pop/remove/del; изменяющие методы возвращаютNone.sorted()— новый список,.sort()— на месте.b = a— не копия;a[:],a.copy()— поверхностная копия.- Двумерный список:
[[0] * m for _ in range(n)], не[[0] * m] * n. - Операции в конце — O(1), в начале — O(n); для очереди —
deque.
Практика решено 0 из 6
a = [10, 20, 30, 40, 50]. Что вернёт выражение (список a каждый раз исходный)?
| [20, 30] | 50 | [50, 40, 30, 20, 10] | [10, 30, 50] | 2 | |
|---|---|---|---|---|---|
a[1:3] |
|||||
a[-1] |
|||||
a[::-1] |
|||||
a[::2] |
|||||
a.index(30) |
|||||
a.pop() |
Чем append отличается от extend?
a = [1, 2]
a.append([3, 4])
b = [1, 2]
b.extend([3, 4])
Напишите функцию remove_duplicates(items), которая возвращает новый список без повторов,
сохраняя порядок первого появления. Исходный список не изменять.
Какие тесты проверяют код (4)
assert remove_duplicates([3, 1, 3, 2, 1]) == [3, 1, 2]
assert remove_duplicates([]) == []
data = [1, 1, 2]; remove_duplicates(data); assert data == [1, 1, 2], "Исходный список изменился"
assert remove_duplicates(list("abracadabra")) == ["a", "b", "r", "c", "d"]
Можно также list(dict.fromkeys(items)) — словари сохраняют порядок вставки.
def remove_duplicates(items):
seen = set()
result = []
for x in items:
if x not in seen:
seen.add(x)
result.append(x)
return result
Что выведет код?
grid = [[0] * 3] * 3
grid[0][0] = 1
print(grid)
Напишите функцию rotate(nums, k), которая возвращает новый список, циклически сдвинутый
вправо на k позиций. rotate([1, 2, 3, 4, 5], 2) → [4, 5, 1, 2, 3]. k может быть больше длины.
Какие тесты проверяют код (4)
assert rotate([1, 2, 3, 4, 5], 2) == [4, 5, 1, 2, 3] assert rotate([1, 2, 3], 0) == [1, 2, 3] assert rotate([1, 2, 3], 4) == [3, 1, 2] assert rotate([], 3) == []
def rotate(nums, k):
if not nums:
return []
k %= len(nums)
return nums[-k:] + nums[:-k] if k else nums[:]
Какая сложность у операции со списком из n элементов?
| O(1) | O(n) | |
|---|---|---|
a.append(x) |
||
a.pop() |
||
a.pop(0) |
||
a.insert(0, x) |
||
a[i] |
||
x in a |
Войдите, чтобы отмечать пройденные уроки.