Python 3. Списки

В этой статье я разбираю списки в Python 3. Вы узнаете, какими свойствами обладают списки, как работают индексы и срезы, как добавлять, удалять, искать, сортировать и копировать элементы.

Свойства, индексация и срезы списков

Я уже писал про последовательности и множества в Python 3. Список (list) — это изменяемая упорядоченная последовательность объектов. Давайте разберём это определение по частям:

  • изменяемая — после создания список можно менять: добавлять, удалять и заменять элементы;
  • упорядоченная — элементы хранятся в том порядке, в котором мы их добавили, и у каждого есть свой номер (индекс);
  • последовательность объектов — в списке можно хранить объекты любых типов, причём в одном списке могут лежать объекты разных типов.

Списки в Python 3 записываются в квадратных скобках, элементы разделяются запятыми. Пустой список можно создать двумя способами: квадратными скобками или функцией list(). Функция list() ещё умеет превращать другие последовательности в список.

Код:

empty_1 = []
empty_2 = list()
letters = list('abc')      # из строки
numbers = list(range(5))   # из диапазона
print(empty_1, empty_2)
print(letters)
print(numbers)

Исполнение:

[] []
['a', 'b', 'c']
[0, 1, 2, 3, 4]

Python — язык с динамической типизацией: тип определяется у объекта, а не у переменной, и проверяется только в момент выполнения кода. Поэтому, если, например, мы захотим из каждого элемента списка отнять 1, а там окажется строка, то ошибка появится не заранее, а только когда программа дойдёт до этой строки.

Индексация

Список, так же как и строка, нумерует свои элементы. Нумерация идёт с нуля. Кроме того, можно считать с конца: последний элемент имеет индекс -1, предпоследний -2 и так далее:

Индексация позволяет обращаться к объектам в списке по индексу (положительному или отрицательному), а также создавать срезы.

Получение некоторых свойств списка и обращение по индексам показано в этом примере:

my_list = [1, 2, 3, 4, 'пять']
print('Тип объекта -', type(my_list))
print('Длина списка -', len(my_list))
print('Первый элемент -', my_list[0])        # Обращение по индексу
print('Последний элемент -', my_list[-1])    # Обращение по отрицательному индексу

Исполнение:

Тип объекта - <class 'list'>
Длина списка - 5
Первый элемент - 1
Последний элемент - пять

Если обратиться к индексу, которого нет в списке, то Python выдаст ошибку IndexError: list index out of range. Запомните: в списке из 5 элементов последний положительный индекс — это 4, а не 5.

Срезы

Срез позволяет получить из списка сразу несколько элементов. Записывается он так: список[старт:стоп:шаг]. Запомните, что элемент с индексом старт входит в срез, а элемент с индексом стоп — не входит. Шаг указывать необязательно, по умолчанию он равен 1. Если не указать старт, то срез начнётся с начала списка, а если не указать стоп — то закончится в конце.

Код:

my_list = [1, 2, 3, 4, 'пять']
print('Срез [1:4] - ', my_list[1:4])
print('Срез [0:5:2] - ', my_list[0:5:2])
print('Срез [:2] - ', my_list[:2])
print('Срез [3:] - ', my_list[3:])
print('Срез [-2:] - ', my_list[-2:])
print('Срез [::-1] - ', my_list[::-1])    # Список в обратном порядке

Исполнение:

Срез [1:4] -  [2, 3, 4]
Срез [0:5:2] -  [1, 3, 'пять']
Срез [:2] -  [1, 2]
Срез [3:] -  [4, 'пять']
Срез [-2:] -  [4, 'пять']
Срез [::-1] -  ['пять', 4, 3, 2, 1]

Срез всегда создаёт новый список, а исходный остаётся без изменений. В отличие от обращения по одному индексу, срез не выдаёт ошибку, если вышел за границы списка, он просто вернёт то, что есть.

Изменяемость списков

Так как список — это изменяемый тип данных, то мы можем изменить его, не создавая новый объект. Давайте это проверим с помощью функции id(), которая возвращает идентификатор объекта в памяти.

Код:

my_list = [1, 2, 3, 4, 'пять']
print(my_list, ' id =', id(my_list))
my_list[4] = 5
print(my_list, ' id =', id(my_list))

Исполнение:

