Список хранит элементы по порядку и разрешает менять себя на месте. Обращение идёт по номеру позиции, нумерация начинается с нуля. Хранить в одном списке можно что угодно, включая другие списки. Весь код выполнен на CPython 3.14.5.
append кладёт один объект. extend — его элементы
append никогда не раскрывает переданный список: это один новый элемент. extend берёт элементы из переданной последовательности по одному.Как обратиться к элементу по индексу
Квадратные скобки и номер позиции. Первый элемент под нулём, последний под len(...) - 1:
cities = ["Москва", "Казань", "Пермь", "Сочи"]
print(cities[0], cities[2], len(cities))
Москва Пермь 4
Отрицательные индексы считают с конца, и это короче, чем cities[len(cities) - 1]:
cities = ["Москва", "Казань", "Пермь", "Сочи"]
print(cities[-1], cities[-2])
Сочи Пермь
Выход за границу роняет программу:
cities = ["Москва", "Казань", "Пермь", "Сочи"]
try:
print(cities[4])
except IndexError as exc:
print(f"IndexError: {exc}")
IndexError: list index out of range
Четвёртого индекса нет: у четырёх элементов номера от 0 до 3. Это самая частая ошибка «на единицу», и лечится она не проверкой длины перед каждым обращением, а перебором циклом без индексов вовсе.
Срезы: взять часть списка
Срез [начало:конец] берёт элементы от первого номера до второго, не включая второй:
cities = ["Москва", "Казань", "Пермь", "Сочи"]
print(cities[1:3])
print(cities[:2])
print(cities[2:])
print(cities[::2])
print(cities[::-1])
['Казань', 'Пермь']
['Москва', 'Казань']
['Пермь', 'Сочи']
['Москва', 'Пермь']
['Сочи', 'Пермь', 'Казань', 'Москва']
Пропущенное начало означает «с самого начала», пропущенный конец — «до самого конца». Третье число задаёт шаг, а шаг -1 разворачивает список.
Срез, в отличие от обращения по индексу, за границей не падает:
cities = ["Москва", "Казань", "Пермь", "Сочи"]
print(cities[1:100], cities[10:20])
['Казань', 'Пермь', 'Сочи'] []
Второй ответ пустой список, а не ошибка. Свойство удобное, но обоюдоострое: опечатка в границах не упадёт, а тихо вернёт пустоту.
Как добавить элемент: append, extend, insert
append добавляет один элемент в конец, extend добавляет элементы другого списка по одному. Разница видна сразу:
a = ["Москва"]
a.append(["Казань", "Пермь"])
print(a)
b = ["Москва"]
b.extend(["Казань", "Пермь"])
print(b)
['Москва', ['Казань', 'Пермь']]
['Москва', 'Казань', 'Пермь']
В первом случае внутри списка оказался ещё один список, и длина стала два, а не три. Ошибка живучая: программа не падает, а данные едут.
insert кладёт элемент на указанную позицию, сдвигая остальные вправо:
c = ["Москва", "Пермь"]
c.insert(1, "Казань")
print(c)
c.insert(0, "Сочи")
print(c)
['Москва', 'Казань', 'Пермь']
['Сочи', 'Москва', 'Казань', 'Пермь']
Вставка в начало дороже вставки в конец: все элементы приходится подвинуть. На больших списках это заметно, и тогда берут collections.deque.
Как удалить: remove, pop, del и clear
Три способа с разным смыслом. remove удаляет по значению и только первое вхождение:
d = ["Москва", "Казань", "Москва"]
d.remove("Москва")
print(d)
try:
d.remove("Тула")
except ValueError as exc:
print(f"ValueError: {exc}")
['Казань', 'Москва']
ValueError: list.remove(x): x not in list
Второй «Москвы» удаление не коснулось. Чтобы убрать все вхождения, строят новый список включением, а не режут старый в цикле.
pop удаляет по индексу и возвращает удалённое значение:
e = ["Москва", "Казань", "Пермь"]
print(e.pop(), e)
print(e.pop(0), e)
try:
[].pop()
except IndexError as exc:
print(f"IndexError: {exc}")
Пермь ['Москва', 'Казань']
Москва ['Казань']
IndexError: pop from empty list
Без аргумента pop снимает последний элемент, поэтому список легко превращается в стек. del удаляет по индексу или по срезу и ничего не возвращает, а clear опустошает список целиком:
f = ["Москва", "Казань", "Пермь", "Сочи"]
del f[1]
print(f)
del f[0:2]
print(f)
f.clear()
print(f)
['Москва', 'Пермь', 'Сочи']
['Сочи']
[]
Как найти элемент: in, index и count
Оператор in отвечает, есть ли значение, index даёт его позицию, count считает вхождения:
g = ["Москва", "Казань", "Москва"]
print("Казань" in g, "Тула" in g)
print(g.index("Москва"), g.count("Москва"))
try:
g.index("Тула")
except ValueError as exc:
print(f"ValueError: {exc}")
True False
0 2
ValueError: list.index(x): x not in list
index возвращает позицию первого вхождения и падает, если значения нет. Поэтому его почти всегда пишут после проверки через in. Оба перебирают список от начала, так что на больших объёмах поиск дорожает линейно, и если проверка наличия делается часто, данные лучше держать в множестве или словаре.
Как превратить список в строку и обратно
Метод join вызывается у разделителя, а не у списка:
h = ["Москва", "Казань", "Пермь"]
print(", ".join(h))
print(" | ".join(h))
Москва, Казань, Пермь
Москва | Казань | Пермь
Запись ", ".join(...) выглядит непривычно, зато позволяет склеивать любой перебираемый объект, а не только список. Когда значение надо вставить в текст, а не склеить список, берут f-строку. Числа join не принимает:
try:
print(", ".join([1, 2, 3]))
except TypeError as exc:
print(f"TypeError: {exc}")
print(", ".join(str(n) for n in [1, 2, 3]))
TypeError: sequence item 0: expected str instance, int found
1, 2, 3
Сообщение прямо называет позицию и тип. Лечится приведением каждого элемента к строке.
Обратное преобразование делает split, а посимвольный разбор строки — list:
print("Москва, Казань, Пермь".split(", "))
print("а б в".split())
print(list("код"))
['Москва', 'Казань', 'Пермь']
['а', 'б', 'в']
['к', 'о', 'д']
split без аргументов режет по любым пробельным символам и заодно выбрасывает пустые куски, поэтому для текста из файла он удобнее, чем split(" "). Остальные методы строк разобраны отдельно.
Что делать со списком чисел
Встроенные функции считают итоги без всякого цикла:
nums = [5, 3, 9, 1, 9]
print(sum(nums), min(nums), max(nums), len(nums))
print(sum(nums) / len(nums))
27 1 9 5
5.4
Сортировка существует в двух видах, и путают их постоянно:
nums = [5, 3, 9, 1]
print(nums.sort(), nums)
nums2 = [5, 3, 9, 1]
print(sorted(nums2), nums2)
None [1, 3, 5, 9]
[1, 3, 5, 9] [5, 3, 9, 1]
Метод sort() сортирует список на месте и возвращает None, поэтому nums = nums.sort() уничтожает данные. Функция sorted() возвращает новый список и не трогает исходный. Та же пара есть у разворота: reverse() меняет на месте, reversed() возвращает объект для перебора. Как сортировать по своему признаку через key и почему reverse=True не равен срезу — в статье про сортировку.
Копирование: почему оригинал меняется
Присваивание не создаёт копию, оно даёт второе имя тому же списку:
original = ["Москва", "Казань"]
alias = original
alias.append("Пермь")
print(original)
['Москва', 'Казань', 'Пермь']
Добавляли в alias, изменился original, потому что это один объект. Копию делают срезом или методом copy:
original = ["Москва", "Казань"]
copy1 = original[:]
copy2 = original.copy()
copy1.append("Пермь")
copy2.append("Сочи")
print(original, copy1, copy2)
['Москва', 'Казань'] ['Москва', 'Казань', 'Пермь'] ['Москва', 'Казань', 'Сочи']
Обе копии независимы, но только на верхнем уровне. Вложенные списки остаются общими:
nested = [["Москва"], ["Казань"]]
shallow = nested.copy()
shallow[0].append("Зеленоград")
print(nested)
[['Москва', 'Зеленоград'], ['Казань']]
Для полностью независимой копии нужен copy.deepcopy. Откуда берётся эта разница, разобрано в статье про изменяемые и неизменяемые типы.
Ошибки, на которых спотыкаются
Умножение списка со вложенным. Быстрый способ создать таблицу оказывается ловушкой:
grid_bad = [[0] * 3] * 2
grid_bad[0][0] = 1
print(grid_bad)
grid_ok = [[0] * 3 for _ in range(2)]
grid_ok[0][0] = 1
print(grid_ok)
[[1, 0, 0], [1, 0, 0]]
[[1, 0, 0], [0, 0, 0]]
В первом случае единица появилась в обеих строках: умножение скопировало не список, а ссылку на него, и обе строки таблицы — один объект. Включение создаёт новый список на каждой итерации.
Удаление во время перебора. Список меняет длину прямо под циклом, индексы сдвигаются, часть элементов проскакивает. Разбор этой ошибки с выводом лежит в статье про циклы, а готовый приём удаления дубликатов с сохранением порядка — в статье про дедупликацию.
append вместо extend. Один вызов, и вместо трёх элементов в списке оказывается один вложенный список.
Сравнение по содержимому, а не по объекту. Списки сравниваются поэлементно и с учётом порядка:
print([1, 2] == [1, 2], [1, 2] == [2, 1], [1, 2] < [1, 3])
True False True
Последнее сравнение лексикографическое: первые элементы равны, решает второй.
Частые вопросы
Как удалить элемент по значению
list.remove(значение) убирает первое вхождение и падает с ValueError, если значения нет. Когда падение нежелательно, сначала проверяют через in.
Как получить последний элемент списка
items[-1]. На пустом списке это IndexError, поэтому в коде, где список может оказаться пустым, пишут items[-1] if items else None.
Как выбрать из списка только подходящие элементы
Включением с условием: [x for x in items if x > 0]. Подробности и границы применимости в статье про списковые включения.
Чем список отличается от кортежа
Список изменяемый, кортеж нет. Кортеж годится в ключи словаря, только если все его элементы хешируемы: например, (1, 2) подходит, а ([1], 2) — нет. Сам список не хешируется.
Как посчитать сумму или среднее
sum(nums) и sum(nums) / len(nums). Для пустого списка деление упадёт с ZeroDivisionError, так что длину проверяют заранее.
Где потренироваться
В пути «Python с нуля» на Koddo под списки отведён отдельный пак: сначала выборка по условию, потом накопление результата, потом работа со срезами. Проверить себя на живой задаче можно в дедупликации событий, где нужно вернуть новый список и не тронуть исходный.