Операции со списками в Python на примерах
Операции со списком в Python - это действия, которые выполняются операторами и встроенными функциями без вызова методов: обращение по индексу, срезы, сложение, повторение, проверка вхождения, сравнение. В статье каждая операция показана на коротком примере с результатом, а также разобраны частые практические задачи: перебор с индексом, получение последнего элемента, склейка и очистка от повторов.

Содержание статьи
Индексы и перебор с номером элемента
Элементы списка нумеруются с нуля, обращение идет через квадратные скобки. Отрицательный индекс отсчитывает с конца: -1 означает последний элемент, -2 предпоследний. Индекс за пределами списка вызывает IndexError.
nums = [21, 44, 35, 11]
print(nums[0], nums[-1]) # 21 11
nums[1] = 50 # список изменяемый
print(nums) # [21, 50, 35, 11]Когда при переборе нужен и номер, и значение, используют функцию enumerate(). Она выдает пары (индекс, элемент), а параметр start задает начальный номер, если нумерация нужна не с нуля.
nums = [21, 44, 35, 11]
for i, val in enumerate(nums, start=1):
print(i, val)
# 1 21
# 2 44
# 3 35
# 4 11Вариант с range(len(nums)) тоже работает, но читается хуже и требует лишнего обращения по индексу внутри цикла.
Срезы
Срез возвращает новый список из части элементов. Синтаксис список[start:stop:step]: start включается, stop нет, step задает шаг. Любую из трех частей можно пропустить, тогда берутся значения по умолчанию: начало, конец и шаг 1.
a = [1, 2, 3, 4, 5]
print(a[2:]) # [3, 4, 5] с индекса 2 до конца
print(a[:2]) # [1, 2] до индекса 2, не включая его
print(a[1:4]) # [2, 3, 4]
print(a[::2]) # [1, 3, 5] каждый второй
print(a[::-1]) # [5, 4, 3, 2, 1] в обратном порядке
print(a[-2:]) # [4, 5] два последнихВ отличие от индекса, срез не вызывает ошибку при выходе за границы: a[3:100] просто вернет [4, 5]. Срезу можно и присвоить значение, тогда часть списка заменится: a[1:3] = ["x"] превратит список в [1, "x", 4, 5]. Подробно о шаге и отрицательных границах написано в статье про срезы.
Сложение, повторение и распаковка
Оператор + склеивает два списка в новый, исходные не меняются. Оператор * с целым числом повторяет содержимое нужное число раз. Оба работают только между списками: [1] + (2,) вызовет TypeError.
a = [1, "a"]
b = [3, 4, 5]
print(a + b) # [1, 'a', 3, 4, 5]
print([0] * 4) # [0, 0, 0, 0]
print([*a, *range(2, 4)]) # [1, 'a', 2, 3]
a += b # дописывает b в конец a на местеРаспаковка через звездочку внутри квадратных скобок собирает список из любых итерируемых объектов: других списков, кортежей, диапазонов, строк. Это удобнее, чем сначала приводить каждый объект к списку.
С повторением вложенных списков есть ловушка. Выражение [[0] * 3] * 2 создаст две ссылки на один и тот же внутренний список, и изменение одной строки изменит обе. Для матриц пишут генератор: [[0] * 3 for _ in range(2)].
Проверка вхождения, длина и сравнение
Оператор in проверяет, есть ли значение в списке, not in проверяет обратное. Поиск идет перебором слева направо, поэтому на больших списках при частых проверках выгоднее множество.
langs = ["python", "go", "rust"]
print("go" in langs) # True
print("java" not in langs) # True
print(len(langs)) # 3
print(min([4, 1, 7]), max([4, 1, 7]), sum([4, 1, 7])) # 1 7 12Списки сравниваются поэлементно, как слова в словаре: первая пара различающихся элементов определяет результат. Оператор == проверяет равенство содержимого, а is проверяет, что это один и тот же объект в памяти.
print([1, 2, 3] < [1, 3]) # True, 2 < 3
x = [1, 2]
y = [1, 2]
print(x == y, x is y) # True FalseУдаление элементов и дубликатов
Инструкция del удаляет элемент по индексу или целый срез. В отличие от метода pop(), она ничего не возвращает. Остальные способы удаления, включая remove() и clear(), описаны в обзоре методов списка.
a = [10, 20, 30, 40, 50]
del a[0] # [20, 30, 40, 50]
del a[1:3] # [20, 50]Чтобы убрать повторы, список превращают во множество и обратно. Способ короткий, но порядок элементов после set() не гарантируется. Если порядок важен, используют dict.fromkeys(): ключи словаря уникальны и хранятся в порядке добавления.
data = [3, 1, 3, 2, 1]
print(list(set(data))) # порядок может быть любым
print(list(dict.fromkeys(data))) # [3, 1, 2]
a, b = [1, 2, 4, 6], [7, 8, 2, 1]
print(sorted(set(a) | set(b))) # объединение без повторов: [1, 2, 4, 6, 7, 8]
print(sorted(set(a) ^ set(b))) # только в одном из списков: [4, 6, 7, 8]Другие способы, включая удаление повторов в списке словарей, собраны в статье об удалении дубликатов.
Сводная таблица операций
Ниже перечислены операции, которые работают со списком без вызова методов, и их сложность для списка длины n.
| Операция | Пример | Результат | Сложность |
|---|---|---|---|
| Индекс | a[i] | элемент | O(1) |
| Срез | a[i:j] | новый список | O(j-i) |
| Сложение | a + b | новый список | O(n+m) |
| Повторение | a * k | новый список | O(n*k) |
| Вхождение | x in a | bool | O(n) |
| Длина | len(a) | int | O(1) |
| Удаление | del a[i] | изменяет a | O(n) |
Удаление из начала списка дорогое, потому что все последующие элементы сдвигаются. Если элементы часто снимаются с обоих концов, лучше подходит collections.deque.
Частые вопросы
Короткие ответы на вопросы, которые возникают при работе со списками.
Как получить последний элемент списка?
Через отрицательный индекс: a[-1]. Для пустого списка это вызовет IndexError, поэтому перед обращением проверяют if a:.
Чем a + b отличается от a.extend(b)?
a + b создает новый список, а оба исходных остаются прежними. extend() и оператор += меняют список a на месте, это быстрее и не требует лишней памяти.
Как скопировать список, чтобы изменения не затронули оригинал?
Срез a[:], a.copy() или list(a) создают поверхностную копию. Для вложенных списков нужен copy.deepcopy(), иначе внутренние списки останутся общими.
Как развернуть список?
Срез a[::-1] вернет новый перевернутый список, метод a.reverse() развернет существующий на месте, а reversed(a) даст итератор для перебора в обратном порядке.