[1, 2, 3, 4, 'пять']  id = 2308488126592
[1, 2, 3, 4, 5]  id = 2308488126592

Из кода видно, что мы изменили список, при этом объект остался прежним (id не изменился). Конкретное число у вас будет другим, это нормально.

Для сравнения: строки и кортежи изменять нельзя, поэтому у них такой фокус не получится.

Работа со списками в Python

Изменение элементов списка

Чтобы изменить элемент, нужно присвоить новое значение по его индексу.

Код:

my_list = [1, 2, 3, 4, 'пять']
my_list[4] = 5
print(my_list)

Исполнение:

[1, 2, 3, 4, 5]

Также можно заменить сразу несколько элементов с помощью среза. При этом количество новых элементов может не совпадать с количеством старых, тогда список сам станет длиннее или короче.

Код:

my_list = [1, 2, 3, 4, 5]
my_list[1:3] = [20, 30, 40]   # Заменили элементы с индексами 1 и 2 на три новых
print(my_list)

Исполнение:

[1, 20, 30, 40, 4, 5]

Добавление элемента в список

Мы можем добавить элемент в конец списка с помощью метода append. Или добавить элемент на определённую позицию с помощью метода insert (при этом элементы сдвигаются вправо).

Код:

my_list = [1, 2, 3, 4, 'пять']
my_list.append('шесть')   # Добавили элемент в конец списка
print(my_list)
my_list.insert(1, 'два')  # Добавили элемент на позицию с индексом 1
print(my_list)

Исполнение:

[1, 2, 3, 4, 'пять', 'шесть']
[1, 'два', 2, 3, 4, 'пять', 'шесть']

Обратите внимание, что в insert первым аргументом передаётся индекс, на который встанет новый элемент, а вторым — сам элемент.

Расширение списка другим списком

Можно расширить список другим списком с помощью метода extend.

Код:

my_list1 = [1, 2, 3]
my_list2 = [4, 5, 6]
my_list1.extend(my_list2)
print(my_list1)

Исполнение:

[1, 2, 3, 4, 5, 6]

Новички часто путают append и extend. Разница в том, что append добавляет один объект как есть, а extend берёт каждый элемент из переданной последовательности и добавляет их по одному.

Код:

my_list = [1, 2, 3]
my_list.append([4, 5])   # Добавили один элемент - список
print(my_list)

my_list = [1, 2, 3]
my_list.extend([4, 5])   # Добавили два элемента
print(my_list)

Исполнение:

[1, 2, 3, [4, 5]]
[1, 2, 3, 4, 5]

В первом случае мы получили список внутри списка. Это допустимо, такие списки называют вложенными, но обычно это не то, чего мы хотели.

Если нужно объединить два списка, то можно воспользоваться оператором +. Он создаёт новый список, а исходные не меняет. Оператор += работает как extend. Кроме того, список можно умножить на число, тогда он повторится указанное количество раз.

Код:

a = [1, 2]
b = [3, 4]
print(a + b)
print(a)
a += b          # то же самое, что a.extend(b)
print(a)
print([0] * 5)

Исполнение:

[1, 2, 3, 4]
[1, 2]
[1, 2, 3, 4]
[0, 0, 0, 0, 0]

Удаление элемента из списка

Удалить элемент из списка можно несколькими способами:

  • remove(значение) — удаляет элемент по его значению, причём только первый найденный;
  • pop(индекс) — удаляет элемент по индексу и возвращает его. Если индекс не указать, то удалится последний элемент;
  • del список[индекс] — удаляет элемент по индексу, в том числе можно удалить срез.

Код:

my_list = [1, 2, 3, 4, 5, 6]
my_list.remove(6)           # Удалили элемент со значением 6
removed = my_list.pop(0)    # Удалили элемент с индексом 0 и сохранили его в переменную
print(my_list)
print('Удалённый элемент -', removed)
print('Последний элемент -', my_list.pop())  # Удалили и вывели последний элемент
print(my_list)

Исполнение:

[2, 3, 4, 5]
Удалённый элемент - 1
Последний элемент - 5
[2, 3, 4]

Если в списке несколько одинаковых значений, то remove удалит только первое из них.

Код:

my_list = [1, 2, 2, 3, 2]
my_list.remove(2)
print(my_list)

