Главная / Курсы / Python / Глава 11. Списки
# Глава 11. Списки В этой главе мы будем говорить про тип `list` (список) — последовательность, не требующую от элементов уникальности и позволяющую обращаться к ним по индексам. Это что-то вроде массива динамической длины: можно модифицировать элементы списка, удалять и добавлять новые. ## Создание списка При создании списка используются квадратные скобки `[]` либо конструктор `list()`. Объявление списка строк: ```python langs = ["haskell", "erlang", "scala"] ``` Объявление пустого списка: ```python langs = [] ``` Список — это гетерогенный контейнер: он запросто может содержать элементы разных типов, в том числе и вложенные списки: ```python random_stuff = [2, 2, False, 9.1, ["A", 8], "search"] ``` Внимательно прочитайте объявление списка `links`. Как думаете, сколько в нем элементов и какие они? ```python links = ["realpython.com", "docs.python.org" "python.org"] print(links) ``` Легко ошибиться и сказать, что элементов три. Но если присмотреться, заметно, что между последним и пред-последним элементами пропущена запятая. Идущие одна за другой строки питон просто склеивает, поэтому в коллекцию попадет всего два элемента, а консольный вывод списка будет таким: ``` ['realpython.com', 'docs.python.orgpython.org'] ``` Так что при объявлении списка не забывайте расставлять запятые! А вот пустой список, объявленный с помощью конструктора: ```python langs = list() ``` ...И еще один пример с подвохом. Как считаете, что окажется в списке, если при создании вызвать конструктор `list()` от строки? ```python 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 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 s = "Lists & tuples" s[8] = "T" ``` При попытке модификации элемента строки по индексу генерируется исключение: ``` TypeError: 'str' object does not support item assignment ``` Как и при [посимвольном доступе к строкам,](/courses/python/chapters/python_chapter_0100#block-string-indices) при обращении к элементам списка допускается использовать отрицательные индексы. Особенно это удобно для получения последнего элемента: ```python 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 l = [1, 2, "bonjour"] m = [1, "bonjour", 2] print(l == m) ``` В этом примере сравнение вернуло `False`. Если же содержимое списков поэлементно совпадает, то списки считаются равными: ```python 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 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_0110##block-operators) в прошлой главе применительно к строкам, применимы и к спискам. Среди них присваивание `=`, сравнивание `==`, проверка на вхождение элемента с помощью `in`, конкатенация (`+`, `+=`), повторение последовательности n-ное количество раз через умножение `* n`. Так выглядит проверка, содержит ли список значение -1: ```python if -1 in [8, -1, 93]: print("list contains -1") ``` ``` list contains -1 ``` А так выглядит умножение списка на число: ```python 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 temperatures = [23.2, 21.0, 19.9, 22.5] min_temp = min(temperatures) print(min_temp) ``` ``` 19.9 ``` Для удаления одного элемента или среза элементов из списка используется **оператор del:** ```python 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 words = ["clear", "pop", "append"] words.sort(key=len, reverse=True) print(words) ``` ``` ['append', 'clear', 'pop'] ``` Как считаете, что выведет этот код? ```python 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` реализован как массив динамической длины. Элементы массива - это ссылки на области памяти, в которых хранятся элементы списка.
Отправка...

Если вам нравится проект, вы можете поддержать его!

Задонатить