# Глава 11. Списки
Рассмотрим тип `list` (список) — последовательность, не требующую от элементов уникальности и позволяющую обращаться к ним по индексам. Это что-то вроде массива динамической длины: можно модифицировать элементы списка, удалять и добавлять новые.
## Создание списка
При создании списка используются квадратные скобки `[]` либо конструктор `list()`.
Объявление списка строк:
```python
langs = ["haskell", "erlang", "scala"]
```
Объявление пустого списка:
```python
langs = []
```
Список — это гетерогенный контейнер: он запросто может содержать элементы разных типов, в том числе и вложенные списки:
```python
random_stuff = [2, 2, False, 9.1, ["A", 8], "search"]
```
Внимательно прочитайте объявление списка `links`. Как думаете, сколько в нем элементов и какие они?
```python {.example_for_playground}
links = ["realpython.com", "docs.python.org" "python.org"]
print(links)
```
Легко ошибиться и сказать, что элементов три. Но если присмотреться, заметно, что между последним и пред-последним элементами пропущена запятая. Идущие одна за другой строки питон просто склеивает, поэтому в коллекцию попадет всего два элемента, а консольный вывод списка будет таким:
```
['realpython.com', 'docs.python.orgpython.org']
```
Так что при объявлении списка не забывайте расставлять запятые!
А вот пустой список, объявленный с помощью конструктора:
```python
langs = list()
```
...И еще один пример с подвохом. Как считаете, что окажется в списке, если при создании вызвать конструктор `list()` от строки?
```python {.example_for_playground}
langs = list("elixir")
print(langs)
```
Естественно предположить, что `langs` — список, состоящий из единственного элемента. Но это не так.
Чтобы ответить правильно, нужно знать, что конструктор `list()` в качестве аргумента принимает итерабельный объект. В нашем случае `list()` проходит по строке и добавляет в список каждую букву по отдельности. Поэтому список `langs` состоит из 6-ти элементов:
```
['e', 'l', 'i', 'x', 'i', 'r']
```
А чтобы получить желаемый результат, вместо вызова конструктора от итерируемого объекта нужно инициализировать список напрямую: `langs = ["elixir"]`.
## Работа с элементами списка
Индексация в списке начинается с 0. Для получения значения элемента по индексу применяются все те же квадратные скобки.
Выведите в консоль значение второго элемента списка `sequences`. {.task_text}
```python {.task_source #python_chapter_0110_task_0010}
sequences = ["str", "list", "tuple", "range"]
```
Второй элемент списка имеет индекс 1. {.task_hint}
```python {.task_answer}
sequences = ["str", "list", "tuple", "range"]
print(sequences[1])
```
При обращении к элементам списков, вложенных в другие списки, индексы указываются в последовательно идущих квадратных скобках:
```python {.example_for_playground}
matrix = [[1, 2], [3, 4], [5, 6], [7, 8]]
val = matrix[2][1]
matrix[1][0] = val
print(matrix)
```
В данном примере переменной `val` присвоено значение 6: это 1-ый (начиная с нуля) элемент второго вложенного списка внутри `matrix`. Затем элемент со значением 3 был заменен на 6:
```
[[1, 2], [6, 4], [5, 6], [7, 8]]
```
Из примера видно, что в отличие от [строк,](/courses/python/chapters/python_chapter_0100/) элементы списков можно модифицировать. Со строками подобный фокус не пройдет:
```python {.example_for_playground}
s = "Lists & tuples"
s[8] = "T"
```
При попытке модификации элемента строки по индексу генерируется исключение:
```
TypeError: 'str' object does not support item assignment
```
Как и при [посимвольном доступе к строкам,](/courses/python/chapters/python_chapter_0100#block-string-indices) при обращении к элементам списка допускается использовать отрицательные индексы. Особенно это удобно для получения последнего элемента:
```python {.example_for_playground}
collections = ["sequences", "sets", "mappings"]
last_val = collections[-1]
```
В этом примере переменной `last_val` присваивается строка "mappings".
Замените элемент `"E"` матрицы на значение `"NEW"`, используя доступ по отрицательным индексам. Выведите `matrix` на экран.{.task_text}
```python {.task_source #python_chapter_0110_task_0020}
matrix = [["A", "B", "C"], ["D", "E", "F"]]
```
Элемент со значением `"E"` находится в первом с конца вложенном списке. Это его второй с конца элемент. {.task_hint}
```python {.task_answer}
matrix = [["A", "B", "C"], ["D", "E", "F"]]
matrix[-1][-2] = "NEW"
print(matrix)
```
Так как списки в питоне — это **упорядоченные** коллекции с доступом по индексу, то два списка, содержащих одинаковые элементы, но в разном порядке, считаются разными объектами:
```python {.example_for_playground}
l = [1, 2, "bonjour"]
m = [1, "bonjour", 2]
print(l == m)
```
В этом примере сравнение вернуло `False`.
Если же содержимое списков поэлементно совпадает, то списки считаются равными:
```python {.example_for_playground}
l = [16, 32]
m = [16, 32]
print(l == m)
```
В этом примере сравнение вернуло `True`.
К спискам, так же как и к строкам, применим синтаксис [срезов.](/courses/python/chapters/python_chapter_0100#block-slices)
Создайте переменную `l2` и сохраните в нее копию списка `l1`, используя синтаксис срезов. Затем выведите `l2` в консоль. {.task_text}
```python {.task_source #python_chapter_0110_task_0030}
l1 = ["pointer", "reference"]
```
Копия списка `l1` — это список, содержащий все его элементы. Его можно получить через срез `[:]`. {.task_hint}
```python {.task_answer}
l1 = ["pointer", "reference"]
l2 = l1[:]
print(l2)
```
С помощью срезов удобно получать перевернутый список: для этого достаточно опустить начальный и конечный индексы среза, а в качестве шага указать -1.
Выведите в консоль элементы списка `lst` в обратном порядке. Используйте для этого срез. {.task_text}
```python {.task_source #python_chapter_0110_task_0040}
lst = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
```
Чтобы указать отрицательный шаг среза, воспользуйтесь синтаксисом `[::-1]`. {.task_hint}
```python {.task_answer}
lst = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print(lst[::-1])
```
Интересная фишка питона: через срез элементы списка можно не только получать, но и присваивать!
```python
l = ["A", "B", "C", "D", "E", "F", "G", "H"]
l[2:5] = [10, 20, 30, 40, 50, 60]
print(l)
```
После присваивания срезу список `l` выглядит следующим образом:
```
['A', 'B', 10, 20, 30, 40, 50, 60, 'F', 'G', 'H']
```
Причем совершенно необязательно следить за тем, чтобы длина присваиваемого списка совпадала с длиной среза - назначения. Срез подстроится: если количество присваиваемых элементов больше, то они будут добавлены. Если меньше, то срез ужмется.
Что произойдет с исходным списком и срезами, на него ссылающимися, если один из таких срезов изменить? Приведет ли изменение среза к изменению исходного списка?
```python {.example_for_playground}
lst = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print("lst", lst)
sl1 = lst[1:7]
print("sl1", sl1)
sl2 = lst[2:4]
print("sl2", sl2)
sl1[1:5] = []
print("\nAfter sl1 reassignment:")
print("lst", lst)
print("sl1", sl1)
print("sl2", sl2)
```
Ответ — нет, не приведет. При изменении среза, ссылающегося на список, срез потеряет связь с исходным списком:
```
lst [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
sl1 [1, 2, 3, 4, 5, 6]
sl2 [2, 3]
After sl1 reassignment:
lst [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
sl1 [1, 6]
sl2 [2, 3]
```
В этом списке последовательность степеней двойки прервана символами. Исправьте это при помощи присвоения срезу. {.task_text}
```python {.task_source #python_chapter_0110_task_0050}
lst = [1, 2, 4, 8, 'N', 'M', 128, 256, 512]
# Your code here
print(lst)
```
В списке есть два символа по индексам 4 и 5. Соответственно срезу списка `lst`, указывающему на эти индексы, требуется присвоить список из трех элементов: степенй двойки между 8 и 128. {.task_hint}
```python {.task_answer}
lst = [1, 2, 4, 8, 'N', 'M', 128, 256, 512]
lst[4:6] = [16, 32, 64]
print(lst)
```
В этом списке последовательность букв английского алфавита прервана элементами - пустыми списками. Исправьте это при помощи присвоения срезу. {.task_text}
```python {.task_source #python_chapter_0110_task_0060}
lst = ["A", "B", [], [], [], "D", "E", "F", "G", "H"]
# Your code here
print(lst)
```
Пустые списки содержатся по индексам 2, 3, 4. Срез, смотрящий на эти элементы, требуется заменить списком из одного элемента. {.task_hint}
```python {.task_answer}
lst = ["A", "B", [], [], [], "D", "E", "F", "G", "H"]
lst[2:5] = ["C"]
print(lst)
```
Через присвоение срезу пустого списка можно удалять произвольные последовательности элементов.
Удалите выбивающиеся из последовательности элементы. Используйте для этого присвоение срезу. {.task_text}
```python {.task_source #python_chapter_0110_task_0070}
lst = ["A", None, 0, "B", "C", "D", "E"]
# Your code here
print(lst)
```
В последовательности букв английского алфавита есть два лишних вхождения: `None` и 0. Они содержатся по индксам 1 и 2. Соответствующему им срезу нужно присвоить пустой список. {.task_hint}
```python {.task_answer}
lst = ["A", None, 0, "B", "C", "D", "E"]
lst[1:3] = []
print(lst)
```
## Распространенные операции над списками {#block-operators}
Операции, [рассмотренные](/courses/python/chapters/python_chapter_0100#block-operators) в прошлой главе применительно к строкам, применимы и к спискам. Среди них присваивание `=`, сравнивание `==`, проверка на вхождение элемента с помощью `in`, конкатенация (`+`, `+=`), повторение последовательности n-ное количество раз через умножение `* n`.
Так выглядит проверка, содержит ли список значение -1:
```python {.example_for_playground}
if -1 in [8, -1, 93]:
print("list contains -1")
```
```
list contains -1
```
А так выглядит умножение списка на число:
```python {.example_for_playground}
lst = ["a", "b"]
print(lst * 4)
```
```
['a', 'b', 'a', 'b', 'a', 'b', 'a', 'b']
```
Добавьте элементы списка `m` в начало списка `l`. {.task_text}
```python {.task_source #python_chapter_0110_task_0080}
m = ["dart", "carbon"]
l = ["kotlin"]
# Your code here
print(l)
```
Сложите два списка и результат присвойте одному из исходных списков. {.task_hint}
```python {.task_answer}
m = ["dart", "carbon"]
l = ["kotlin"]
l = m + l
print(l)
```
Реализуйте функцию `is_item_in_list()`, которая принимает 2 аргумента: список и некое значение. Функция должна проверить, содержится ли значение в списке, и в зависимости от этого вернуть `True` либо `False`. {.task_text}
```python {.task_source #python_chapter_0110_task_0090}
```
Для проверки вхождения элемента в список воспользуйтесь оператором `in`. {.task_hint}
```python {.task_answer}
def is_item_in_list(lst, item):
return item in lst
```
К спискам, как и к строкам, применимы функции: определение длины `len()`, поиск минимального и максимального элементов `min()` и `max()`.
Пример поиска минимального элемента:
```python {.example_for_playground}
temperatures = [23.2, 21.0, 19.9, 22.5]
min_temp = min(temperatures)
print(min_temp)
```
```
19.9
```
Для удаления одного элемента или среза элементов из списка используется **оператор del:**
```python {.example_for_playground}
x = ["assert", "bool", "false"]
del x[1]
print(x)
```
В данном примере из списка был удален элемент по индексу 1:
```
['assert', 'false']
```
Используя оператор `del` для среза, удалите из списка `words` все элементы кроме первого и последнего. {.task_text}
Выведите получившийся список в консоль. {.task_text}
```python {.task_source #python_chapter_0110_task_0100}
words = "Sparse is better than dense".split()
```
Срез должен включать в себя элементы с 1-го индекса и до последнего. Последний элемент имеет индекс -1. {.task_hint}
```python {.task_answer}
words = "Sparse is better than dense".split()
del words[1:-1]
print(words)
```
## Распространенные методы списков
- `l.append(x)` — добавляет элемент `x` в конец списка `l`.
- `l.insert(i, x)` — добавляет к списку `l` по индексу `i` значение `x`.
- `l1.extend(l2)` — добавляет к списку `l1` элементы из итерабельного объекта `l2`.
- `l.remove(x)` — удаляет из списка `l` первое вхождение `x`. Если такое значение не найдено, кидает исключение `ValueError: list.remove(x): x not in list`.
- `l.pop(i=-1)` — удаляет из списка `l` элемент по индексу и возвращает его значение. `i` является необязательным параметром со значением -1 по умолчанию.
- `l.clear()` — удаляет из списка все элементы.
- `l.sort(key=None, reverse=False)` — сортирует список по возрастанию. Если `reverse` выставлен в `True`, то по убыванию. Если задан аргумент `key`, то для сравнения элементов при сортировке используется функция от одного аргумента, например `key=str.lower`.
Пример сортировки списка слов по убыванию их длины:
```python {.example_for_playground}
words = ["clear", "pop", "append"]
words.sort(key=len, reverse=True)
print(words)
```
```
['append', 'clear', 'pop']
```
Как считаете, что выведет этот код?
```python {.example_for_playground}
res = ["Errors", "should", "never", "pass"]
res += "silently"
print(res)
```
При конкатенации списка с неким объектом интерпретатор неявно пытается применить конструктор `list()` к данному объекту. А как мы помним, на вход `list()` принимает итерабельный объект. В нашем случае это строка "silently". Поэтому результат выглядит так:
```
['Errors', 'should', 'never', 'pass', 's', 'i', 'l', 'e', 'n', 't', 'l', 'y']
```
Исправьте этот код с помощью одного из методов списка, чтобы на 2-ой строке к списку добавлялась полноценная строка. {.task_text}
```python {.task_source #python_chapter_0110_task_0110}
res = ["Errors", "should", "never", "pass"]
res += "silently" # Fix me
print(res)
```
Замените оператор `+=` на вызов метода `append()`. {.task_hint}
```python {.task_answer}
res = ["Errors", "should", "never", "pass"]
res.append("silently")
print(res)
```
Составьте из элементов списка `words` строку. Назовите ее `citation`. Слова в строке должны быть разделены пробелом. {.task_text}
Выведите строку в консоль. {.task_text}
```python {.task_source #python_chapter_0110_task_0120}
words = ["simple", "is", "better", "than", "complex"]
```
Воспользуйтесь методом строки `join()`, рассмотренным в прошлой главе. {.task_hint}
```python {.task_answer}
words = ["simple", "is", "better", "than", "complex"]
citation = " ".join(words)
print(citation)
```
Реализуйте функцию `abs_sort`, которая принимает на вход список чисел и сортирует его in-place по убыванию модуля числа. {.task_text}
```python {.task_source #python_chapter_0110_task_0130}
```
Для in-place сортировки вызовите метод списка `sort()`. Передайте в него два параметра: `key=abs` для указания в качестве ключа сортировки встренной функции `abs()`; `reverse=True` для сортировки списка в обратном порядке. {.task_hint}
```python {.task_answer}
def abs_sort(lst):
lst.sort(key=abs, reverse=True)
```
## Как организован список изнутри {#block-lst-inner}
В недрах интерпретатора CPython тип `list` — это массив динамической длины, а не связный список, как можно было бы предположить. Данный массив состоит из элементов, аллоцированных друг за другом в единой выделенной под них области памяти. Каждый элемент массива хранит ссылку на область памяти, содержащую нужный объект. Интерпретатор хранит указатель на первый элемент массива и его длину.
Исходя из этого, можно сделать выводы об [алгоритмической сложности](https://ru.wikipedia.org/wiki/%D0%92%D1%8B%D1%87%D0%B8%D1%81%D0%BB%D0%B8%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D1%81%D0%BB%D0%BE%D0%B6%D0%BD%D0%BE%D1%81%D1%82%D1%8C) манипуляций над объектами типа `list`.
Например, поиск элемента в списке `elem in lst` осуществляется полным перебором, поэтому выполняется за O(n).
Операция `lst.pop(0)` тоже имеет сложность O(n): она удаляет нулевой элемент и сдвигает оставшиеся элементы массива на 1.
В то же время операция `lst.append(x)` имеет амортизированную сложность O(1): если в выделенной области памяти хватает места, новый элемент добавляется в массив за константное время. Если же места под элемент не хватает, осуществляется аллокация новой более обширной области памяти, в которую переносятся все элементы из старой.
## Резюмируем
- Тип `list` (список) — это гетерогенная последовательность элементов с доступом по индексу.
- Для создания списка используются квадратные скобки `[]` либо конструктор `list()`.
- Для работы с подмножествами элементов списка можно использовать срезы.
- В отличие от строк, список — это изменяемая последовательность. Ее можно модифицировать, не изменяя при этом id объекта.
- В CPython тип `list` реализован как массив динамической длины. Элементы массива - это ссылки на области памяти, в которых хранятся элементы списка.
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!