Исполнение:

[1, 2, 3, 2]

А с помощью del можно удалить сразу несколько элементов, указав срез.

Код:

my_list = [10, 20, 30, 40, 50]
del my_list[1:3]   # Удалили элементы с индексами 1 и 2
print(my_list)

Исполнение:

[10, 40, 50]

Будьте внимательны: если указать значение, которого нет в списке, то remove выдаст ошибку ValueError. А pop для пустого списка выдаст IndexError. Поэтому перед удалением по значению полезно проверять, есть ли элемент в списке (как это сделать, я покажу ниже).

Очистка списка

Также можно очистить список полностью с помощью метода clear.

Код:

my_list = [1, 2, 3, 4, 5, 6]
my_list.clear()
print(my_list)

Исполнение:

[]

Проверяем, содержится ли элемент в списке

Проверить, содержится ли элемент в списке, можно с помощью оператора in. Он возвращает True или False. Противоположный оператор — not in.

Код:

my_list = [1, 3, 5, 7]
print(1 in my_list)
print(2 in my_list)
print(2 not in my_list)

Исполнение:

True
False
True

Поиск индекса элемента

Если нужно узнать, на какой позиции стоит элемент, то используйте метод index. Он возвращает индекс первого найденного элемента. Если элемента нет в списке, то будет ошибка ValueError.

Код:

my_list = [1, 3, 2, 5, 4, 6, 2, 1, 2, 1, 7, 1]
print(my_list.index(2))      # Первая двойка
print(my_list.index(2, 3))   # Поиск двойки, начиная с индекса 3

Исполнение:

2
6

Сколько раз элемент встречается в списке

Посчитать, сколько раз элемент встречается в списке, можно с помощью метода count.

Код:

my_list = [1, 3, 2, 5, 4, 6, 2, 1, 2, 1, 7, 1]
print(my_list.count(2))

Исполнение:

3

Сортировка списка

Отсортировать список с однотипными данными можно с помощью метода sort. Важно, что этот метод меняет сам список и ничего не возвращает (точнее, возвращает None). Поэтому записывать результат в переменную не нужно. А чтобы отсортировать в обратном порядке, нужно указать параметр reverse=True.

Код:

my_list = [1, 3, 2, 5, 4, 6]
my_list.sort()
print(my_list)
my_list.sort(reverse=True)
print(my_list)

Исполнение:

[1, 2, 3, 4, 5, 6]
[6, 5, 4, 3, 2, 1]

Если нужно сохранить исходный список, а отсортированный получить в виде нового, то используйте встроенную функцию sorted. Она не меняет исходный список, а возвращает новый.

Код:

my_list = [3, 1, 2]
new_list = sorted(my_list)
print(my_list)
print(new_list)

Исполнение:

[3, 1, 2]
[1, 2, 3]

Со строками нужно быть аккуратнее: Python сравнивает их по кодам символов, поэтому все слова с заглавной буквы окажутся перед словами со строчной. Чтобы этого избежать, передайте в параметр key функцию, по результату которой будет идти сортировка. Например, str.lower приведёт слова к нижнему регистру на время сравнения, а сами слова при этом не изменятся. А key=len отсортирует слова по длине.

Код

words = ['banana', 'Apple', 'cherry']
words.sort()
print(words)

words = ['banana', 'Cherry', 'apple']
words.sort(key=str.lower)
print(words)

words = ['kiwi', 'banana', 'fig']
words.sort(key=len)
print(words)

Исполнение:

['Apple', 'banana', 'cherry']
['apple', 'banana', 'Cherry']
['fig', 'kiwi', 'banana']

Теперь вернёмся к списку, с которого мы начали статью: [1, 2, 3, 4, 'пять']. Если попытаться отсортировать список с числами и строками, то получится ошибка, потому что Python не знает, как сравнить число со строкой.

Код:

my_list = [3, 'a']
my_list.sort()

Исполнение:

TypeError: '<' not supported between instances of 'str' and 'int'

Поэтому сортировать можно только списки, элементы которых можно сравнивать между собой.

Сортировка списка кортежей

Более сложный вариант сортировки — это сортировка списка кортежей.

