Методы списков в Python: добавление, сортировка, удаление и копирование
Обновлено:
Список — изменяемый объект, поэтому многие методы класса list работают с самим существующим списком, а не создают новый.
В этой теме разберём:
count()
index()
append()
insert()
extend()
reverse()
sort()
pop()
remove()
clear()
copy()
count()
1. Подсчёт элементов
С методом:
count()
мы уже знакомы по строкам.
У списка count() считает, сколько элементов списка равны переданному объекту.
numbers = [
10,
20,
10,
30,
10,
]
result = numbers.count(10)
print(result)
Результат:
3
Если подходящих элементов нет:
print(numbers.count(100))
Результат:
0
Метод всегда возвращает:
int
2. count() сравнивает элементы целиком
У строк:
text.count('Py')
можно искать подстроку внутри строки.
В списке поиск устроен иначе:
technologies = [
'Python',
'Python backend',
'SQL',
]
print(
technologies.count('Python')
)
Результат:
1
Строка:
Python
внутри:
Python backend
не считается отдельным элементом списка.
Метод сравнивает объект, который мы передали, с отдельными элементами списка.
3. У list.count() нет start и stop
У строкового метода count() мы могли ограничивать область поиска:
text.count(
'Python',
start,
end,
)
У списка метод проще:
list.count(value)
Передаётся только искомый объект.
index()
4. Поиск индекса элемента
Метод:
index()
возвращает индекс первого подходящего элемента.
numbers = [
10,
20,
30,
20,
40,
]
index = numbers.index(20)
print(index)
Результат:
1
Хотя 20 встречается дважды, возвращается индекс первого совпадения.
5. Можно указать start и stop
Например:
numbers = [
10,
20,
30,
20,
40,
]
print(
numbers.index(
20,
2,
)
)
Поиск начинается с индекса 2.
Результат:
3
Можно указать обе границы:
numbers.index(
20,
start,
stop,
)
stop, как и в срезах, является невключительной правой границей.
6. Если элемента нет
numbers = [
10,
20,
30,
]
numbers.index(100)
возникнет:
ValueError
То есть list.index() ведёт себя в этом отношении так же, как строковый:
str.index()
7. У списков нет find() и rfind()
У строк мы изучали:
find()
rfind()
index()
rindex()
У списка существуют:
index()
но нет строковых методов:
find()
rfind()
rindex()
Если нужно найти первый индекс элемента списка, используем:
list.index()
append()
8. Добавление одного объекта в конец
Метод:
append()
добавляет ровно один объект в конец списка.
technologies = [
'Python',
'SQL',
]
technologies.append('Django')
print(technologies)
Результат:
['Python', 'SQL', 'Django']
9. Добавить можно любой объект
Например:
data = [
'Python',
'SQL',
]
data.append(100)
data.append(None)
print(data)
Результат:
['Python', 'SQL', 100, None]
Можно добавить даже другой список:
numbers = [
1,
2,
]
numbers.append(
[3, 4]
)
print(numbers)
Результат:
[1, 2, [3, 4]]
Добавился один объект — вложенный список.
append() изменяет существующий список
10. id() остаётся прежним
numbers = [
10,
20,
]
print(id(numbers))
numbers.append(30)
print(id(numbers))
Идентификатор списка остаётся тем же.
Мы изменили:
[10, 20]
на:
[10, 20, 30]
но объект list остался прежним.
insert()
11. Вставка по указанному индексу
Метод:
insert()
принимает:
list.insert(index, object)
Например:
technologies = [
'Python',
'Django',
]
technologies.insert(
1,
'SQL',
)
print(technologies)
Результат:
['Python', 'SQL', 'Django']
Новый объект вставился под индексом 1.
Существующие элементы справа были сдвинуты.
12. Вставка в начало
numbers = [
20,
30,
]
numbers.insert(
0,
10,
)
print(numbers)
Результат:
[10, 20, 30]
13. Большой индекс не вызывает IndexError
numbers = [
10,
20,
30,
]
numbers.insert(
1000,
40,
)
print(numbers)
Результат:
[10, 20, 30, 40]
Если положительный индекс находится значительно правее конца списка, элемент просто вставляется в конец.
А очень маленький отрицательный индекс будет ограничен началом списка.
append() и insert()
14. Стоимость операций
Вспоминаем устройство списка CPython как динамического массива ссылок.
При:
numbers.append(value)
элемент добавляется в конец.
Если во внутреннем массиве есть резервная вместимость, существующие элементы перемещать не требуется.
Поэтому append() имеет:
amortized O(1)
При:
numbers.insert(
0,
value,
)
нужно сдвинуть существующие элементы:
до:
[A][B][C][D]
после:
[X][A][B][C][D]
Поэтому вставка в начало:
O(n)
Чем ближе позиция вставки к концу списка, тем меньше элементов приходится сдвигать.
15. Небольшой эксперимент
Можно сравнить работу с концом и началом большого списка:
from timeit import timeit
numbers = list(range(100_000))
def work_with_end():
numbers.append(999)
numbers.pop()
def work_with_start():
numbers.insert(0, 999)
del numbers[0]
print(
timeit(
work_with_end,
number=10_000,
)
)
print(
timeit(
work_with_start,
number=10_000,
)
)
Конкретные числа зависят от компьютера, версии Python и размера списка.
Главное здесь не конкретное количество секунд, а разница алгоритмов:
конец списка → обычно O(1)
начало списка → O(n)
extend()
16. Добавление элементов из итерируемого объекта
Метод:
extend()
тоже добавляет элементы в конец списка.
Но он принимает итерируемый объект и добавляет его элементы по одному.
first_numbers = [
10,
20,
]
second_numbers = [
30,
40,
]
first_numbers.extend(
second_numbers
)
print(first_numbers)
Результат:
[10, 20, 30, 40]
append() и extend()
17. Главное различие
numbers = [
1,
2,
]
numbers.append(
[3, 4]
)
Результат:
[1, 2, [3, 4]]
Добавили один объект.
А:
numbers = [
1,
2,
]
numbers.extend(
[3, 4]
)
Результат:
[1, 2, 3, 4]
extend() обошёл переданный список и добавил его элементы.
18. Кортеж
numbers = [
1,
2,
]
numbers.extend(
(3, 4, 5)
)
print(numbers)
Результат:
[1, 2, 3, 4, 5]
19. Строка
Строка является итерируемым объектом:
letters = [
'A',
'B',
]
letters.extend(
'XYZ'
)
print(letters)
Результат:
['A', 'B', 'X', 'Y', 'Z']
Каждый символ добавился как отдельный элемент.
Сравним с:
letters = [
'A',
'B',
]
letters.append(
'XYZ'
)
print(letters)
Результат:
['A', 'B', 'XYZ']
20. int передать в extend() нельзя
numbers = [
1,
2,
]
numbers.extend(100)
Возникнет:
TypeError
Потому что int не является итерируемым объектом.
При этом:
numbers.append(100)
работает нормально, потому что append() просто добавляет переданный объект целиком.
reverse()
21. Разворот существующего списка
numbers = [
10,
20,
30,
40,
]
numbers.reverse()
print(numbers)
Результат:
[40, 30, 20, 10]
Метод изменяет существующий список.
Новый список не создаётся.
reverse() и [::-1]
22. Срез создаёт новый список
numbers = [
10,
20,
30,
]
reversed_numbers = (
numbers[::-1]
)
print(numbers)
print(reversed_numbers)
Результат:
[10, 20, 30]
[30, 20, 10]
Получили два разных списка.
А:
numbers = [
10,
20,
30,
]
numbers.reverse()
изменяет сам:
numbers
23. Сложность обоих способов
И:
numbers[::-1]
и:
numbers.reverse()
имеют линейную сложность:
O(n)
Но [::-1]:
- проходит по элементам;
- создаёт новый список;
- выделяет память под новый массив ссылок.
reverse():
- работает с существующим списком;
- не создаёт второй список;
- переставляет ссылки внутри него.
Поэтому, если новый список не нужен, reverse() обычно является более подходящей операцией.
Конкретная разница во времени зависит от версии Python, размера данных и компьютера.
24. Идея алгоритма reverse()
Упрощённо разворот можно представить так:
[A][B][C][D][E]
поменять:
A ↔ E
[B] ↔ [D]
C остаётся на месте
То есть достаточно двигаться от двух краёв к центру.
Например, учебный вариант:
def my_reverse(values):
last_index = len(values) - 1
for index in range(
len(values) // 2
):
opposite_index = (
last_index - index
)
(
values[index],
values[opposite_index],
) = (
values[opposite_index],
values[index],
)
С циклами подробно познакомимся позже.
Сейчас важна сама идея:
Нет необходимости создавать второй список, если элементы можно поменять местами внутри существующего.
sort()
25. Сначала вспомним sorted()
Функция:
sorted()
принимает итерируемый объект и возвращает новый список.
numbers = [
30,
10,
20,
]
sorted_numbers = sorted(
numbers
)
print(numbers)
print(sorted_numbers)
Результат:
[30, 10, 20]
[10, 20, 30]
Исходный список не изменился.
Метод sort()
26. Сортировка на месте
numbers = [
30,
10,
20,
]
numbers.sort()
print(numbers)
Результат:
[10, 20, 30]
Метод изменил существующий список.
27. sort() существует именно у списков
Например:
numbers = (
30,
10,
20,
)
У tuple нет:
numbers.sort()
Для таких объектов можно использовать:
sorted(numbers)
которая вернёт новый список.
28. Обратная сортировка
Как и у sorted(), можно использовать:
reverse=True
numbers = [
30,
10,
20,
]
numbers.sort(
reverse=True
)
print(numbers)
Результат:
[30, 20, 10]
pop()
29. Удалить и получить элемент
Мы уже можем удалить элемент:
del numbers[-1]
Но иногда удаляемое значение нужно получить.
Для этого используется:
pop()
tasks = [
'task_1',
'task_2',
'task_3',
]
last_task = tasks.pop()
print(last_task)
print(tasks)
Результат:
task_3
['task_1', 'task_2']
Метод одновременно:
- удалил элемент;
- вернул его.
30. По умолчанию удаляется последний элемент
list.pop()
по умолчанию работает с индексом:
-1
Работа с концом динамического массива является эффективной операцией:
O(1)
31. Практический пример
Предположим, список хранит задачи, которые нужно обработать с конца:
tasks = [
'send_email',
'create_report',
'backup_database',
]
current_task = tasks.pop()
print(
f'Выполняем: {current_task}'
)
print(
f'Осталось: {tasks}'
)
Мы одновременно забрали задачу для работы и удалили её из списка ожидающих задач.
32. Можно передать индекс
numbers = [
10,
20,
30,
40,
]
removed_number = numbers.pop(1)
print(removed_number)
print(numbers)
Результат:
20
[10, 30, 40]
Если удаляется элемент не с конца, элементы справа приходится сдвигать.
33. Ошибки pop()
Несуществующий индекс:
numbers = [
10,
20,
]
numbers.pop(100)
приведёт к:
IndexError
Пустой список:
numbers = []
numbers.pop()
также приведёт к:
IndexError
remove()
34. Удаление по значению
pop() принимает индекс.
remove() принимает значение объекта, который нужно удалить.
technologies = [
'Python',
'SQL',
'Django',
]
technologies.remove(
'SQL'
)
print(technologies)
Результат:
['Python', 'Django']
35. Удаляется первое совпадение
numbers = [
10,
20,
10,
30,
]
numbers.remove(10)
print(numbers)
Результат:
[20, 10, 30]
Удалено только первое значение 10.
36. Почему remove() может быть дорогим
Чтобы выполнить:
numbers.remove(value)
сначала необходимо найти первое равное значение.
Упрощённо:
index 0 → подходит?
index 1 → подходит?
index 2 → подходит?
...
Поиск может потребовать обхода значительной части списка.
После удаления элементы справа ещё нужно сдвинуть.
Поэтому remove() имеет сложность:
O(n)
37. Если значения нет
numbers = [
10,
20,
30,
]
numbers.remove(100)
возникнет:
ValueError
clear()
38. Очистить содержимое списка
numbers = [
10,
20,
30,
]
numbers.clear()
print(numbers)
Результат:
[]
Сам объект списка продолжает существовать.
39. clear() и del — разные действия
numbers = [
10,
20,
30,
]
numbers.clear()
После этого имя:
numbers
по-прежнему существует и ссылается на пустой список.
А:
numbers = [
10,
20,
30,
]
del numbers
удаляет само имя:
numbers
из текущей области видимости.
После этого:
print(numbers)
приведёт к:
NameError
Методы, которые возвращают None
40. Очень важное правило
Большинство методов, которые изменяют список на месте, возвращают:
None
Например:
numbers = [
10,
20,
]
result = numbers.append(30)
print(numbers)
print(result)
Результат:
[10, 20, 30]
None
41. Нельзя писать так
numbers = [
10,
20,
]
numbers = numbers.append(30)
После выполнения:
numbers
будет ссылаться на:
None
Мы потеряли через это имя доступ к списку.
Правильно:
numbers.append(30)
42. Какие методы изменяют список и возвращают None
Из изученных:
append()
insert()
extend()
reverse()
sort()
remove()
clear()
они изменяют список и возвращают:
None
Это хорошо показывает намерение:
Метод вызван ради изменения существующего объекта.
Методы с другим поведением
43. count() и index()
Они список не изменяют.
numbers.count(10)
возвращает количество.
numbers.index(10)
возвращает индекс.
44. pop()
Особый случай:
value = numbers.pop()
Метод:
- изменяет список;
- возвращает удалённый объект.
45. copy()
Тоже возвращает значение:
new_list = numbers.copy()
Потому что задача метода — создать другой список.
copy()
46. Поверхностная копия
original = [
10,
20,
30,
]
copied = original.copy()
print(original)
print(copied)
print(
original is copied
)
Результат:
[10, 20, 30]
[10, 20, 30]
False
Это два разных объекта list.
47. Изменение внешнего списка независимо
original = [
10,
20,
30,
]
copied = original.copy()
original.append(40)
print(original)
print(copied)
Результат:
[10, 20, 30, 40]
[10, 20, 30]
Добавление в один внешний список не изменило второй.
Поверхностная копия
48. Что именно копирует copy()
Внутри списка хранятся ссылки на объекты.
Представим:
original = [
object_1,
object_2,
object_3,
]
После:
copied = original.copy()
создаётся новый внешний список, но в него копируются ссылки на те же элементы:
original
↓
[ref A][ref B][ref C]
↓ ↓ ↓
A B C
copied
↓
[ref A][ref B][ref C]
↓ ↓ ↓
A B C
Это называется:
shallow copy — поверхностная копия.
Вложенный изменяемый объект
49. Важный пример
original = [
[10, 20],
[30, 40],
]
copied = original.copy()
Внешние списки разные:
print(
original is copied
)
Результат:
False
Но:
print(
original[0] is copied[0]
)
Результат:
True
Оба внешних списка содержат ссылки на один и тот же вложенный список.
50. Поэтому изменение вложенного списка видно в обеих копиях
original = [
[10, 20],
[30, 40],
]
copied = original.copy()
original[0].append(99)
print(original)
print(copied)
Результат:
[[10, 20, 99], [30, 40]]
[[10, 20, 99], [30, 40]]
Мы изменили сам вложенный объект.
А ссылка на него находится в обоих внешних списках.
deepcopy()
51. Глубокая копия
Для рекурсивного копирования составной структуры существует функция:
deepcopy()
из модуля:
copy
Пример:
from copy import deepcopy
original = [
[10, 20],
[30, 40],
]
copied = deepcopy(
original
)
original[0].append(99)
print(original)
print(copied)
Результат:
[[10, 20, 99], [30, 40]]
[[10, 20], [30, 40]]
Теперь вложенные изменяемые структуры тоже были скопированы, поэтому изменение вложенного списка original не затронуло copied.
52. copy() и deepcopy()
Упрощённо:
copy()
↓
новый внешний контейнер
внутренние объекты остаются общими
deepcopy()
↓
рекурсивно копирует составную структуру
deepcopy() нужен не всегда.
Для обычного плоского списка:
numbers = [
10,
20,
30,
]
обычного:
numbers.copy()
обычно достаточно.
Краткая таблица методов list
| Метод | Что делает |
|---|---|
list.count(value) |
Возвращает количество элементов, равных value. |
list.index(value, start=..., stop=...) |
Возвращает индекс первого найденного value; если элемента нет — ValueError. |
list.append(object) |
Добавляет один объект в конец списка. |
list.insert(index, object) |
Вставляет один объект перед указанным индексом. |
list.extend(iterable) |
Добавляет в конец элементы из итерируемого объекта. |
list.reverse() |
Разворачивает существующий список на месте. |
list.sort(reverse=False) |
Сортирует существующий список на месте. |
list.pop(index=-1) |
Удаляет элемент по индексу и возвращает его. |
list.remove(value) |
Удаляет первое вхождение указанного значения. |
list.clear() |
Удаляет все элементы из существующего списка. |
list.copy() |
Возвращает поверхностную копию списка. |
Главное по теме
Методы:
append()
insert()
extend()
reverse()
sort()
remove()
clear()
изменяют существующий список и возвращают:
None
Поэтому:
numbers.append(10)
а не:
numbers = numbers.append(10)
Методы:
count()
index()
список не изменяют, а возвращают информацию о его элементах.
Метод:
pop()
одновременно:
- изменяет список;
- возвращает удалённый элемент.
Метод:
copy()
возвращает новый список, но выполняет только поверхностное копирование.
Для независимого копирования вложенных изменяемых структур существует:
from copy import deepcopy
С точки зрения производительности особенно полезно помнить:
append() в конец → amortized O(1)
pop() с конца → O(1)
insert() в начало → O(n)
remove() → O(n)
pop(0) → O(n)
Поэтому динамический массив особенно эффективен для работы с концом списка.