Если сортировать список кортежей с помощью метода sort() без дополнительных параметров, то сортировка будет происходить по первому элементу кортежей. Если встретятся два одинаковых первых элемента, то Python сравнит вторые элементы, потом третьи и так далее.

Чтобы отсортировать список не по первому элементу кортежа, нужно передать в параметр key функцию, которая возвращает значение для сравнения. Проще всего написать её с помощью lambda. Запись key=lambda i: i[1] означает, что сортировка будет по элементу кортежа с индексом 1, то есть по второму элементу (индексы начинаются с нуля). Вы можете задать любой индекс, главное, чтобы элементы по этому индексу можно было сравнивать между собой.

my_list = [(23, 18, 53), (18, 51, 48), (27, 38, 24), (15, 12, 33)]

# Вывод без сортировки
print(my_list) # [(23, 18, 53), (18, 51, 48), (27, 38, 24), (15, 12, 33)]

# Сортировка по первому элементу
my_list.sort()
print(my_list) # [(15, 12, 33), (18, 51, 48), (23, 18, 53), (27, 38, 24)]

# Сортировка по второму элементу
my_list.sort(key=lambda i: i[1])
print(my_list) # [(15, 12, 33), (23, 18, 53), (27, 38, 24), (18, 51, 48)]

# Сортировка по третьему элементу
my_list.sort(key=lambda i: i[2])
print(my_list) # [(27, 38, 24), (15, 12, 33), (18, 51, 48), (23, 18, 53)]

Вместо lambda можно использовать функцию itemgetter из стандартного модуля operator. Она делает то же самое, но читается проще, а ещё немного быстрее. Параметр reverse=True работает и здесь:

from operator import itemgetter

my_list = [(23, 18, 53), (18, 51, 48), (27, 38, 24), (15, 12, 33)]
my_list.sort(key=itemgetter(2), reverse=True)  # По третьему элементу, от большего к меньшему
print(my_list) # [(23, 18, 53), (18, 51, 48), (15, 12, 33), (27, 38, 24)]

Ещё одна полезная особенность: сортировка в Python устойчивая. Это значит, что элементы с одинаковым ключом сохраняют тот порядок, в котором они были до сортировки.

Разворот списка и простые вычисления

Развернуть список на месте можно методом reverse. Он, как и sort, меняет сам список. Если нужен новый развёрнутый список, то используйте срез [::-1], который мы разбирали выше.

Для списков чисел есть удобные встроенные функции: min — минимум, max — максимум, sum — сумма.

Код:

my_list = [1, 2, 3]
my_list.reverse()
print(my_list)
print('Минимум -', min(my_list))
print('Максимум -', max(my_list))
print('Сумма -', sum(my_list))

Исполнение:

[3, 2, 1]
Минимум - 1
Максимум - 3
Сумма - 6

Перебор элементов списка

Чаще всего со списком нужно проделать что-то для каждого элемента. Для этого используют цикл for.

Код:

servers = ['web-01', 'db-01', 'backup-01']
for server in servers:
    print('Проверяю', server)

Исполнение:

Проверяю web-01
Проверяю db-01
Проверяю backup-01

Если в цикле нужен ещё и индекс, то используйте функцию enumerate.

Код:

servers = ['web-01', 'db-01', 'backup-01']
for i, server in enumerate(servers):
    print(i, server)

Исполнение:

0 web-01
1 db-01
2 backup-01

Новый список из старого можно создать коротко, с помощью списочного выражения (list comprehension). Читается оно почти как обычное предложение: «для каждого x из диапазона взять x умножить на 2»:

doubled = [x * 2 for x in range(5)]
print(doubled) # [0, 2, 4, 6, 8]

Подробно циклы и списочные выражения я разберу в отдельных статьях.

Списки и строки: split и join

Со списками часто связаны две строковые операции. Метод строки split разбивает строку на список, а метод join собирает список строк обратно в одну строку.

Код:

line = 'a b c'
parts = line.split()           # Разбиваем по пробелам
print(parts)
print(', '.join(parts))        # Склеиваем через запятую с пробелом

Исполнение:

['a', 'b', 'c']
a, b, c

Но join работает только со строками. Поэтому, если у вас список чисел, то их нужно преобразовать в строки.

Код:

numbers = [1, 2, 3]
strings = []                  # Сюда будем складывать строки
for n in numbers:
    strings.append(str(n))    # Превращаем каждое число в строку
print(strings)
print(', '.join(strings))     # Склеиваем через запятую с пробелом

Исполнение:

['1', '2', '3']
1, 2, 3

Тоже самое можно сделать с помощью map:

numbers = [1, 2, 3]
print(', '.join(map(str, numbers))) # map(str, numbers) - каждый элемент списка numbers преобразит в str

Исполнение:

1, 2, 3
  • map(str, numbers) делает то же, что цикл выше: вызывает str для каждого элемента. Только новый список мы не создаём, join забирает строки прямо из map.

Копирование списка

Так как список относится к изменяемым типам данных, то при присваивании существующего списка новой переменной копирования не происходит. Новая переменная просто ссылается на старый список. Может случиться такая ситуация: мы думаем, что изменяем новый список, а на самом деле изменяем и старый тоже (потому что это один и тот же список).

Код:

my_list_1 = [1, 3, 5, 7]
my_list_2 = my_list_1
my_list_2.append(9)
print(my_list_1)

Исполнение:

[1, 3, 5, 7, 9]

Поэтому список нужно копировать с помощью метода copy.

Код:

my_list_1 = [1, 3, 5, 7]
my_list_2 = my_list_1.copy()
my_list_2.append(9)
print(my_list_1)
print(my_list_2)

Исполнение:

[1, 3, 5, 7]
[1, 3, 5, 7, 9]

Есть и другие способы получить копию: срез my_list_1[:] и функция list(my_list_1). Результат у них такой же.

Проверить, один и тот же это объект или нет, можно оператором is, а сравнить содержимое — оператором ==.

Код:

a = [1, 3]
b = a          # Ссылка на тот же список
c = a.copy()   # Копия
print(b is a)  # Один и тот же объект?
print(c is a)
print(c == a)  # Одинаковое ли содержимое?

Исполнение:

True
False
True

Но и у copy есть подвох: он делает поверхностную копию. Если внутри списка лежат другие списки, то копируются только ссылки на них. В итоге вложенные списки остаются общими у оригинала и копии.

Код:

matrix = [[1, 2], [3, 4]]
copy_matrix = matrix.copy()
copy_matrix[0].append(99)

print(matrix)
print(copy_matrix)

Исполнение:

[[1, 2, 99], [3, 4]]
[[1, 2, 99], [3, 4]]

Чтобы скопировать список вместе со всеми вложенными объектами, используйте функцию deepcopy() из модуля copy.

Код:

import copy

matrix = [[1, 2], [3, 4]]
deep_matrix = copy.deepcopy(matrix)
deep_matrix[0].append(99)

print(matrix)
print(deep_matrix)

Исполнение:

[[1, 2], [3, 4]]
[[1, 2, 99], [3, 4]]

Эта же особенность проявляется при умножении списка. Если создать «таблицу» так: [[0] * 3] * 3, то внешний список будет содержать три ссылки на один и тот же вложенный список. Поэтому, если изменить элемент в одном из вложенных списков, он изменится во всех трёх. Правильно создавать вложенные списки с помощью списочного выражения, тогда каждый вложенный список будет отдельным объектом.

Код:

bad = [[0] * 3] * 3
print(bad)

bad[0][0] = 1
print(bad)

good = [[0] * 3 for _ in range(3)]
print(good)

good[0][0] = 1
print(good)

Исполнение:

[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[1, 0, 0], [1, 0, 0], [1, 0, 0]]
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]
[[1, 0, 0], [0, 0, 0], [0, 0, 0]]

Итоги

Подведём итог. Список — это изменяемая упорядоченная последовательность, в которой можно хранить объекты любых типов. Запомните главное:

  • индексы начинаются с нуля, а отрицательные индексы считают с конца;
  • срез [старт:стоп:шаг] не включает элемент стоп и создаёт новый список;
  • методы append, insert, extend, remove, pop, clear, sort, reverse меняют сам список;
  • sorted и срезы возвращают новый список, а исходный не трогают;
  • присваивание b = a не копирует список, для копии используйте copy(), а для вложенных списков — deepcopy().

Все примеры из статьи я проверил на Python 3.13, в более новых версиях Python 3 они работают так же.


Если понравилась статья, подпишись на мой канал в VK или Telegram .