Тип данных list в Python: Глубокое Погружение с Примерами
Оглавление
- Начало работы с типом данных list в Python
- Построение списков в Python
- Доступ к элементам в списке: индексация
- Извлечение нескольких Элементов из списка: Нарезка
- Создание копий списка
- Обновление элементов в списках: Присвоение индексов
- Динамически увеличивающиеся и сокращающиеся списки
- Объединение и повтор списков
- Переворачивание и сортировка списков
- Просмотр списков
- Изучение других возможностей списков
- Распространенные ошибки в списках Python
- Создание подкласса для встроенного класса list
- Приведение списков В действие
- Принятие решения о том, следует ли использовать списки
- Заключение
Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Просмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Изучение типа данных list в Python на примерах
list Класс является фундаментальным встроенным типом данных в Python. Он обладает впечатляющим и полезным набором функций, позволяющих эффективно организовывать разнородные данные и манипулировать ими. Знание того, как использовать списки, является обязательным навыком для вас, как разработчика Python. Списки имеют множество вариантов использования, поэтому вы часто будете обращаться к ним при написании кода в реальных условиях.
Работая с этим руководством, вы углубитесь в изучение списков и получите четкое представление об их ключевых функциях. Эти знания позволят вам писать более эффективный код, используя преимущества списков.
В этом руководстве вы узнаете, как:
- Создание новых списков в Python
- Доступ к элементам в существующем списке
- Копировать, обновлять, увеличивать, сокращать и объединять существующие списки
- Сортировать, перевернуть и пересечь существующие списки
- Используйте другие возможности списков Python
Кроме того, вы закодируете несколько примеров, демонстрирующих распространенные варианты использования списков в Python. Они помогут вам понять, как лучше использовать списки в вашем коде.
Чтобы извлечь максимальную пользу из этого руководства, вы должны хорошо понимать основные концепции Python, включая переменные, функции, и for циклы. Вам также будет полезно ознакомиться с другими встроенными типами данных, такими как строки, кортежи, словарей и наборов.
Бесплатный бонус: Нажмите здесь, чтобы загрузить пример кода, который сделает вас экспертом по list Python.> тип данных.
Начало работы с типом данных Python list
Python list - это гибкий, разносторонний, мощный и популярный встроенный тип данных. Это позволяет вам создавать последовательности объектов переменной длины и изменяемые . В списке вы можете хранить объекты любого типа. Вы также можете смешивать объекты разных типов в одном списке, хотя элементы списка часто имеют один и тот же тип.
Примечание: В этом руководстве вы будете использовать термины элементы, элементы, и значения взаимозаменяемы для обозначения объектов, хранящихся в списке.
Некоторые из наиболее важных характеристик объектов list включают в себя:
- Упорядоченные: они содержат элементы или позиции, которые расположены последовательно в соответствии с их конкретным порядком вставки.
- Основанные на нуле: они позволяют вам получать доступ к их элементам по индексам, начинающимся с нуля.
- Изменяемые: Они поддерживают локальные мутации или изменения содержащихся в них элементов.
- Разнородные: они могут хранить объекты разных типов.
- Расширяемые и динамичные: они могут динамически увеличиваться или уменьшаться в размере, что означает, что они поддерживают добавление, вставку и удаление элементов.
- Вложенные: Они могут содержать другие списки, так что у вас могут быть списки списков.
- Повторяемые: они поддерживают итерацию, поэтому вы можете просматривать их, используя цикл или понимание, при выполнении операций над каждым из их элементов.
- Возможность нарезки: Они поддерживают операции нарезки, что означает, что вы можете извлекать из них ряд элементов.
- Комбинируемые: они поддерживают операции объединения, поэтому вы можете объединить два или более списка, используя операторы объединения.
- Возможность копирования: Они позволяют вам создавать копии их контента, используя различные методы.
Списки - это последовательности объектов. Их обычно называют контейнерами или коллекциями, потому что один список может содержать или собирать произвольное количество других объектов.
Примечание: В Python списки поддерживают широкий набор операций, общих для всех типов последовательностей, включая кортежи, строки, и диапазоны. Эти операции известны как операции общей последовательности. В этом руководстве вы узнаете о нескольких операциях, которые относятся к этой категории.
В Python списки упорядочены, что означает, что они сохраняют свои элементы в порядке вставки:
>>> colors = [
... "red",
... "orange",
... "yellow",
... "green",
... "blue",
... "indigo",
... "violet"
... ]
>>> colors
['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']
Элементы в этом списке представляют собой строки, представляющие цвета. Если вы откроете объект list, то увидите, что цвета отображаются в том же порядке, в котором вы вставили их в список. Этот порядок остается неизменным в течение всего срока службы списка, если только вы не внесете в него какие-либо изменения.
Вы можете получить доступ к отдельному объекту в списке по его позиции или индексу в последовательности. Индексы начинаются с нуля:
>>> colors[0]
'red'
>>> colors[1]
'orange'
>>> colors[2]
'yellow'
>>> colors[3]
'green'
Позиции нумеруются от нуля до длины списка минус единица. Элемент с индексом 0 является первым элементом в списке, элемент с индексом 1 - вторым и так далее.
Списки могут содержать объекты разных типов. Вот почему списки представляют собой разнородные коллекции:
[42, "apple", True, {"name": "John Doe"}, (1, 2, 3), [3.14, 2.78]]
Этот список содержит объекты различных типов данных, включая целое число , число, строку, логическое значение , значение словарь, кортеж и другой список. Несмотря на то, что эта функция списков может показаться интересной, на практике вы обнаружите, что в списках обычно хранятся однородные данные.
Примечание: Одной из наиболее важных характеристик списков является то, что они являются изменяемыми типами данных. Эта особенность сильно влияет на их поведение и варианты использования. Например, изменчивость подразумевает, что списки не являются хэшируемыми, поэтому вы не можете использовать их в качестве словаря ключей. В этом руководстве вы узнаете много нового о том, как изменчивость влияет на списки. Итак, продолжайте читать!
Хорошо! Этого достаточно для первого ознакомления со списками Python. В оставшейся части этого руководства вы познакомитесь со всеми вышеперечисленными характеристиками списков и не только. Ты готов? Чтобы начать, вы начнете с изучения различных способов создания списков.
Построение списков в Python
Обо всем по порядку. Если вы хотите использовать список для хранения или сбора каких-либо данных в своем коде, вам необходимо создать объект list. Вы найдете несколько способов создания списков в Python. Это одна из особенностей, которая делает списки такими универсальными и популярными.
Например, вы можете создавать списки с помощью одного из следующих инструментов:
В следующих разделах вы узнаете, как использовать три перечисленных выше инструмента для создания новых списков в вашем коде. Вы начнете с литералов списков.
Создание списков с помощью литералов
Литералы-списки, вероятно, являются наиболее популярным способом создания объекта list в Python. Эти литералы довольно просты. Они состоят из пары квадратных скобок, в которые заключен ряд объектов, разделенных запятыми.
Вот общий синтаксис литерала списка:
[item_0, item_1, ..., item_n]
Этот синтаксис создает список из n элементов, заключая их в пару квадратных скобок. Обратите внимание, что вам не нужно заранее указывать тип элементов или размер списка. Помните, что списки имеют переменный размер и могут содержать разнородные объекты.
Вот несколько примеров того, как использовать синтаксис literal для создания новых списков:
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> fruits = ["apple", "banana", "orange", "kiwi", "grape"]
>>> cities = [
... "New York",
... "Los Angeles",
... "Chicago",
... "Houston",
... "Philadelphia"
... ]
>>> matrix = [
... [1, 2, 3],
... [4, 5, 6],
... [7, 8, 9]
... ]
>>> inventory = [
... {"product": "phone", "price": 1000, "quantity": 10},
... {"product": "laptop", "price": 1500, "quantity": 5},
... {"product": "tablet", "price": 500, "quantity": 20}
... ]
>>> functions = [print, len, range, type, enumerate]
>>> empty = []
В этих примерах вы используете синтаксис list literal для создания списков, содержащих числа, строки, другие списки, словари и даже функциональные объекты. Как вы уже знаете, в списках могут храниться объекты любого типа. Они также могут быть пустыми, как последний список в приведенном выше фрагменте кода.
Пустые списки полезны во многих ситуациях. Например, возможно, вы хотите создать список объектов, являющихся результатом вычислений, выполняемых в цикле. Цикл позволит вам заполнять пустой список по одному элементу за раз.
Использование list-литералов, пожалуй, самый распространенный способ создания списков. Вы найдете эти литералы во многих примерах и кодовых базах Python. Они пригодятся, когда у вас есть набор элементов с тесно связанными значениями, и вы хотите упаковать их в единую структуру данных.
Обратите внимание, что именование списков существительными во множественном числе является обычной практикой, которая улучшает читаемость. Однако в некоторых ситуациях вы также можете использовать собирательные существительные.
Например, у вас может быть список с именем people. В этом случае каждый элемент будет иметь значение person. Другим примером может служить список, представляющий таблицу в базе данных. Вы можете назвать этот список table, и каждый элемент будет иметь значение row. Другие примеры, подобные этим, вы найдете в руководстве по использованию списков.
С помощью конструктора list()
Еще одним инструментом, позволяющим создавать list объектов, является класс конструктор, list(). Вы можете вызвать этот конструктор с любым итерируемым объектом, включая другие списки, кортежи, наборы, словари и их компоненты, строки и многие другие. Вы также можете вызвать его без каких-либо аргументов, и в этом случае вы получите обратно пустой список.
Вот общий синтаксис:
list([iterable])
Чтобы создать список, вам нужно вызвать list() так, как вы бы вызвали любой конструктор класса или функцию. Обратите внимание, что квадратные скобки вокруг iterable означают, что аргумент является необязательным, поэтому скобки не являются частью синтаксиса. Вот несколько примеров использования конструктора:
>>> list((0, 1, 2, 3, 4, 5, 6, 7, 8, 9))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list({"circle", "square", "triangle", "rectangle", "pentagon"})
['square', 'rectangle', 'triangle', 'pentagon', 'circle']
>>> list({"name": "John", "age": 30, "city": "New York"}.items())
[('name', 'John'), ('age', 30), ('city', 'New York')]
>>> list("Pythonista")
['P', 'y', 't', 'h', 'o', 'n', 'i', 's', 't', 'a']
>>> list()
[]
В этих примерах вы создаете различные списки, используя конструктор list(), который принимает любые типы итерируемых объектов, включая кортежи, словари, строки и многое другое. Он даже принимает наборы, и в этом случае вам нужно помнить, что наборы представляют собой неупорядоченные структуры данных, поэтому вы не сможете предсказать окончательный порядок элементов в результирующем списке.
Вызов list() без аргумента создает, а возвращает новый пустой список. Этот способ создания пустых списков менее распространен, чем использование пары пустых квадратных скобок. Однако в некоторых ситуациях это может сделать ваш код более понятным, четко сообщив о ваших намерениях: создание пустого списка.
Конструктор list() особенно полезен, когда вам нужно создать список из объекта итератора. Например, предположим, что у вас есть функция генератора, которая выдает числа из последовательности Фибоначчи по запросу, и вам нужно сохранить первые десять чисел в списке.
В этом случае вы можете использовать list() как в приведенном ниже коде:
>>> def fibonacci_generator(stop):
... current_fib, next_fib = 0, 1
... for _ in range(0, stop):
... fib_number = current_fib
... current_fib, next_fib = next_fib, current_fib + next_fib
... yield fib_number
...
>>> fibonacci_generator(10)
<generator object fibonacci_generator at 0x10692f3d0>
>>> list(fibonacci_generator(10))
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Вызов fibonacci_generator() напрямую возвращает итератор генератора объект, который позволяет вам выполнять итерации по числам в последовательности Фибоначчи вплоть до индекса по вашему выбору. Однако вам не нужен итератор в вашем коде. Вам нужен список. Быстрый способ получить этот список - обернуть итератор вызовом list(), как вы сделали в последнем примере.
Этот метод удобен, когда вы работаете с функциями, возвращающими итераторы, и хотите создать объект списка из элементов, которые выдает итератор. Конструктор list() использует итератор, создает ваш список и возвращает его вам обратно.
Примечание: Вы также можете использовать синтаксис literal и оператор повторяемой распаковки (*) в качестве альтернатива конструктору list().
Вот как это делается:
>>> [*fibonacci_generator(10)]
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
В этом примере оператор распаковки iterable использует итератор, а квадратные скобки формируют окончательный список чисел. Однако этот метод менее удобочитаем и понятен, чем использование list().
В качестве дополнительного замечания, вы часто обнаружите, что встроенные и сторонние функции возвращают итераторов. Такие функции, как reversed(), enumerate(), map() и filter(), являются хорошими примерами этой практики. Реже встречаются функции, которые напрямую возвращают list объектов, но встроенная функция sorted() является одним из примеров. Он принимает iterable в качестве аргумента и возвращает список отсортированных элементов.
Создание списков С Расширением списка
Использование списков - одна из самых отличительных особенностей Python. Они довольно популярны в сообществе Python, так что вы, скорее всего, найдете их повсюду. Понимание списков позволяет быстро создавать и преобразовывать списки, используя синтаксис, который имитирует for цикл, но в одной строке кода.
Основной синтаксис представления списка выглядит примерно так:
[expression(item) for item in iterable]
Для понимания каждого списка необходимы как минимум три компонента:
expression()является выражением Python , которое возвращает конкретное значение, и в большинстве случаев это значение зависит отitem. Обратите внимание, что это не обязательно должна быть функция.itemявляется ли текущий объект изiterable.iterableможет быть любым итерируемым объектом Python, таким как список, кортеж, набор, строка или генератор.
Конструкция for выполняет итерацию по элементам в iterable, в то время как expression(item) предоставляет соответствующий элемент списка, который является результатом выполнения понимания.
Чтобы проиллюстрировать, как использование списков позволяет создавать новые списки из существующих повторяющихся элементов, предположим, что вы хотите создать список с квадратными значениями первых десяти целых чисел. В этом случае вы можете написать следующее понимание:
>>> [number ** 2 for number in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
В этом примере вы используете range() для получения первых десяти целых чисел. При вычислении квадрата и построении нового списка понимание повторяет их. Этот пример - всего лишь краткий пример того, что вы можете сделать с пониманием списка.
Примечание: Чтобы глубже разобраться в понимании списков и в том, как их использовать, ознакомьтесь с Когда использовать понимание списков в Python.
В общем, вы будете использовать list comprehension, когда вам нужно создать список преобразованных значений из существующего iterable. Comprehensions - отличный инструмент, которым вы должны овладеть как разработчик Python. Они оптимизированы для повышения производительности и просты в написании.
Доступ к элементам списка: индексация
Вы можете получить доступ к отдельным элементам из списка, используя связанный с элементом индекс . Что такое индекс элемента? Каждый элемент в списке имеет индекс, который определяет его положение в списке. Индексы - это целые числа, которые начинаются с 0 и увеличиваются до количества элементов в списке минус 1.
Чтобы получить доступ к элементу списка через его индекс, вы используете следующий синтаксис:
list_object[index]
Эта конструкция известна как операция индексации, а часть [index] известна как оператор индексации. Он состоит из пары квадратных скобок, в которые заключен желаемый или целевой индекс. Вы можете прочитать эту конструкцию как из list_object дайте мне элемент по адресу index.
Вот как этот синтаксис работает на практике:
>>> languages = ["Python", "Java", "JavaScript", "C++", "Go", "Rust"]
>>> languages[0]
'Python'
>>> languages[1]
'Java'
>>> languages[2]
'JavaScript'
>>> languages[3]
'C++'
>>> languages[4]
'Go'
>>> languages[5]
'Rust'
Индексирование вашего списка по различным индексам дает вам прямой доступ к основным элементам. Если вы используете Обозначение большого числа O для временной сложности, то вы можете сказать, что индексация - это O(1) операция. Это означает, что списки вполне подходят для тех ситуаций, когда вам нужно получить доступ к случайным элементам из ряда элементов.
Вот более наглядное представление того, как индексы соотносятся с элементами в списке:
"Python" |
"Java" |
"JavaScript" |
"C++" |
"Go" |
"Rust" |
|---|---|---|---|---|---|
0 |
1 |
2 |
3 |
4 |
5 |
В любом списке Python индекс первого элемента равен 0, индекс второго элемента равен 1 и так далее. Индекс последнего элемента - это количество элементов за вычетом 1.
Количество элементов в списке называется длиной списка . Вы можете проверить длину списка с помощью встроенной функции len():
>>> len(languages)
6
Итак, индекс последнего элемента в languages равен 6 - 1 = 5. Это индекс элемента "Rust" в вашем списке примеров. Если вы используете индекс, превышающий это число, в операции индексации, то вы получите IndexError исключение:
>>> languages[6]
Traceback (most recent call last):
...
IndexError: list index out of range
В этом примере вы пытаетесь получить элемент с индексом 6. Поскольку этот индекс больше, чем 5, в результате вы получите значение IndexError. Использование индексов, выходящих за пределы диапазона, может быть невероятно распространенной проблемой при работе со списками, поэтому следите за своими целевыми индексами.
Операции индексирования в Python довольно гибкие. Например, вы также можете использовать отрицательные индексы при индексировании списков. Этот вид индекса дает вам доступ к элементам списка в обратном порядке:
>>> languages[-1]
'Rust'
>>> languages[-2]
'Go'
>>> languages[-3]
'C++'
>>> languages[-4]
'JavaScript'
>>> languages[-5]
'Java'
>>> languages[-6]
'Python'
Отрицательный индекс определяет положение элемента относительно правого конца списка, возвращая его к началу списка. Вот представление процесса:
"Python" |
"Java" |
"JavaScript" |
"C++" |
"Go" |
"Rust" |
|---|---|---|---|---|---|
-6 |
-5 |
-4 |
-3 |
-2 |
-1 |
Вы можете получить доступ к последнему элементу в списке, используя индекс -1. Аналогично, индекс -2 указывает предпоследний элемент и так далее. Важно отметить, что отрицательные индексы не начинаются с 0, потому что 0 уже указывает на первый элемент. Это может сбить с толку, когда вы впервые узнаете об отрицательных и положительных индексах, но вы привыкнете к этому. Это просто требует небольшой практики.
Обратите внимание, что если вы используете отрицательные индексы, то первым элементом в списке будет -len(languages). Если вы используете индекс ниже этого значения, то получите IndexError:
>>> languages[-7]
Traceback (most recent call last):
...
IndexError: list index out of range
Когда вы используете индекс ниже -len(languages), вы получаете сообщение об ошибке, сообщающее о том, что целевой индекс находится вне диапазона.
Использование отрицательных индексов может быть очень удобным во многих ситуациях. Например, доступ к последнему элементу в списке - довольно распространенная операция. В Python вы можете сделать это, используя отрицательные индексы, как в languages[-1], что более читабельно и лаконично, чем выполнение чего-то вроде languages[len(languages) - 1].
Примечание: Отрицательные индексы также пригодятся, когда вам нужно перевернуть список, как вы узнаете из раздела Переворачивание и сортировка списков.
Как вы уже знаете, списки могут содержать элементы любого типа, включая другие списки и последовательности. Если у вас есть список, содержащий другие последовательности, вы можете получить доступ к элементам в любой вложенной последовательности, объединив операции индексирования. Рассмотрим следующий список записей о сотрудниках:
>>> employees = [
... ("John", 30, "Software Engineer"),
... ("Alice", 25, "Web Developer"),
... ("Bob", 45, "Data Analyst"),
... ("Mark", 22, "Intern"),
... ("Samantha", 36, "Project Manager")
... ]
Как вы можете получить доступ к отдельным фрагментам данных любого конкретного сотрудника? Вы можете использовать следующий синтаксис индексации:
list_of_sequences[index_0][index_1]...[index_n]
Число в конце каждого индекса представляет уровень вложенности списка. Например, ваш список сотрудников имеет один уровень вложенности. Таким образом, чтобы получить доступ к данным Алисы, вы можете сделать что-то вроде этого:
>>> employees[1][0]
'Alice'
>>> employees[1][1]
25
>>> employees[1][2]
'Web Developer'
В этом примере, когда вы выполняете employees[1][0], индекс 1 ссылается на второй элемент в списке employees. Это вложенный список, содержащий три элемента. 0 относится к первому элементу в этом вложенном списке, которым является "Alice". Как вы можете видеть, вы можете получить доступ к элементам во вложенных списках, применив несколько операций индексации подряд. Этот метод применим к спискам с несколькими уровнями вложенности.
Если вложенные элементы являются словарями, то вы можете получить доступ к их данным, используя ключи:
>>> employees = [
... {"name": "John", "age": 30, "job": "Software Engineer"},
... {"name": "Alice", "age": 25, "job": "Web Developer"},
... {"name": "Bob", "age": 45, "job": "Data Analyst"},
... {"name": "Mark", "age": 22, "job": "Intern"},
... {"name": "Samantha", "age": 36, "job": "Project Manager"}
... ]
>>> employees[3]["name"]
'Mark'
>>> employees[3]["age"]
22
>>> employees[3]["job"]
Intern
В этом примере у вас есть список словарей. Чтобы получить доступ к данным из одного из словарей, вам нужно использовать его индекс в списке, за которым следует целевой ключ в квадратных скобках.
Извлечение нескольких элементов из списка: Нарезка
Другим распространенным требованием при работе со списками является извлечение части или фрагмента из данного списка. Вы можете сделать это с помощью операции нарезки, которая имеет следующий синтаксис:
list_object[start:stop:step]
Часть [start:stop:step] этой конструкции известна как оператор нарезки. Его синтаксис состоит из пары квадратных скобок и трех необязательных индексов, start, stop, и step. Второе двоеточие необязательно. Обычно вы используете его только в тех случаях, когда вам нужно значение step, отличное от 1.
Примечание: Нарезка - это операция, общая для всех типов данных последовательности Python, включая списки, кортежи, строки, диапазоны и другие.
Вот что означают индексы в операторе нарезки:
startуказывает индекс, с которого вы хотите начать нарезку. Результирующий фрагмент содержит элемент с этим индексом.stopуказывает индекс, при достижении которого вы хотите, чтобы при разделении элементы перестали извлекаться. Результирующий фрагмент не содержит элемент с таким индексом.stepпредоставляет целочисленное значение, представляющее, сколько элементов будет пропущено при нарезке на каждом шаге. Результирующий фрагмент не будет включать пропущенные элементы.
Все индексы в операторе нарезки необязательны. Они имеют следующие значения по умолчанию:
| Index | Default Value |
|---|---|
start |
0 |
stop |
len(list_object) |
step |
1 |
Минимальным рабочим вариантом оператора индексирования является [:]. В этом варианте вы полагаетесь на значения всех индексов по умолчанию и используете тот факт, что второе двоеточие необязательно. Вариант [::] оператора нарезки приводит к тому же результату, что и [:]. На этот раз вы полагаетесь на значения трех индексов по умолчанию.
Примечание: Оба приведенных выше варианта оператора нарезки ([:] и [::]) позволяют создать неполную копию вашего целевого списка. Вы узнаете больше об этой теме в разделе Мелкие копии списка.
Теперь пришло время вам изучить несколько примеров того, как работает нарезка:
>>> letters = ["A", "a", "B", "b", "C", "c", "D", "d"]
>>> upper_letters = letters[0::2] # Or [::2]
>>> upper_letters
['A', 'B', 'C', 'D']
>>> lower_letters = letters[1::2]
>>> lower_letters
['a', 'b', 'c', 'd']
В этом примере у вас есть список букв в верхнем и нижнем регистре. Вы хотите извлечь заглавные буквы в один список, а строчные - в другой список. Оператор [0::2] поможет вам с первой задачей, а [1::2] - со второй.
В обоих примерах вы установили для step значение 2, потому что хотите получить все остальные буквы из исходного списка. В первой нарезке вы используете start из 0, потому что хотите начать с самого начала списка. Во второй нарезке вы используете start из 1, потому что вам нужно перепрыгнуть через первый элемент и начать извлекать элементы из второго.
Вы можете использовать любой вариант оператора нарезки, который соответствует вашим потребностям. Во многих ситуациях очень полезно полагаться на индексы по умолчанию. В приведенных выше примерах вы используете значение по умолчанию stop, которое равно len(list_object). Этот метод позволяет выполнять нарезку до последнего элемента целевого списка.
Вот еще несколько примеров нарезки:
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> first_three = digits[:3]
>>> first_three
[0, 1, 2]
>>> middle_four = digits[3:7]
>>> middle_four
[3, 4, 5, 6]
>>> last_three = digits[-3:]
>>> last_three
[7, 8, 9]
>>> every_other = digits[::2]
>>> every_other
[0, 2, 4, 6, 8]
>>> every_three = digits[::3]
>>> every_three
[0, 3, 6, 9]
В этих примерах имена переменных отражают ту часть списка, которую вы извлекаете при каждой операции нарезки. Как вы можете заключить, оператор нарезки довольно гибкий и универсален. Он позволяет использовать даже отрицательные индексы.
Каждая операция нарезки использует внутренний объект slice. Встроенная функция slice() предоставляет альтернативный способ создания slice объектов, которые можно использовать для извлечения нескольких элементов из списка. Сигнатура этой встроенной функции следующая:
slice(start, stop, step)
Он принимает три аргумента с тем же значением, что и индексы в операторе slicing, и возвращает объект slice, эквивалентный [start:stop:step]. Чтобы проиллюстрировать, как работает slice(), вернитесь к примеру letters и перепишите его, используя эту функцию вместо оператора нарезки. В итоге у вас получится что-то вроде следующего:
>>> letters = ["A", "a", "B", "b", "C", "c", "D", "d"]
>>> upper_letters = letters[slice(0, None, 2)]
>>> upper_letters
['A', 'B', 'C', 'D']
>>> lower_letters = letters[slice(1, None, 2)]
>>> lower_letters
['a', 'b', 'c', 'd']
Передача None любым аргументам slice() сообщает функции, что вы хотите использовать ее внутреннее значение по умолчанию, которое совпадает с значением по умолчанию эквивалентного индекса в операторе нарезки. В этих примерах вы передаете None в stop, что сообщает slice(), что вы хотите использовать len(letters) в качестве значения для stop.
В качестве упражнения вы можете написать digits примеров, используя slice() вместо оператора нарезки. Давайте, попробуйте! Эта практика поможет вам лучше разобраться в тонкостях операций нарезки в Python.
Наконец, важно отметить, что значения, выходящие за пределы диапазона для start и stop, не приводят к тому, что выражения с разделением на увеличивают значение TypeError исключение. В общем, вы будете наблюдать следующее поведение:
- Если
startнаходится перед началом списка, что может произойти при использовании отрицательных индексов, то Python будет использовать0вместо этого. - Если
startбольше, чемstop, то при нарезке будет возвращен пустой список. - Если
stopпревышает длину списка, то вместо этого Python будет использовать длину списка.
Вот несколько примеров, демонстрирующих это поведение в действии:
>>> colors = [
... "red",
... "orange",
... "yellow",
... "green",
... "blue",
... "indigo",
... "violet"
... ]
>>> len(colors)
7
>>> colors[-8:]
['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']
>>> colors[8:]
[]
>>> colors[:8]
['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']
В этих примерах ваш список цветов содержит семь элементов, поэтому len(colors) возвращает 7. В первом примере вы используете отрицательное значение для start. Первый элемент colors находится под индексом -7. Поскольку -8 < -7, Python заменяет ваше значение start на 0, в результате чего получается фрагмент, содержащий элементы от 0 до конца списка.
Примечание: В приведенных выше примерах вы используете только одно двоеточие в каждом фрагменте. В повседневном программировании это обычная практика. Второе двоеточие будет использоваться только в том случае, если вам нужно указать step, отличное от 1. Вот пример, где step равно 2:
>>> colors[::2]
['red', 'yellow', 'blue', 'violet']
В этом примере вы устанавливаете для step значение 2, потому что вам нужна копия colors, которая содержит все остальные цвета. На каждом шаге нарезка переходит на два цвета и возвращает вам список из четырех цветов.
Во втором примере вы используете значение start, длина которого больше, чем длина colors. Поскольку после конца списка извлекать нечего, Python возвращает пустой список. В последнем примере вы используете значение stop, которое больше длины colors. В этом случае Python извлекает все элементы до конца списка.
Создание копий списка
Создание копий существующего списка - распространенная необходимость в коде на Python. Наличие копии гарантирует, что при изменении данного списка это изменение не повлияет на исходные данные или данные в других копиях.
Примечание: В Python идентификатор объекта - это уникальный идентификатор, который отличает его от других объектов. Вы можете использовать встроенную функцию id(), чтобы получить идентификатор любого объекта Python. В реализации Python на CPython идентификатор объекта совпадает с адресом памяти, где этот объект хранится.
В Python у вас есть два вида механизмов для создания копий существующего списка. Вы можете создать любой из них:
- A мелкая копия
- A глубокая копия
Оба типа копий имеют специфические характеристики, которые непосредственно влияют на их поведение. В следующих разделах вы узнаете, как создавать мелкие и глубокие копии существующих списков в Python. Сначала вы ознакомитесь с псевдонимами, связанным понятием, которое может вызвать некоторую путаницу и привести к проблемам и багам.
Псевдонимы списка
В Python вы можете создавать псевдонимы из переменных, используя оператор присваивания (=). Присвоения не создают копий объектов в Python. Вместо этого они создают привязки между переменной и объектом, участвующим в присвоении. Поэтому, если у вас есть несколько псевдонимов из заданного списка, изменения в псевдониме будут влиять на остальные псевдонимы.
Чтобы проиллюстрировать, как вы можете создавать псевдонимы и как они работают, рассмотрим следующий пример:
>>> countries = ["United States", "Canada", "Poland", "Germany", "Austria"]
>>> nations = countries
>>> id(countries) == id(nations)
True
>>> countries[0] = "United States of America"
>>> nations
['United States of America', 'Canada', 'Poland', 'Germany', 'Austria']
В этом фрагменте кода первая выделенная строка создает nations в качестве псевдонима countries. Обратите внимание, что обе переменные указывают на один и тот же объект, который вы знаете, потому что идентичность объекта одинакова. Во второй выделенной строке вы обновляете объект с индексом 0 на countries. Это изменение отражается на псевдониме nations.
Операторы присваивания, подобные приведенному в первой выделенной строке выше, не создают копий правого объекта. Они просто создают псевдонимы или переменные, которые указывают на тот же базовый объект.
В общем, псевдонимы могут пригодиться в ситуациях, когда вам нужно избежать коллизий имен в вашем коде или когда вам нужно адаптировать имена к определенным шаблонам именования.
Для иллюстрации предположим, что у вас есть приложение, которое использует ваш список стран как countries в одной части кода. Приложению требуется тот же список в другой части кода, но там уже есть переменная с именем countries с другим содержимым.
Если вы хотите, чтобы оба фрагмента кода работали с одним и тем же списком, вы можете использовать nations в качестве псевдонима для countries. Удобным способом сделать это было бы использовать as ключевое слово для создания псевдонима с помощью неявного присвоения, например, когда вы импортируете список из другого модуля.
Неполные копии списка
Поверхностная копия существующего списка - это новый список, содержащий ссылки на объекты, сохраненные в исходном списке. Другими словами, когда вы создаете неполную копию списка, Python создает новый список с новым идентификатором. Затем он вставляет ссылки на объекты из исходного списка в новый список.
Существует как минимум три различных способа создания неполных копий существующего списка. Вы можете использовать:
Эти три инструмента демонстрируют одинаковое поведение. Итак, для начала вы познакомитесь с оператором нарезки:
>>> countries = ["United States", "Canada", "Poland", "Germany", "Austria"]
>>> nations = countries[:]
>>> nations
['United States', 'Canada', 'Poland', 'Germany', 'Austria']
>>> id(countries) == id(nations)
False
Выделенная строка создает nations как уменьшенную копию countries с помощью оператора обрезки только с одним двоеточием. Эта операция выполняет срез от начала до конца countries. В этом случае nations и countries имеют разные идентификаторы. Это полностью независимые list объекты.
Однако элементы в nations являются псевдонимами элементов в countries:
>>> id(nations[0]) == id(countries[0])
True
>>> id(nations[1]) == id(countries[1])
True
Как вы можете видеть, элементы под одним и тем же индексом в nations и countries имеют один и тот же идентификатор объекта. Это означает, что у вас нет копий элементов. Вы действительно делитесь ими. Такое поведение позволяет вам сэкономить немного памяти при работе со списками и их копиями.
Как это повлияет на поведение обоих списков? Если вы изменили элемент в nations, отразится ли это изменение в countries? Приведенный ниже код поможет вам ответить на эти вопросы:
>>> countries[0] = "United States of America"
>>> countries
['United States of America', 'Canada', 'Poland', 'Germany', 'Austria']
>>> nations
['United States', 'Canada', 'Poland', 'Germany', 'Austria']
>>> id(countries[0]) == id(nations[0])
False
>>> id(countries[1]) == id(nations[1])
True
В первой строке этого фрагмента кода вы обновляете элемент с индексом 0 в countries. Это изменение не затрагивает элемент с индексом 0 в nations. Теперь первые элементы в списках - это совершенно разные объекты со своими собственными идентификаторами. Однако остальные элементы по-прежнему имеют одинаковые идентификаторы. Таким образом, в каждом случае это один и тот же объект.
Поскольку создание копий списка является такой распространенной операцией, в классе list для этого есть специальный метод. Метод называется .copy(), и он возвращает неполную копию целевого списка:
>>> countries = ["United States", "Canada", "Poland", "Germany", "Austria"]
>>> nations = countries.copy()
>>> nations
['United States', 'Canada', 'Poland', 'Germany', 'Austria']
>>> id(countries) == id(nations)
False
>>> id(countries[0]) == id(nations[0])
True
>>> id(countries[1]) == id(nations[1])
True
>>> countries[0] = "United States of America"
>>> countries
['United States of America', 'Canada', 'Poland', 'Germany', 'Austria']
>>> nations
['United States', 'Canada', 'Poland', 'Germany', 'Austria']
При вызове .copy() в countries вы получите неполную копию этого списка. Теперь у вас есть два разных списка. Однако их элементы являются общими для обоих. Опять же, если вы измените элемент в одном из списков, то это изменение не отразится в копии.
Вы найдете еще один инструмент для создания мелких копий списка. Функция copy() из модуля copy позволяет вам делать именно это:
>>> from copy import copy
>>> countries = ["United States", "Canada", "Poland", "Germany", "Austria"]
>>> nations = copy(countries)
>>> nations
['United States', 'Canada', 'Poland', 'Germany', 'Austria']
>>> id(countries) == id(nations)
False
>>> id(countries[0]) == id(nations[0])
True
>>> id(countries[1]) == id(nations[1])
True
>>> countries[0] = "United States of America"
>>> countries
['United States of America', 'Canada', 'Poland', 'Germany', 'Austria']
>>> nations
['United States', 'Canada', 'Poland', 'Germany', 'Austria']
Когда вы вводите copy() с изменяемым типом данных контейнера, таким как список, функция возвращает неполную копию входного объекта. Эта копия ведет себя так же, как и предыдущие неполные копии, которые вы создали в этом разделе.
Глубокие копии списка
Иногда может потребоваться создать полную копию существующего списка. Другими словами, вам нужна копия, которая создает новый объект списка, а также новые копии содержащихся в нем элементов. В таких ситуациях вам придется создать так называемую глубокую копию .
Когда вы создаете глубокую копию списка, Python создает новый объект list, а затем рекурсивно вставляет копии объектов из исходного списка .
Чтобы создать полную копию существующего списка, вы можете использовать функцию deepcopy() из модуля copy. Вот пример того, как работает эта функция:
>>> from copy import deepcopy
>>> matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> matrix_copy = deepcopy(matrix)
>>> id(matrix) == id(matrix_copy)
False
>>> id(matrix[0]) == id(matrix_copy[0])
False
>>> id(matrix[1]) == id(matrix_copy[1])
False
В этом примере вы создаете полную копию своего списка matrix. Обратите внимание, что оба списка и их родственные элементы имеют разные идентификаторы.
Зачем вам вообще понадобилось создавать глубокую копию matrix? Например, если вы создадите только небольшую копию matrix, то при попытке изменить вложенные списки вы можете столкнуться с некоторыми проблемами:
>>> from copy import copy
>>> matrix_copy = copy(matrix)
>>> matrix_copy[0][0] = 100
>>> matrix_copy[0][1] = 200
>>> matrix_copy[0][2] = 300
>>> matrix_copy
[[100, 200, 300], [4, 5, 6], [7, 8, 9]]
>>> matrix
[[100, 200, 300], [4, 5, 6], [7, 8, 9]]
В этом примере вы создаете неполную копию matrix. Если вы изменяете элементы во вложенном списке в matrix_copy, то эти изменения влияют на исходные данные в matrix. Способ избежать такого поведения - использовать глубокую копию:
>>> matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> matrix_copy = deepcopy(matrix)
>>> matrix_copy[0][0] = 100
>>> matrix_copy[0][1] = 200
>>> matrix_copy[0][2] = 300
>>> matrix_copy
[[100, 200, 300], [4, 5, 6], [7, 8, 9]]
>>> matrix
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Теперь изменения в matrix_copy или любой другой глубокой копии не влияют на содержимое matrix, как вы можете видеть по выделенным строкам.
Наконец, важно отметить, что когда у вас есть список, содержащий неизменяемые объекты, такие как числа, строки или кортежи, поведение deepcopy() имитирует то, что делает copy():
>>> countries = ["United States", "Canada", "Poland", "Germany", "Austria"]
>>> nations = deepcopy(countries)
>>> id(countries) == id(nations)
False
>>> id(countries[0]) == id(nations[0])
True
>>> id(countries[1]) == id(nations[1])
True
В этом примере, даже если вы используете deepcopy(), элементы в nations являются псевдонимами элементов в countries. Такое поведение имеет смысл, потому что вы не можете изменять неизменяемые объекты на месте. Опять же, такое поведение оптимизирует потребление памяти вашим кодом при работе с несколькими копиями списка.
Обновление элементов в списках: Присвоение индексов
Списки Python являются изменяемыми типами данных. Это означает, что вы можете изменять их элементы, не меняя идентификатор базового списка. Такого рода изменения обычно известны как мутации на месте. Они позволяют вам обновить значение одного или нескольких элементов в существующем списке.
примечание:, чтобы погрузиться глубже в то, что изменяемые и неизменяемые типы данных и как они работают в Python, проверить в Python изменяемые против незыблемых видах: в чем разница?
Чтобы изменить значение данного элемента в списке, вы можете использовать следующий синтаксис:
list_object[index] = new_value
Оператор индексации предоставляет вам доступ к целевому элементу через его индекс, в то время как оператор присваивания позволяет вам изменить его текущее значение.
Вот как работает это задание:
>>> numbers = [1, 2, 3, 4]
>>> numbers[0] = "one"
>>> numbers
['one', 2, 3, 4]
>>> numbers[1] = "two"
>>> numbers
['one', 'two', 3, 4]
>>> numbers[-1] = "four"
>>> numbers
['one', 'two', 3, 'four']
>>> numbers[-2] = "three"
>>> numbers
['one', 'two', 'three', 'four']
В этом примере вы заменили все числовые значения в numbers строками. Для этого вы использовали их индексы и оператор присваивания в том, что вы можете назвать назначениями индексов. Обратите внимание, что отрицательные индексы также работают.
Что делать, если вы знаете значение элемента, но не знаете его индекс в списке? Как вы можете обновить значение элемента? В этом случае вы можете использовать метод .index(), как в приведенном ниже коде:
>>> fruits = ["apple", "banana", "orange", "kiwi", "grape"]
>>> fruits[fruits.index("kiwi")] = "mango"
>>> fruits
['apple', 'banana', 'orange', 'mango', 'grape']
Метод .index() принимает определенный элемент в качестве аргумента и возвращает индекс первого появления этого элемента в базовом списке. Вы можете воспользоваться этим, если знаете элемент, который хотите обновить, но не его индекс. Однако обратите внимание, что если целевой элемент отсутствует в списке, то вы получите ValueError.
Вы также можете обновить значения нескольких элементов списка за один раз. Для этого вы можете получить доступ к элементам с помощью оператора разбиения на части, а затем использовать оператор присваивания и итерацию новых значений. Для краткости эту комбинацию операторов можно назвать назначением фрагмента.
Вот общий синтаксис:
list_object[start:stop:step] = iterable
В этом синтаксисе значения из iterable заменяют часть из list_object, определенную оператором нарезки. Если iterable содержит то же количество элементов, что и целевой фрагмент, то Python обновляет элементы один за другим, не изменяя длину фрагмента. list_object.
Чтобы понять это поведение, рассмотрим следующие примеры:
>>> numbers = [1, 2, 3, 4, 5, 6, 7]
>>> numbers[1:4] = [22, 33, 44]
>>> numbers
[1, 22, 33, 44, 5, 6, 7]
В этом примере вы обновляете элементы, расположенные в диапазоне от 1 до 4, не включая последний элемент. В этом фрагменте у вас есть три элемента, поэтому вы используете список из трех новых значений, чтобы обновлять их одно за другим.
Если iterable содержит больше или меньше элементов, чем целевой фрагмент, то list_object автоматически увеличится или уменьшится соответственно:
>>> numbers = [1, 5, 6, 7]
>>> numbers[1:1] = [2, 3, 4]
>>> numbers
[1, 2, 3, 4, 5, 6, 7]
Теперь в исходном списке чисел есть только четыре значения. Значения 1, 2, и 3 отсутствуют. Итак, вы используете фрагмент, чтобы вставить их, начиная с индекса 1. В этом случае фрагмент имеет один индекс, в то время как список значений содержит три новых значения, поэтому Python автоматически расширяет ваш список, чтобы содержать новые значения.
Вы также можете использовать фрагмент для сокращения существующего списка:
>>> numbers = [1, 2, 0, 0, 0, 0, 4, 5, 6, 7]
>>> numbers[2:6] = [3]
>>> numbers
[1, 2, 3, 4, 5, 6, 7]
Здесь в исходном списке есть куча нулей там, где должно быть 3. Ваш оператор нарезки берет порцию, заполненную нулями, и заменяет ее единицей 3.
Использование оператора нарезки для обновления значений нескольких элементов в существующем списке - довольно полезная техника, которую поначалу может быть трудно освоить. Продолжайте и попрактикуйтесь еще немного, чтобы лучше понять, как работает эта техника.
Динамически увеличивающиеся и сокращающиеся списки
В списках Python изменяемость не ограничивается возможностью изменять элементы на месте. Поскольку списки изменяемы, вы можете изменять их длину на лету, добавляя или удаляя элементы. Итак, списки также являются контейнерами переменной длины, как вы уже узнали.
Добавление новых элементов в список или удаление ненужных - это повседневные задачи. Вот почему Python предоставляет различные эффективные способы выполнения этих действий. Использование подходящего инструмента для работы - важный навык.
В следующих разделах вы познакомитесь с различными инструментами, которые предлагает Python для динамического увеличения и сокращения списка.
Одновременное добавление одного элемента: .append()
Метод .append(), вероятно, является наиболее распространенным инструментом, который вы будете использовать для добавления элементов в существующий список. Как следует из названия, этот метод позволяет добавлять элементы в список. Метод берет по одному элементу за раз и добавляет его в правый конец целевого списка.
Вот пример того, как работает .append():
>>> pets = ["cat", "dog"]
>>> pets.append("parrot")
>>> pets
['cat', 'dog', 'parrot']
>>> pets.append("gold fish")
>>> pets
['cat', 'dog', 'parrot', 'gold fish']
>>> pets.append("python")
>>> pets
['cat', 'dog', 'parrot', 'gold fish', 'python']
В этих примерах каждый вызов .append() добавляет нового питомца в конец вашего списка. Такое поведение позволяет вам постепенно заполнять пустой список или добавлять элементы в существующий список, как вы делали в примере.
Примечание: Для более глубокого понимания того, как работает .append(), ознакомьтесь с Python'ом .append(): Добавляйте элементы в Ваши списки на месте.
Использование .append() эквивалентно выполнению следующего задания фрагмента:
>>> pets[len(pets):] = ["hawk"]
>>> pets
['cat', 'dog', 'parrot', 'gold fish', 'python', 'hawk']
Назначение фрагмента в этом примере расширяет ваши списки, добавляя новый элемент "hawk" после последнего текущего элемента в pets. Этот метод работает так же, как и .append(). Однако использование .append() приводит к более удобочитаемому и понятному решению.
При использовании .append() важно помнить, что этот метод одновременно добавляет только один элемент. Этот элемент может быть любого типа, включая другой список:
>>> pets.append(["hamster", "turtle"])
>>> pets
[
'cat',
'dog',
'parrot',
'gold fish',
'python',
'hawk',
['hamster', 'turtle']
]
Обратите внимание, что последний элемент в pets представляет собой список из двух питомцев, а не двух новых независимых питомцев. Такое поведение может быть источником незначительных ошибок. Чтобы избежать проблем, вы должны помнить, что .append() каждый раз берется и добавляется один элемент.
Если вам нужно добавить несколько элементов из повторяющегося списка в конец существующего списка, вы можете использовать метод .extend(), который будет рассмотрен в следующем разделе.
Расширение списка несколькими элементами одновременно: .extend()
При работе со списками вы можете столкнуться с необходимостью добавить сразу несколько элементов в правый конец списка. Поскольку это такое распространенное требование, в list Python есть специальный метод для этой задачи.
Этот метод называется .extend(). Он берет итерацию объектов и добавляет их как отдельные элементы в конец целевого списка:
>>> fruits = ["apple", "pear", "peach"]
>>> fruits.extend(["orange", "mango", "banana"])
>>> fruits
['apple', 'pear', 'peach', 'orange', 'mango', 'banana']
Метод .extend() распаковывает элементы во входном iterable и добавляет их один за другим в правый конец вашего целевого списка. Теперь в конце fruits есть еще три элемента.
Следует отметить, что .extend() может принимать в качестве аргумента любую итерацию. Таким образом, вы можете использовать кортежи, строки, словари и их компоненты, итераторы и даже наборы. Однако помните, что если вы используете set в качестве аргумента для extend(), то вы не будете знать окончательный порядок элементов заранее.
Опять же, вы должны отметить, что .extend() эквивалентно следующему распределению фрагментов:
>>> fruits = ["apple", "pear", "peach"]
>>> fruits[len(fruits):] = ["orange", "mango", "banana"]
>>> fruits
['apple', 'pear', 'peach', 'orange', 'mango', 'banana']
В этом примере вы используете назначение фрагмента, чтобы добавить три элемента после конца вашего исходного списка fruits. Обратите внимание, что результат будет таким же, как при использовании .extend(). Однако вариант .extend() более удобочитаем и понятен.
Вставка элемента в заданное положение: .insert()
Метод .insert() - это еще один инструмент, который вы можете использовать для добавления элементов в существующий список. Этот метод немного отличается от .append() и .extend(). Вместо добавления элементов в правый конец списка, .insert() позволяет вам выбрать, куда вы хотите поместить свой элемент. При этом .insert() принимает два аргумента:
index: индекс, по которому вы хотите вставить элементitem: элемент, который вам нужно вставить в список
Когда вы вставляете элемент с заданным индексом, Python перемещает все следующие элементы на одну позицию вправо, чтобы освободить место для нового элемента, который займет место старого элемента с целевым индексом:
>>> letters = ["A", "B", "F", "G"]
>>> letters.insert(2, "C")
>>> letters
['A', 'B', 'C', 'F', 'G']
>>> letters.insert(3, "D")
>>> letters
['A', 'B', 'C', 'D', 'F', 'G']
>>> letters.insert(4, "E")
>>> letters
['A', 'B', 'C', 'D', 'E', 'F', 'G']
В этом примере вы вставляете буквы в определенные позиции в letters. Вы должны вставлять по одной букве за раз, потому что .insert() добавляет один элемент в каждый вызов. Чтобы вставить элемент, метод перемещает все элементы, начиная с целевого индекса, в правый конец списка. Это перемещение освобождает место для нового элемента.
Не могли бы вы в качестве упражнения придумать задание для фрагментов, которое дало бы тот же результат, что и .insert()? Решение можно найти в разделе "Сворачиваемый" ниже:
Вот эквивалентная нарезка операции вставки:
list_object[index:index] = [item]
Этот оператор использует пустой фрагмент из list_object. Почему пустой? Итак, нарезка начинается с index и заканчивается на index. Из-за этого фрагмент не содержит никаких элементов.
Затем оператор присваивает пустому фрагменту список из одного элемента. В результате этого действия item будет вставлен в index в list_object. Давайте, попробуйте!
Теперь, когда вы научились добавлять элементы в существующий список с помощью различных инструментов и техник, пришло время научиться удалять ненужные элементы из списка, что является еще одной распространенной задачей.
Удаление элементов из списка
Python также позволяет вам удалить один или несколько элементов из существующего списка. Опять же, удаление элементов из списков - настолько распространенная операция, что в классе list уже есть методы, которые помогут вам в этом. У вас будут следующие методы:
| Method | Description |
|---|---|
.remove(item) |
Removes the first occurrence of item from the list. It raises a ValueError if there’s no such item. |
.pop([index]) |
Removes the item at index and returns it back to the caller. If you don’t provide a target index, then .pop() removes and returns the last item in the list. Note that the square brackets around index mean that the argument is optional. The brackets aren’t part of the syntax. |
.clear() |
Removes all items from the list. |
Метод .remove() удобен, когда вы хотите удалить элемент из списка, но не знаете его индекс. Если у вас есть несколько элементов с одинаковым значением, то вы можете удалить их все, вызвав .remove() столько раз, сколько встречается этот элемент:
>>> sample = [12, 11, 10, 42, 14, 12, 42]
>>> sample.remove(42)
>>> sample
[12, 11, 10, 14, 12, 42]
>>> sample.remove(42)
>>> sample
[12, 11, 10, 14, 12]
>>> sample.remove(42)
Traceback (most recent call last):
...
ValueError: list.remove(x): x not in list
При первом вызове .remove() удаляется первый экземпляр числа 42. При втором вызове удаляется оставшийся экземпляр 42. Если вы вызываете .remove() с элементом, которого нет в целевом списке, то вы получаете ValueError.
Метод .pop() позволяет удалять и возвращать определенный элемент, используя его индекс. Если вы вызываете метод без индекса, то он удаляет и возвращает последний элемент в базовом списке:
>>> to_visit = [
... "https://realpython.com",
... "https://python.org",
... "https://stackoverflow.com",
... ]
>>> visited = to_visit.pop()
>>> visited
'https://stackoverflow.com'
>>> to_visit
['https://realpython.com', 'https://python.org']
>>> visited = to_visit.pop(0)
>>> visited
'https://realpython.com'
>>> to_visit
['https://python.org']
>>> visited = to_visit.pop(-1)
>>> visited
'https://python.org'
>>> to_visit
[]
В этих примерах первый вызов .pop() удаляет и возвращает последний сайт в вашем списке сайтов для посещения. Второй вызов удаляет и возвращает первый сайт, который является сайтом по индексу 0.
Наконец, вы используете .pop() с -1 в качестве аргумента, чтобы подчеркнуть, что вы также можете использовать отрицательные индексы. Этот вызов удаляет и возвращает последний элемент. В конце процесса ваш список сайтов для посещения будет пуст, что указывает на то, что вы совершили все запланированные посещения.
Удаление всех элементов из списка за один раз может быть еще одной частой задачей. В этом случае Python также поможет вам, потому что в list есть метод, называемый .clear(), который выполняет именно это. Рассмотрим следующий пример:
>>> cache = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> cache.clear()
>>> cache
[]
Если вы вызовете .clear() для непустого объекта list, то содержимое списка будет полностью удалено. Этот метод может быть полезен, когда ваши списки работают как кэш, который вы хотите быстро очистить перед перезапуском.
Следующее назначение фрагмента приводит к тому же результату, что и метод .clear():
>>> cache = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]
>>> cache[:] = []
>>> cache
[]
При таком назначении фрагмента вы присваиваете пустой список фрагменту, который захватывает весь целевой список. Опять же, этот синтаксис менее понятен, чем при использовании .clear().
Есть еще один инструмент Python, который вы можете использовать для удаления одного или нескольких элементов из существующего списка. Да, это оператор del. Вы можете комбинировать del с операцией индексации или нарезки, чтобы удалить элемент или несколько элементов, соответственно:
>>> colors = [
... "red",
... "orange",
... "yellow",
... "green",
... "blue",
... "indigo",
... "violet"
... ]
>>> del colors[1]
>>> colors
['red', 'yellow', 'green', 'blue', 'indigo', 'violet']
>>> del colors[-1]
>>> colors
['red', 'yellow', 'green', 'blue', 'indigo']
>>> del colors[2:4]
>>> colors
['red', 'yellow', 'indigo']
>>> del colors[:]
>>> colors
[]
С помощью первого оператора del вы удаляете цвет с индексом 1, который равен "orange". Во втором del вы используете отрицательный индекс -1, чтобы удалить последний цвет, "violet". Затем вы используете фрагмент, чтобы удалить "green" и "blue".
Примечание: Чтобы глубже разобраться в использовании инструкции del, ознакомьтесь с инструкцией в Python del: Удалить ссылки из Областей и контейнеров.
В последнем примере вы используете del и фрагмент, чтобы удалить все элементы из существующего списка. Эта конструкция выдает результат, эквивалентный вызову .clear() в вашем целевом списке.
Учет производительности при расширении Списков
Когда вы создаете список, Python выделяет достаточно места для хранения предоставленных элементов. Он также выделяет дополнительное пространство для размещения будущих элементов. Когда вы используете дополнительное пространство, добавляя новые элементы в этот список с помощью .append(), .extend(), или .insert(), Python автоматически создает пространство для дополнительных элементов.
Этот процесс известен как изменение размера, и хотя он гарантирует, что список может принимать новые элементы, он требует дополнительного процессорного времени и дополнительной памяти. Почему? Итак, чтобы расширить существующий список, Python создает новый, в котором есть место для ваших текущих данных и дополнительных элементов. Затем он перемещает текущие элементы в новый список и добавляет новый элемент или элементы.
Рассмотрим следующий код, чтобы изучить, как Python динамически увеличивает список:
>>> from sys import getsizeof
>>> numbers = []
>>> for value in range(100):
... print(getsizeof(numbers))
... numbers.append(value)
...
56
88
88
88
88
120
120
120
120
184
184
...
В этом фрагменте кода вы сначала импортируете getsizeof() из модуля sys . Эта функция позволяет получить размер объекта в байтах. Затем вы определяете numbers как пустой список.
Внутри цикла for вы получаете и выводите размер вашего объекта списка в байтах. Первая итерация показывает, что размер вашего пустого списка равен 56 байтам, что является базовым размером каждого списка в Python.
Далее метод .append() добавляет новое значение в ваш список. Обратите внимание, что размер numbers увеличивается до 88 байт. Это базовый размер плюс 32 дополнительных байта (56 + 4 × 8 = 88), которые представляют собой четыре 8-байтовых указателя или ячейки для будущих элементов. Это означает, что Python пошел дальше и выделил место для четырех элементов, когда вы добавили первый элемент.
По мере выполнения цикла размер numbers увеличивается до 120 байт, что равно 88 + 4 × 8 = 120. На этом шаге выделяется пространство для еще четырех элементов. Вот почему на вашем экране четыре раза появляется 120.
Если вы проследите за выводами цикла, то заметите, что на следующих шагах добавляется место для восьми дополнительных элементов, затем для двенадцати, затем для шестнадцати и так далее. Каждый раз, когда Python изменяет размер списка, ему приходится перемещать все элементы в новое пространство, что занимает значительное время.
На практике, если вы работаете с небольшими списками, то общее влияние такого внутреннего поведения незначительно. Однако в ситуациях, критичных к производительности, или когда ваши списки большие, вы можете захотеть использовать более эффективные типы данных, такие как collections.deque, например.
Ознакомьтесь с вики-страницей о временной сложности для получения подробной информации о том, насколько эффективны операции list. Например, метод .append() имеет временную сложность O(1), что означает, что добавление элемента в список занимает постоянное время. Однако, когда Python приходится увеличивать список, чтобы освободить место для нового элемента, эта производительность будет немного ниже.
Осознание временной сложности выполнения обычных операций со списками значительно улучшит вашу способность выбирать подходящий инструмент для работы в зависимости от ваших конкретных потребностей.
Объединение и повтор списков
Еще одной интересной и полезной особенностью list в Python является то, что он поддерживает следующие две операции:
- Объединение, в котором используется оператор plus (
+) - Повторение, в котором используется оператор умножения (
*)
В следующих разделах вы узнаете, как эти две операции работают со списками Python и как вы можете использовать их в своем коде.
Объединение списков
Конкатенация заключается в соединении двух элементов вместе. В этом случае вы хотели бы объединить два списка, что вы можете сделать с помощью оператора plus (+). В данном контексте этот оператор известен как оператор объединения .
Вот как это работает:
>>> [0, 1, 2, 3] + [4, 5, 6] + [7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
В этом примере вы объединяете три объекта списка с помощью оператора объединения. Обратите внимание, как этот оператор создает новый список, содержащий все отдельные элементы из трех исходных списков.
Всякий раз, когда вы используете оператор объединения, в результате вы получаете новый объект list. Рассмотрим следующий пример. Следите за идентификатором digits:
>>> digits = [0, 1, 2, 3, 4, 5]
>>> id(digits)
4558758720
>>> digits = digits + [6, 7, 8, 9]
>>> id(digits)
4470412224
В этом примере вы сначала создаете список, содержащий несколько чисел. Функция id() позволяет вам узнать идентификатор этого первого списка. Затем вы используете операцию объединения для завершения вашего списка цифр. Обратите внимание, что id() теперь возвращает другое значение. Этот результат подтверждает, что оператор объединения всегда создает новый список, который объединяет его операнды.
Примечание: Объединить список можно только с другим списком. Если вы попытаетесь объединить список с чем-то другим, то получите исключение:
>>> [0, 1, 2, 3, 4, 5] + (6, 7, 8, 9)
Traceback (most recent call last):
...
TypeError: can only concatenate list (not "tuple") to list
Оператор объединения в Python вызывает исключение TypeError при попытке объединить список с другим типом данных, таким как кортеж.
У оператора объединения есть расширенный вариант , который использует оператор +=. Вот как работает этот оператор:
>>> digits = [0, 1, 2, 3, 4, 5]
>>> digits += [6, 7, 8, 9]
>>> digits
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Оператор расширенной конкатенации работает с существующим списком. Он берет второй список и добавляет его элементы, один за другим, в конец исходного списка. Эта операция является сокращением к чему-то вроде digits = digits + [6, 7, 8, 9]. Однако она работает немного по-другому.
В отличие от обычного оператора объединения, расширенный вариант изменяет целевой список на месте, а не создает новый список:
>>> digits = [0, 1, 2, 3, 4, 5]
>>> id(digits)
4699578112
>>> digits += [6, 7, 8, 9]
>>> id(digits)
4699578112
В этом примере функция id() возвращает одно и то же значение в обоих вызовах, что означает, что у вас есть один объект списка вместо двух. Расширенная конкатенация изменяет digits на месте, поэтому весь процесс более эффективен с точки зрения объема памяти и времени выполнения, чем обычная конкатенация.
Повторение содержимого списка
Повторение заключается в клонировании содержимого данного списка определенное количество раз. Вы можете добиться этого с помощью оператора повторения (*),, который принимает два операнда:
- Список, содержимое которого вы хотите повторить
- Количество раз, которое вам нужно повторить содержимое
Чтобы понять, как работает этот оператор, рассмотрим следующий пример:
>>> ["A", "B", "C"] * 3
['A', 'B', 'C', 'A', 'B', 'C', 'A', 'B', 'C']
>>> 3 * ["A", "B", "C"]
['A', 'B', 'C', 'A', 'B', 'C', 'A', 'B', 'C']
Здесь вы повторяете содержимое списка три раза и в результате получаете новый список. В первом примере левый операнд - это целевой список, а правый операнд - это целое число, представляющее количество раз, которое вы хотите повторить содержимое списка. В этом втором примере операнды меняются местами, но результат тот же, что и при выполнении операции умножения.
У оператора повторения также есть расширенный вариант, который вы будете называть оператором расширенного повторения. В этом варианте используется оператор *=. Вот как это работает:
>>> letters = ["A", "B", "C"]
>>> letters *= 3
>>> letters
['A', 'B', 'C', 'A', 'B', 'C', 'A', 'B', 'C']
В выделенном выражении левый операнд является целевым списком, а правый - целым значением. Вы не можете поменять их местами.
Опять же, обычный оператор повторения возвращает новый объект списка, содержащий повторяющиеся данные. Однако его расширенный вариант изменяет целевой список на месте, что делает его более эффективным. В качестве упражнения воспользуйтесь функцией id() для подтверждения этого утверждения.
Реверсирование и сортировка списков
Реверсирование и специальная сортировка списков значений являются обычными задачами в программировании. В Python у вас будут встроенные функции reversed() и sorted() для выполнения этих задач. Когда вы работаете со списками, у вас также есть методы .reverse() и .sort(), которые изменяют и сортируют целевой список на месте.
В следующих разделах вы узнаете, как переворачивать и сортировать списки, используя инструменты, которые предоставляет Python для этих задач.
Перевернуть список: reversed() и .reverse()
Встроенная функция reversed() принимает последовательность в качестве аргумента и возвращает итератор, который возвращает значения этой последовательности в обратном порядке:
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> reversed(digits)
<list_reverseiterator object at 0x10b261a50>
>>> list(reversed(digits))
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
>>> digits
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Когда вы вызываете reversed() со списком в качестве аргумента, вы получаете объект обратного итератора. Этот итератор возвращает значения из входного списка в обратном порядке. В этом примере вы используете конструктор list() для использования итератора и получения обратных данных в виде списка.
Примечание: Чтобы глубже ознакомиться с реверсивными списками в Python, ознакомьтесь с Реверсивными списками Python: за пределами .reverse() и reversed().
Функция reversed() не изменяет входной объект. Обычно вы используете reversed() в циклах как способ перебора ваших данных в обратном порядке. Если вам нужно сохранить ссылку на ваши данные, то вы можете использовать list() и присвоить возвращаемое значение новой переменной, которая будет полностью независима от вашей исходной последовательности.
Важно отметить, что reversed() лениво извлекает элементы из входной последовательности. Это означает, что если что-то изменится во входной последовательности во время обратного процесса, то эти изменения отразятся на конечном результате:
>>> numbers = [1, 2, 3]
>>> reversed_numbers = reversed(numbers)
>>> next(reversed_numbers)
3
>>> numbers[1] = 222
>>> next(reversed_numbers)
222
>>> next(reversed_numbers)
1
В этом примере вы используете встроенную функцию next(), чтобы использовать значение итератора по значению. Первый вызов next() возвращает последний элемент из numbers. Затем вы изменяете значение второго элемента с 2 на 222. При повторном вызове next() вы получите 222 вместо 2. Это связано с тем, что reversed() не создает копию входного итеративного параметра. Вместо этого он работает со ссылкой на него.
Функция reversed() полезна, когда вы хотите выполнить итерацию по списку в обратном порядке, не изменяя исходный список. Что делать, если у вас есть список, и по какой-то причине вам необходимо постоянно изменять его содержимое? В этом случае вы можете использовать метод .reverse():
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> digits.reverse()
>>> digits
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Метод .reverse() изменяет существующий список на противоположный. Это означает, что если вы вызовете .reverse() для существующего списка, то изменения отразятся на базовом списке.
Имейте в виду, что в то время как reversed() возвращает итератор, метод .reverse() возвращает None. Такое поведение может быть источником незначительных ошибок, когда вы начинаете использовать списки. Рассмотрим следующий код:
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> reversed_digits = digits.reverse()
>>> reversed_digits is None
True
В этом примере reversed_digits не выводит список из перевернутых цифр. Вместо этого выводится None, поскольку .reverse() изменяет исходный список и не имеет возвращаемого значения.
Наконец, нарезка - это еще один метод, который можно использовать для получения обратной копии существующего списка. Для этого вы можете использовать следующую операцию нарезки:
>>> digits = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> digits[::-1]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
Вариант [::-1] оператора разбиения на части творит чудеса в этом коде. С помощью этого оператора вы создаете обратную копию исходного списка. Но как это работает? Третий индекс, step, обычно является положительным числом, поэтому при обычной операции нарезки элементы извлекаются слева направо.
Устанавливая для step отрицательное значение, например -1, вы указываете оператору нарезки извлекать элементы справа налево. Вот почему в приведенном выше примере вы получаете обратную копию digits.
Сортировка списка: sorted() и .sort()
Если вам нужно отсортировать список значений без изменения исходного списка, вы можете воспользоваться встроенной функцией sorted(). Эта функция принимает повторяющийся набор значений и возвращает список отсортированных значений:
>>> numbers = [2, 9, 5, 1, 6]
>>> sorted(numbers)
[1, 2, 5, 6, 9]
>>> numbers
[2, 9, 5, 1, 6]
Когда вы передаете список в sorted(), в результате вы получаете список отсортированных значений. Функция не изменяет исходные данные в вашем списке.
Примечание: Важно отметить, что sorted() возвращает список, а не итератор. Это поведение отличается от reversed(), которое возвращает итератор вместо списка.
Как вы можете видеть из приведенного выше примера, Python сортирует числа в соответствии с их конкретными значениями. Когда дело доходит до сортировки строк, все может немного запутаться. Рассмотрим следующий пример:
>>> words = ["Hello,", "World!", "I", "am", "a", "Pythonista!"]
>>> sorted(words)
['Hello,', 'I', 'Pythonista!', 'World!', 'a', 'am']
Что? Список отсортирован не в алфавитном порядке. Почему? Python сортирует строки посимвольно, используя для каждого символа Юникод кодовую точку. Потому что заглавные буквы идут перед строчными в наборе символов Python по умолчанию , UTF-8, в итоге получается "Hello" в первом позиции и "am" в последней.
Примечание: Чтобы глубже разобраться в инструментах сортировки, ознакомьтесь с Как использовать sorted() и .sort() в Python.
Вы можете использовать встроенную функцию ord(), чтобы получить кодовую точку Unicode символа в Python:
>>> ord("H")
72
>>> ord("a")
97
Как вы можете убедиться в этом фрагменте кода, в таблице Unicode заглавная буква "H" стоит перед строчной "a". Вот почему в приведенном выше примере вы получаете "Hello" перед "am".
По умолчанию функция sorted() сортирует элементы списка в порядке возрастания. Если вам нужно отсортировать элементы в порядке убывания, вы можете использовать reverse аргумент, содержащий только ключевое слово. По умолчанию этот аргумент имеет значение False. Если вы установите значение True, то получите данные в порядке убывания:
>>> numbers = [2, 9, 5, 1, 6]
>>> sorted(numbers, reverse=True)
[9, 6, 5, 2, 1]
Установив для аргумента reverse значение True, вы указываете sorted() на сортировку входных данных в обратном порядке. Разве это не здорово?
Примечание: Как вы уже узнали, в списках могут храниться объекты разных типов. Однако разнородные списки часто не позволяют сортировать их содержимое:
>>> numbers = [2, "9", 5, "1", 6]
>>> sorted(numbers)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'str' and 'int'
На практике вы не найдете разнородных списков во многих случаях использования. Последовательности разнородных объектов подобны записи базы данных с несколькими известными и неизменяемыми полями. В таких случаях лучше использовать кортеж.
Чтобы проиллюстрировать, как sorted() может помочь вам в реальном мире, предположим, что вы хотите рассчитать медиану для числового набора данных или выборки. Медиана - это значение, которое находится посередине при сортировке данных. В большинстве случаев ваши данные не будут отсортированы, поэтому первым шагом будет сортировка. Затем вам просто нужно найти значение посередине.
Если число значений в вашем наборе данных четное, то медиана - это среднее значение двух значений в середине. Вот функция Python, которая позволяет вычислить медиану для выборки значений:
>>> def median(samples):
... n = len(samples)
... middle_index = n // 2
... sorted_samples = sorted(samples)
... # Odd number of values
... if n % 2:
... return sorted_samples[middle_index]
... # Even number of values
... lower, upper = middle_index - 1, middle_index + 1
... return sum(sorted_samples[lower:upper]) / 2
...
>>> median([3, 5, 1, 4, 2])
3
>>> median([3, 5, 1, 4, 2, 6])
3.5
Внутри median() вы используете sorted() для сортировки выборок в порядке возрастания. Затем вы проверяете, содержит ли ваш список нечетное количество точек данных, и в этом случае вы возвращаете непосредственно элемент в середине. Если в списке четное количество выборок, то вы вычисляете индекс двух элементов в середине, вычисляете их среднее значение и возвращаете результирующее значение.
Функция sorted() также принимает другой аргумент, содержащий только ключевое слово, который называется key. Этот аргумент позволяет указать функцию с одним аргументом, которая будет извлекать ключ сравнения из каждого элемента списка.
В качестве примера использования key предположим, что у вас есть список кортежей, в каждом из которых содержатся данные о сотруднике, включая его имя, возраст, должность и зарплату. Теперь представьте, что вы хотите отсортировать сотрудников по их возрасту.
В этой ситуации вы можете сделать что-то вроде следующего:
>>> employees = [
... ("John", 30, "Designer", 75000),
... ("Jane", 28, "Engineer", 60000),
... ("Bob", 35, "Analyst", 50000),
... ("Mary", 25, "Service", 40000),
... ("Tom", 40, "Director", 90000)
... ]
>>> sorted(employees, key=lambda employee: employee[1])
[
('Mary', 25, 'Service', 40000),
('Jane', 28, 'Engineer', 60000),
('John', 30, 'Designer', 75000),
('Bob', 35, 'Analyst', 50000),
('Tom', 40, 'Director', 90000)
]
В этом примере в аргумент key передается функция lambda. Это lambda принимает в качестве аргумента кортеж employee и возвращает значение age, которое имеет индекс 1. Затем sorted() использует это значение для сортировки кортежей.
Аргумент key весьма полезен на практике, поскольку он позволяет вам точно настроить процесс сортировки, изменив критерии сортировки в соответствии с вашими конкретными потребностями.
Если вам нужно отсортировать список на месте, а не получать новый список отсортированных данных, вы можете использовать метод .sort(). Этот метод аналогичен функции sorted():
>>> numbers = [2, 9, 5, 1, 6]
>>> numbers.sort()
>>> numbers
[1, 2, 5, 6, 9]
Основное различие между sorted() и .sort() заключается в том, что первый возвращает новый список отсортированных данных, в то время как второй сортирует целевой список на месте. Кроме того, поскольку .sort() является методом, вам необходимо вызвать его для объекта list.
Как и большинство методов list, которые запускают мутации, .sort() возвращает None. Например, в приведенном ниже коде вы сталкиваетесь с распространенной ошибкой, которая может возникнуть при работе со списками:
>>> numbers = [2, 9, 5, 1, 6]
>>> sorted_numbers = numbers.sort()
>>> sorted_numbers is None
True
Метод .sort() сортирует список на месте и возвращает None, чтобы напомнить пользователям, что он работает с помощью побочного эффекта. Вы должны помнить об этом, поскольку это может привести к незначительным ошибкам.
Вы также можете использовать аргументы reverse и key только для ключевых слов с .sort(). Они имеют то же значение и функциональность, что и эквивалентные аргументы в функции sorted(). Попробуйте их!
Просмотр списков
Когда вы работаете со списками в Python, одной из наиболее распространенных задач, которые вам придется выполнять, является просмотр списка во время выполнения некоторого преобразования данных или использования данных для других целей.
Чтобы просмотреть список, вам понадобится цикл, который будет проходить по каждому элементу от начала до конца списка. В Python есть несколько конструкций, которые позволяют это сделать. Наиболее популярными из них являются for циклы и обработка списков. Вы также можете использовать некоторые из инструментов функционального программирования на Python для обхода списков.
В следующих разделах вы узнаете, как просматривать существующий список с помощью этих инструментов. Для начала вы начнете с циклов for.
Использование цикла for для перебора списка
Рекомендуемый способ перебора списка - использовать цикл for. Такой цикл в Python довольно удобочитаем и понятен. Вот как это происходит:
>>> colors = [
... "red",
... "orange",
... "yellow",
... "green",
... "blue",
... "indigo",
... "violet"
... ]
>>> for color in colors:
... print(color)
...
red
orange
yellow
green
blue
indigo
violet
Чтобы использовать цикл for со списком, вы помещаете список после ключевого слова in и указываете подходящую переменную цикла. Вам не кажется, что этот цикл прекрасен? Его значение понятно, и читается оно как обычный английский. Это здорово!
Циклы for в Python по своей сути готовы непосредственно работать с любым повторяющимся вводом. В этом примере вы используете список, но он будет работать так же с кортежем, строкой, набором или любым другим повторяемым элементом.
В приведенном выше примере целевой итерацией является ваш список colors. Переменная цикла color содержит текущий цвет на каждой итерации, и вы можете обрабатывать каждый цвет в теле цикла по мере необходимости. Обратите внимание, что если в качестве имени вашего списка используется существительное во множественном числе, то переменная цикла может использовать то же имя в единственном числе. Эта крошечная деталь улучшит читаемость вашего цикла.
Шаблон кодирования, который вы обычно замечаете в коде людей, работающих на других языках программирования, заключается в том, что они обычно перебирают списки, используя цикл for, который выглядит примерно так:
>>> for i in range(len(colors)):
... print(colors[i])
...
red
orange
yellow
green
blue
indigo
violet
Этот цикл выполняет итерацию по диапазону целых чисел от 0 до длины целевого списка. На каждой итерации вы используете текущий индекс для доступа к соответствующему элементу в базовом списке. Несмотря на то, что этот цикл работает, он не основан на Python и считается плохой практикой.
Вам не обязательно использовать диапазон индексов для выполнения итерации по списку в цикле for. Просто продолжайте и используйте свой список непосредственно в определении цикла. Python позаботится обо всем остальном.
Некоторые люди возразят, что во многих ситуациях вам нужно знать индекс текущего элемента, чтобы иметь возможность выполнять некоторые вычисления. Это верно! Это особенно актуально, когда вы имеете дело со сложными алгоритмами, работающими с индексами. В таких случаях Python также поможет вам. Он предлагает встроенную функцию enumerate(), которую вы можете использовать, как показано в следующем примере:
>>> for i, color in enumerate(colors):
... print(f"{i} is the index of '{color}'")
...
0 is the index of 'red'
1 is the index of 'orange'
2 is the index of 'yellow'
3 is the index of 'green'
4 is the index of 'blue'
5 is the index of 'indigo'
6 is the index of 'violet'
Функция enumerate() принимает значение iterable и возвращает итератор. Этот итератор выдает кортежи из двух элементов по запросу. Каждый кортеж будет содержать индекс и связанный с ним элемент.
Примечание: Функция enumerate() также принимает второй аргумент с именем start, который является необязательным и по умолчанию равен 0. Этот аргумент позволяет вам указать другую начальную точку для подсчета элементов. Однако, если вы установите для этого аргумента значение, отличное от 0, то результирующие значения не будут соответствовать индексам элементов в базовом списке.
Чтобы узнать больше о enumerate(), ознакомьтесь с Python enumerate(): Упрощение циклов, для которых требуются счетчики.
Python предоставляет множество других инструментов, которые вы можете использовать при итерации по списку значений. Например, вы можете использовать reversed() для итерации по списку в обратном порядке:
>>> for color in reversed(colors):
... print(color)
...
violet
indigo
blue
green
yellow
orange
red
В этом цикле вы используете функцию reversed() для просмотра списка цветов в обратном порядке, что может быть обычным требованием в вашем коде.
Еще одна распространенная необходимость - просмотреть список в отсортированном порядке. В этой ситуации вы можете использовать своего старого знакомого sorted(), как в приведенном ниже коде:
>>> numbers = [2, 9, 5, 1, 6]
>>> for number in sorted(numbers):
... print(number)
...
1
2
5
6
9
Функция sorted() позволяет получить новый список отсортированных данных из исходного списка. Затем вы выполняете итерацию по новому отсортированному списку, как обычно.
Если вы продолжите копаться в наборе инструментов Python, то найдете множество других инструментов, которые позволят вам по-разному просматривать ваши списки. Например, у вас будет функция zip(), которая позволяет вам выполнять итерацию по нескольким спискам параллельно:
>>> integers = [1, 2, 3]
>>> letters = ["a", "b", "c"]
>>> floats = [4.0, 5.0, 6.0]
>>> for i, l, f in zip(integers, letters, floats):
... print(i, l, f)
...
1 a 4.0
2 b 5.0
3 c 6.0
В этом примере вы используете zip() для параллельной итерации по трем спискам. Функция zip() возвращает итератор кортежей. Элементы каждого кортежа берутся из входных итераций. В этом примере кортежи объединяют элементы из integers, letters, и floats.
Примечание: Чтобы более подробно ознакомиться с использованием встроенной функции zip(), ознакомьтесь с Использованием функции Python zip() Функция для параллельной итерации.
До этого момента все ваши примеры с обходом списка выполнялись по списку без внесения каких-либо изменений в сам список. Изменение списка во время итерации может привести к неожиданному поведению и ошибкам, поэтому избегайте этой практики. Как правило, если вам нужно изменить содержимое списка в цикле, сначала сделайте копию этого списка.
Допустим, у вас есть список чисел, и вы хотите удалить только нечетные значения. В этой ситуации вы можете попробовать что-то вроде этого в качестве первой попытки:
>>> numbers = [2, 9, 5, 1, 6]
>>> for number in numbers:
... if number % 2:
... numbers.remove(number)
...
>>> numbers
[2, 5, 6]
К сожалению, только 9 и 1 были удалены, в то время как 5 остались в вашем списке. Это неожиданное и некорректное поведение произошло из-за того, что удаление элементов из списка приводит к изменению их индексов, что влияет на индексы внутри запущенного цикла for. Вы можете избежать этой проблемы несколькими способами.
Например, вы можете выполнить итерацию по копии исходного списка:
>>> numbers = [2, 9, 5, 1, 6]
>>> for number in numbers[:]:
... if number % 2:
... numbers.remove(number)
...
>>> numbers
[2, 6]
На этот раз результат правильный. Вы используете оператор [:] для создания неполной копии вашего списка. Эта копия позволяет выполнять итерацию по исходным данным безопасным способом. Как только у вас будет копия, вы введете ее в цикл for, как и раньше.
В качестве альтернативы вы можете выполнить итерацию по списку в обратном порядке:
>>> numbers = [2, 9, 5, 1, 6]
>>> for number in reversed(numbers):
... if number % 2:
... numbers.remove(number)
...
>>> numbers
[2, 6]
При удалении только последнего элемента из правого конца списка на каждой итерации длина списка изменяется, но индексация остается неизменной. Это позволяет корректно сопоставлять индексы с соответствующими элементами списка.
Обратите внимание, что это был всего лишь иллюстративный пример, основанный на выборочных данных. Помните, что при вызове .remove() удаляется первое вхождение данного значения, начиная с левой части списка, вместо последнего. Если бы в списке были повторяющиеся значения, то элементы списка были бы удалены в другом порядке.
Хотя изменение элементов списка во время итерации является меньшей проблемой, чем их удаление, это также не считается хорошей практикой. Обычно более желательно создать совершенно новый список и заполнить его преобразованными значениями:
>>> numbers_as_strings = ["2", "9", "5", "1", "6"]
>>> numbers_as_integers = []
>>> for number in numbers_as_strings:
... numbers_as_integers.append(int(number))
...
>>> numbers_as_integers
[2, 9, 5, 1, 6]
В этом примере показан довольно распространенный шаблон в Python. Шаблон заключается в создании пустого списка и последующем его заполнении в цикле. Вы найдете этот шаблон в нескольких кодовых базах Python. Это обеспечивает интуитивно понятный и читаемый способ заполнения списка с нуля. Однако вы часто обнаружите, что можете заменить этот шаблон чем-то еще лучшим, а именно пониманием списка.
Создание Новых Списков С Пониманием
Обработка списков - еще один отличный и популярный способ просмотра ваших списков. Обработка - это, по сути, инструмент преобразования списков. Они позволяют создавать списки с преобразованными данными из другого списка или с возможностью повторения.
Чтобы понять, как понимание может помочь вам преобразовать ваши списки, обратитесь к примеру, в котором у вас есть список чисел в виде строк и вы хотите преобразовать их в целые числа. Вы можете решить эту задачу, используя следующее понимание:
>>> numbers = ["2", "9", "5", "1", "6"]
>>> numbers = [int(number) for number in numbers]
>>> numbers
[2, 9, 5, 1, 6]
Это понимание повторяет значения в вашем исходном списке. Выражение в понимании выполняет преобразование из строки в целое число. Конечным результатом является новый объект list, который вы присваиваете обратно переменной numbers.
Обратите внимание, что это понимание эквивалентно циклу с функцией enumerate():
>>> numbers = ["2", "9", "5", "1", "6"]
>>> for i, number in enumerate(numbers):
... numbers[i] = int(number)
...
>>> numbers
[2, 9, 5, 1, 6]
Цикл более подробный и сложный, потому что вам нужно вызвать enumerate() и объявить дополнительную индексирующую переменную i. С другой стороны, цикл изменяет исходный список на месте, в то время как понимание списка создает новый список.
Вы также можете использовать общие сведения для фильтрации существующих списков. Например, предположим, что у вас есть список целочисленных значений и вы хотите создать новый список, содержащий только четные значения из вашего исходного списка:
>>> integers = [20, 31, 52, 6, 17, 8, 42, 55]
>>> even_numbers = [number for number in integers if number % 2 == 0]
>>> even_numbers
[20, 52, 6, 8, 42]
Предложение if в этом списке "понимание" работает как фильтр, который выбирает только четные числа из ваших исходных данных. Как бы вы написали аналогичное "понимание" для получения нечетных чисел?
Обработка Списков С помощью Функциональных Инструментов
Вы также можете воспользоваться некоторыми инструментами функционального программирования на Python, такими как map() и filter(), для просмотра списка значений. Эти функции имеют внутренний цикл, который выполняет итерацию по элементам входного параметра iterable и возвращает заданный результат.
Например, функция map() принимает в качестве аргументов функцию преобразования и iterable. Затем он возвращает итератор, который выдает элементы, полученные в результате применения функции к каждому элементу в iterable.
Используя map(), вы можете преобразовать свой список чисел в целые числа со следующим кодом:
>>> numbers = ["2", "9", "5", "1", "6"]
>>> numbers = list(map(int, numbers))
>>> numbers
[2, 9, 5, 1, 6]
В этом примере map() применяется int() к каждому элементу в numbers в цикле. Поскольку map() возвращает итератор, вы использовали конструктор list() для использования итератора и отображения результата в виде списка.
Примечание: Для более глубокого ознакомления с использованием функции map() ознакомьтесь с в map()Python: Обработка итераций Без цикла.
Если вам нужно отфильтровать значения из существующего списка, то вы можете воспользоваться встроенной функцией filter(). Эта функция принимает два аргумента: функцию с предикатом и итерацию данных. Затем она возвращает итератор, который выдает элементы, соответствующие заданному условию, которое проверяется функцией предиката.
Вот как filter() работает на практике:
>>> integers = [20, 31, 52, 6, 17, 8, 42, 55]
>>> even_numbers = list(filter(lambda number: number % 2 == 0, integers))
>>> even_numbers
[20, 52, 6, 8, 42]
В этом примере вы используете filter() для просмотра вашего списка integers и извлечения тех значений, которые удовлетворяют условию четности чисел.
Примечание: Для более глубокого ознакомления с использованием функции filter() ознакомьтесь с Python's filter(): Извлечение значений Из повторяющихся значений.
В Python вы найдете несколько других встроенных функций и функций стандартной библиотеки, которые позволяют вам просматривать список значений и получать конечный результат в виде другого списка, итератора или даже отдельного значения. Некоторые примеры включают в себя reduce(), min() и max(), sum(), all(), и any(). Обратите внимание, что некоторые из этих функций на самом деле не являются функциональными инструментами программирования, но они выполняют внутреннюю итерацию по списку ввода.
Изучение других возможностей списков
Программа Python list обладает впечатляющим набором функций, что делает ее универсальной, гибкой и мощной структурой данных. На данный момент вы ознакомились с большинством из этих функций. Вы научились создавать списки, добавлять и удалять элементы из своих списков, просматривать существующие списки в цикле или на уровне понимания и многому другому.
В следующих разделах вы узнаете о некоторых дополнительных функциях, которые делают списки еще более функциональными. Вы узнаете, как находить элементы в списке, определять минимальное и максимальное значения и получать длину списка. Вы также подробно изучите, как Python сравнивает списки друг с другом.
Поиск элементов в списке
В Python есть несколько инструментов, которые позволяют вам искать значения в существующем списке. Например, если вам нужно только быстро определить, присутствует ли значение в списке, но вам не нужно извлекать значение, вы можете использовать оператор in или not in, который запустит членство проверьте по своему списку.
Примечание: Чтобы узнать больше об операторах in и not in и о том, как выполнять тесты на членство, ознакомьтесь с Операторы “in” и “not in” в Python: проверьте наличие членства. Эти операторы могут быть очень полезны, когда вам нужно проверить, содержит ли строка Python подстроку.
Как следует из названия, тест на членство - это Логический тест, который позволяет вам выяснить, является ли объект членом группы коллекция значений. Общий синтаксис для проверки принадлежности к объектам списка выглядит примерно так:
item in list_object
item not in list_object
Первое выражение позволяет определить, находится ли item в list_object. Он возвращает True, если находит item в list_object или False в противном случае. Второе выражение работает противоположным образом, позволяя вам проверить, не находится ли item в list_object. В этом случае вы получите True, если item не отображается в list_object.
Вот как тесты на членство работают на практике:
>>> usernames = ["john", "jane", "bob", "david", "eve"]
>>> "linda" in usernames
False
>>> "linda" not in usernames
True
>>> "bob" in usernames
True
>>> "bob" not in usernames
False
В этом примере у вас есть список пользователей, и вы хотите определить, зарегистрированы ли некоторые конкретные пользователи в вашей системе.
В первом тесте используется in, чтобы проверить, зарегистрирован ли пользователь linda. Вы получаете False, потому что этот пользователь не зарегистрирован. Во втором тесте используется оператор not in, который возвращает True в качестве подтверждения того, что linda не является одним из ваших пользователей.
Метод .index() - это еще один инструмент, который вы можете использовать для поиска заданного значения в существующем списке. Этот метод обходит список в поисках указанного значения. Если значение есть в списке, то метод возвращает его индекс. В противном случае возникает ValueError исключение:
>>> usernames = ["john", "jane", "bob", "david", "eve"]
>>> usernames.index("eve")
4
>>> usernames.index("linda")
Traceback (most recent call last):
...
ValueError: 'linda' is not in list
При первом вызове .index() вы получаете индекс, по которому вы можете найти пользователя с именем "eve". Вы можете использовать этот индекс позже в своем коде для доступа к фактическому объекту по мере необходимости. При втором вызове, поскольку пользователя "linda" нет в списке, вы получаете сообщение ValueError с пояснительным сообщением.
Обратите внимание, что если целевое значение вашего поиска появляется в списке несколько раз, то .index() вернет индекс первого вхождения:
>>> sample = [12, 11, 10, 50, 14, 12, 50]
>>> sample.index(12)
0
>>> sample.index(50)
3
Метод .index() возвращает значение, как только находит входное значение в базовом списке. Таким образом, если значение встречается много раз, то .index() всегда возвращает индекс первого вхождения.
Списки предоставляют еще один метод, который можно использовать для поиска. Метод называется .count() и позволяет проверить, сколько раз данное значение встречается в списке:
>>> sample = [12, 11, 10, 50, 14, 12, 50]
>>> sample.count(12)
2
>>> sample.count(11)
1
>>> sample.count(100)
0
Метод .count() принимает элемент в качестве аргумента и возвращает количество раз, когда входной элемент появляется в базовом списке. Если элемента нет в списке, то вы получаете 0.
Поиск определенного значения в списке Python - операция не из дешевых. Временная сложность проверки .index(), .count(), и членства в списках составляет O(n). Такая линейная сложность может быть приемлемой, если вам не нужно выполнять много операций поиска. Однако это может негативно сказаться на производительности, если вам потребуется выполнить много таких операций.
Получение длины, максимума и минимума списка
При работе со списками Python вы столкнетесь с необходимостью получения описательной информации о данном списке. Например, вы можете захотеть узнать количество элементов в списке, которое называется длиной списка . Вы также можете захотеть определить наибольшее и наименьшее значения в списке. Во всех этих случаях Python вам поможет.
Чтобы определить длину списка, вы будете использовать встроенную функцию len(). В следующем примере вы используете эту функцию в качестве промежуточного шага для вычисления средней оценки учащегося:
>>> grades = [80, 97, 86, 100, 98, 82]
>>> n = len(grades)
>>> sum(grades) / n
90.5
Здесь вы рассчитываете средний балл учащегося. Для этого вы используете функцию sum(), чтобы получить общую сумму, и функцию len(), чтобы получить количество оцениваемых предметов, которое соответствует длине вашего списка grades.
Важно отметить, что, поскольку списки отслеживают свою длину, вызов len() выполняется довольно быстро, а временная сложность составляет O(1). Таким образом, в большинстве случаев вам не нужно сохранять возвращаемое значение len() в промежуточной переменной, как вы делали в примере выше.
Примечание: Чтобы узнать больше об использовании функции len(), ознакомьтесь с Использованием функции len() в Python.
Еще одной частой задачей, которую вы будете выполнять со списками, особенно со списками числовых значений, является поиск минимальных и максимальных значений. Для этого вы можете воспользоваться встроенными функциями min() и max():
>>> min([3, 5, 9, 1, -5])
-5
>>> max([3, 5, 9, 1, -5])
9
В этих примерах вы вызываете min() и max() со списком целых чисел. Вызов min() возвращает наименьшее число в списке ввода, -5. Напротив, вызов max() возвращает наибольшее число в списке, или 9.
Примечание: Для более глубокого ознакомления с использованием встроенных функций min() и max() ознакомьтесь с Python’s min() и max(): Найдите наименьшее и наибольшее значения..
В целом, списки Python поддерживают функции len(), min(), и max(). Используя len(), вы получаете длину списка. Используя min() и max(), вы получаете наименьшее и наибольшее значения в списке. Все эти значения могут быть весьма полезны при работе со списками в вашем коде.
Сравнение списков
Вы также можете столкнуться с необходимостью сравнения списков. К счастью, объекты списка поддерживают стандартные операторы сравнения. Все эти операторы работают путем постатейного сравнения двух задействованных списков:
>>> [2, 3] == [2, 3]
True
>>> [5, 6] != [5, 6]
False
>>> [5, 6, 7] < [7, 5, 6]
True
>>> [5, 6, 7] > [7, 5, 6]
False
>>> [4, 3, 2] <= [4, 3, 2]
True
>>> [4, 3, 2] >= [4, 3, 2]
True
Когда вы сравниваете два списка, Python использует лексикографический порядок. Сравниваются первые два элемента из каждого списка. Если они отличаются, это различие определяет результат сравнения. Если они равны, то Python сравнивает следующие два элемента и так далее, пока оба списка не будут исчерпаны.
В этих примерах вы сравниваете списки чисел, используя стандартные операторы сравнения. В первом выражении, приведенном выше, Python сравнивает 2 и 2, которые равны. Затем он сравнивает 3 и 3 и приходит к выводу, что оба списка равны.
Во втором выражении Python сравнивает 5 и 5. Они равны, поэтому Python должен сравнивать 6 и 6. Они тоже равны, так что конечный результат таков False.
В остальных выражениях Python использует тот же шаблон для сравнения. Короче говоря, Python сравнивает списки по элементам, используя лексикографическое сравнение. Первое различие определяет результат.
Вы также можете сравнить списки разной длины:
>>> [5, 6, 7] < [8]
True
>>> [5, 6, 7] == [5]
False
В первом выражении вы получаете True в результате, потому что 5 меньше, чем 8. Этого факта достаточно для того, чтобы Python выполнил вычисление. Во втором примере вы получите False. Этот результат имеет смысл, поскольку списки не имеют одинаковой длины, поэтому они не могут быть одинаковыми.
Как вы можете видеть, сравнение списков может быть сложной задачей. Это также дорогостоящая операция, которая в худшем случае требует обхода целых двух списков. При сравнении списков строк все становится еще сложнее и дороже. В этой ситуации Python также придется сравнивать строки посимвольно, что увеличивает стоимость операции.
Распространенные ошибки в списках Python
Если вы новичок в Python и начинаете работать со списками, то вам следует обратить внимание на несколько подводных камней, которые могут вызвать незначительные проблемы в вашем коде. На данный момент вы узнали все, что вам нужно, чтобы разобраться в большинстве этих проблем, поэтому вот краткое изложение наиболее распространенных из них:
- Путаница между псевдонимами списка и копиями: Это может вызвать проблемы, поскольку изменения одного псевдонима влияют на другие. Ознакомьтесь с практическими примерами этой проблемы в разделе Псевдонимы списка.
- Забывая о том, что большинство методов
listизменяют существующий список и возвращаютNone, а не новый список: это обычно приводит к проблемам при назначении возвращает значение метода list в переменную, думая, что у вас есть новый список, но на самом деле вы получаетеNone. Ознакомьтесь с разделом Реверсирование и сортировка списков, в котором приведены практические примеры этого решения. - Путать
.append()с.extend(): Это может вызвать проблемы, поскольку.append()добавляет один элемент в конец списка, в то время как метод.extend()распаковывает и добавляет несколько элементов. Подробнее о том, как работают эти методы, читайте в разделе Динамически увеличивающиеся и сокращающиеся списки. - Использование пустого списка в качестве значения аргумента по умолчанию в определениях функций: Это может привести к неожиданному поведению, поскольку значения аргументов по умолчанию определяются при первом анализе функции в Python.
Вы уже знаете объяснение первых трех пунктов этого списка. Итак, вам нужно только углубиться в последний пункт. Почему вам следует избегать использования пустого списка — или списка в целом — в качестве значения аргумента по умолчанию? Чтобы ответить на этот вопрос, рассмотрим следующую функцию игрушки:
>>> def append_to(item, target=[]):
... target.append(item)
... return target
...
Эта функция добавляет item в конец target, который по умолчанию является пустым списком. На первый взгляд может показаться, что последовательные вызовы append_to() будут возвращать списки из одного элемента, как в следующем гипотетическом примере:
>>> append_to(1)
[1]
>>> append_to(2)
[2]
>>> append_to(3)
[3]
Но поскольку Python определяет значение аргумента по умолчанию при первом анализе функции и не перезаписывает его при каждом вызове, вы будете работать с одним и тем же объектом list при каждом вызове. Таким образом, описанное выше поведение не работает. Вместо этого вы получаете следующее:
>>> append_to(1)
[1]
>>> append_to(2)
[1, 2]
>>> append_to(3)
[1, 2, 3]
Список target запоминает данные между вызовами. Это происходит потому, что вы используете тот же объект list, который отображается как значение по умолчанию в определении функции.
Чтобы избежать этой проблемы, вы можете использовать None в качестве значения по умолчанию:
>>> def append_to(item, target=None):
... if target is None:
... target = []
... target.append(item)
... return target
...
>>> append_to(1)
[1]
>>> append_to(2)
[2]
>>> append_to(3)
[3]
Отлично! Вы решили проблему. Теперь ваша функция возвращает списки из одного элемента, как и ожидалось. Это потому, что функция не сохраняет состояние между вызовами.
Создание подкласса встроенного list класса
Иногда вам может потребоваться создать класс, похожий на список, который либо расширяет возможности list, либо настраивает некоторые из его стандартных поведений. Долгое время было невозможно напрямую наследовать встроенные типы Python, реализованные в C. В Python 2.2 исправлена эта проблема. Теперь вы можете подклассифицировать встроенные типы, включая list.
Чтобы понять, как это сделать, предположим, что вы работаете над приложением для обработки оценок ваших учеников и составления отчетов о них. Вы хотите создать объект, похожий на список, для хранения оценок. В вашем пользовательском списке должен быть метод, который вычисляет среднюю оценку. В этой ситуации вы можете создать подкласс list, подобный следующему:
>>> class GradeList(list):
... def average(self):
... return sum(self) / len(self)
...
>>> grades = GradeList([80, 97, 86, 100, 98])
>>> grades.append(82)
>>> grades.average()
90.5
>>> grades[0] = 95
>>> grades.average()
93.0
В этом фрагменте кода вы наследуете непосредственно от list. Вы можете создать экземпляр GradeList с помощью итерации значений grade. Обратите внимание, что класс работает как обычный список. Вы можете использовать методы list, такие как .append() и .extend(), выполнять индексацию и нарезку и так далее.
Кроме того, в классе появился новый метод .average(). Этот метод не является частью стандартной функциональности списка. Итак, этот метод расширяет list новыми функциональными возможностями.
Приведенный выше пример является относительно безопасным способом создания подкласса list, поскольку он не затрагивает какого-либо стандартного поведения. Напротив, все становится немного сложнее, когда вам нужно настроить стандартное поведение list.
Например, предположим, что вы хотите продолжить совершенствовать свой класс GradeList и подумываете о добавлении некоторых функций проверки вводимых данных. Вы хотите, чтобы ваш класс проверял любую входную оценку, чтобы убедиться, что это число от 1 до 100.
В этой ситуации вам необходимо внести существенные изменения в стандартную функциональность list. Вам потребуется изменить все методы добавления новых элементов в ваши списки. Эти методы включают в себя следующие специальные методы:
.__init__(), который инициализирует все новые экземпляры класса..__setitem__(), который поддерживает операции индексации.
Вам также придется настроить методы .append(), .extend(), и .insert(). Кроме того, если вы хотите, чтобы ваш класс проверял вводимые данные при выполнении конкатенаций, вам придется обновить другие специальные методы, включая .__add__(), .__radd__(), и .__iadd__().
Вот возможное, но минимальное обновление вашего класса GradeList:
# grades.py
class GradeList(list):
def __init__(self, grades):
grades = [self._validate(grade) for grade in grades]
super().__init__(grades)
def __setitem__(self, index, grade):
if isinstance(index, slice):
start, stop, step = index.indices(len(self))
grades = [self._validate(grade) for grade in grade]
return super().__setitem__(slice(start, stop, step), grades)
super().__setitem__(index, self._validate(grade))
def __add__(self, grades):
grades = [self._validate(grade) for grade in grades]
grades = super().__add__(grades)
return self.__class__(grades)
__radd__ = __add__
def __iadd__(self, grades):
grades = [self._validate(grade) for grade in grades]
return super().__iadd__(grades)
def append(self, grade):
return super().append(self._validate(grade))
def extend(self, grades):
grades = [self._validate(grade) for grade in grades]
return super().extend(grades)
def average(self):
return sum(self) / len(self)
def _validate(self, value):
if not isinstance(value, (int, float)):
raise TypeError("grades must be numeric")
if not (0 <= value <= 100):
raise ValueError("grade must be between 0 and 100")
return value
Этот класс расширяет все стандартные методы, которые добавляют элементы в обычный список. Все эти методы используют вспомогательный метод ._validate(), чтобы гарантировать, что входные значения являются допустимыми. Метод проверяет, являются ли значения числами. Он также проверяет, находятся ли они между 0 и 100.
Как вы можете заключить из приведенного выше кода, изменение стандартного поведения списка в подклассе требует большой работы и очень часто приводит к ошибкам.
Вот несколько примеров того, как приведенный выше класс работает на практике:
>>> from grades import GradeList
>>> grades = GradeList([80, 97, 86, 200])
Traceback (most recent call last):
...
ValueError: grade must be between 0 and 100
>>> grades = GradeList([80, 97, 86, 100])
>>> grades.average()
90.75
>>> grades[0] = 955
Traceback (most recent call last):
...
ValueError: grade must be between 0 and 100
>>> grades[0] = 95
>>> grades
[95, 97, 86, 100]
>>> grades.append(-98)
Traceback (most recent call last):
...
ValueError: grade must be between 0 and 100
>>> grades.append(98)
>>> grades
[95, 97, 86, 100, 98]
>>> grades += [88, 100]
>>> grades
[95, 97, 86, 100, 98, 88, 100]
>>> grades[:3] = [100, 100, 100]
>>> grades
[100, 100, 100, 100, 98, 88, 100]
>>> grades.average()
98.0
Отлично! Ваш класс GradeList работает должным образом. Он генерирует исключение всякий раз, когда вы пытаетесь ввести недопустимую оценку, используя любую из обычных операций, которые добавляют элементы в существующий список.
Примечание: Для более глубокого погружения в создание спископодобных классов ознакомьтесь с Пользовательскими списками Python: наследование от list vs UserList.
Создание подклассов встроенного класса list может быть как полезным, так и сложным делом. Хотя вы можете расширить список с относительно небольшими усилиями, настройка его стандартного поведения сопряжена с серьезными трудностями, как вы узнали из этого раздела. Итак, прежде чем принимать решение о создании подкласса list, подумайте, могут ли другие методы, такие как композиция, быть лучшим решением.
Приведение списков В действие
На данный момент вы многое узнали о списках Python, их возможностях и функциональности. Вы узнали, как создавать новые списки, создавать копии существующих списков, добавлять элементы в свои списки, просматривать существующие списки в цикле или с помощью аналогичного инструмента, создавать пользовательские классы, подобные спискам, и многое другое.
Теперь у вас есть все необходимые знания о списках, чтобы эффективно решать с их помощью распространенные практические задачи программирования на Python. В следующих разделах вы приведете несколько примеров, которые помогут вам закрепить полученные знания и понять, как использовать списки в реальной жизни.
Удаление повторяющихся Элементов из списка
Удаление повторяющихся элементов из существующего списка часто является требованием в Python. Вероятно, вам удастся найти несколько подходов к решению этой проблемы. Использование объекта set может быть одним из них, поскольку наборы не допускают повторения элементов. Итак, вы можете сделать что-то вроде этого:
>>> list(set([2, 4, 5, 2, 3, 5]))
[2, 3, 4, 5]
Это решение работает, потому что вы получаете новый список уникальных значений. Однако в наборах Python не обязательно сохранять порядок содержащихся в них элементов. Поэтому, возможно, вы захотите использовать другой метод, который сохранит исходный порядок вставки.
Возможно, самый безопасный способ решить проблему удаления повторяющихся элементов из списка - это создать новый список с уникальными значениями из исходного списка. Это можно сделать с помощью функции, подобной следующей:
>>> def get_unique_items(list_object):
... result = []
... for item in list_object:
... if item not in result:
... result.append(item)
... return result
...
>>> get_unique_items([2, 4, 5, 2, 3, 5])
[2, 4, 5, 3]
В этой функции вы принимаете список в качестве аргумента. Затем вы определяете новый пустой список для хранения результата функции. В цикле вы выполняете итерацию по элементам входного списка. Условие проверяет, отсутствует ли текущий элемент в result. Если это так, то вы добавляете элемент с помощью .append(). После завершения цикла вы возвращаете результирующий список, который будет содержать уникальные значения.
Обратите внимание, что использование оператора not in в больших списках может быть слишком медленным из-за его линейной временной сложности. Если это так, то вы можете захотеть ввести дополнительную вспомогательную переменную для хранения копий уникальных значений в Python set:
>>> def get_unique_items(list_object):
... result = []
... unique_items = set()
... for item in list_object:
... if item not in unique_items:
... result.append(item)
... unique_items.add(item)
... return result
...
>>> len(get_unique_items(range(100_000)))
100000
Вы используете set, чтобы быстро определить, присутствует ли уже заданное значение. Наборы реализуют операторы in и not in по-разному, что делает их намного быстрее, чем их аналоги из списка. Хотя эта функция возвращает результат мгновенно, она требует в два раза больше памяти, поскольку теперь каждое значение хранится в двух местах.
Создание многомерных списков
Создание многомерного списка, такого как матрица или список списков, также может быть обычным требованием в вашем коде. Опять же, вы можете решить эту проблему множеством различных способов, в зависимости от ваших конкретных потребностей.
Быстрый и безопасный способ создания многомерного списка - это использование цикла for или алгоритма понимания. Например, предположим, что вы хотите создать матрицу числовых значений размером пять на пять и хотите инициализировать все значения как 0. Вы можете сделать что-то вроде этого:
>>> matrix = []
>>> for row in range(5):
... matrix.append([])
... for _ in range(5):
... matrix[row].append(0)
...
>>> matrix
[
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]
]
В этом примере вы сначала создаете пустой список для хранения вашей матрицы. Затем вы запускаете цикл for, который выполняется пять раз. На каждой итерации вы добавляете новый пустой список. Итак, в вашей матрице будет пять строк. Далее вы запускаете вложенный цикл, который также выполняется пять раз.
Каждый раз вы добавляете 0 в текущую строку, используя .append(). В результате вы получаете матрицу размером пять на пять, все ее значения инициализированы следующим образом 0.
Примечание: В Python вы обычно используете символ подчеркивания (_) в качестве переменной-заполнителя, когда синтаксис требует наличия переменной, а ваш код этого не делает.
Вы можете получить тот же результат, что и в приведенном выше примере, если будете понимать список следующим образом:
>>> [[0 for _ in range(5)] for _ in range(5)]
[
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]
]
В этом примере используется понимание списка, выражением которого является другое понимание списка. Внутреннее понимание предоставляет вложенные списки, в то время как внешнее понимание формирует матрицу.
Вы можете сделать приведенное выше понимание еще более кратким и читабельным, воспользовавшись оператором повторения (*), как в следующем коде:
>>> [[0] * 5 for _ in range(5)]
[
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]
]
Эта новая версия вашего понимания списка намного более удобочитаема, чем предыдущая. Для построения строк вашей матрицы используется оператор повторения. Может показаться, что в этом примере будет работать следующее:
>>> [[0] * 5] * 5
[
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]
]
Этот результат выглядит так, как вам нужно. Это список, содержащий пять вложенных списков. Однако внутренняя работа этой результирующей матрицы сильно отличается от всех предыдущих решений. Если вы измените одно значение в данной строке, то это изменение отразится на всех остальных строках:
>>> matrix = [[0] * 5] * 5
>>> matrix
[
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]
]
>>> matrix[0][0] = 1
>>> matrix
[
[1, 0, 0, 0, 0],
[1, 0, 0, 0, 0],
[1, 0, 0, 0, 0],
[1, 0, 0, 0, 0],
[1, 0, 0, 0, 0]
]
В этом примере вы пытаетесь изменить значение первого элемента в первом вложенном списке или строке. Однако на самом деле вы изменили первое значение во всех строках. Когда вы передаете список в качестве аргумента оператору повторения, вы получаете псевдонимы списка вместо копий. Таким образом, все строки в вашей матрице на самом деле являются одним и тем же списком.
Выравнивание многомерных списков
Иногда вам может потребоваться обработать данные, которые поступают в виде списка вложенных списков. Сведение этих данных в одномерный список может быть обычным требованием в таких сценариях. Сглаживая список, вы преобразуете многомерный список, такой как матрица, в одномерный список.
Примечание: Чтобы глубже разобраться в том, как сгладить список списков, ознакомьтесь с Как сгладить список списков в Python.
Например, предположим, что у вас есть следующий список списков:
[[0, 1, 2], [10, 11, 12], [20, 21, 22]]
Обработка этого списка может быть затруднена из-за его вложенной структуры. Поэтому вам нужно сгладить список и получить вместо него следующий список:
[0, 1, 2, 10, 11, 12, 20, 21, 22]
Как бы вы это сделали в Python? Вы наверняка найдете несколько решений этой проблемы. В приведенном ниже фрагменте кода приведено одно из них:
>>> matrix = [[0, 1, 2], [10, 11, 12], [20, 21, 22]]
>>> flattened_list = []
>>> for row in matrix:
... flattened_list.extend(row)
...
>>> flattened_list
[0, 1, 2, 10, 11, 12, 20, 21, 22]
В цикле for, описанном выше, вы выполняете итерацию по вложенным спискам в matrix. Затем вы используете метод .extend(), чтобы добавить содержимое текущего подсписка в flattened_list в качестве независимых элементов. В результате этого цикла получается сглаженный список.
Разбиение Списков На Блоки
Еще одним полезным навыком, связанным со списками, является разбиение существующего списка на определенное количество фрагментов. Этот навык пригодится, когда вам нужно распределить рабочую нагрузку между несколькими потоками или процессами для одновременных обработка.
Примечание: Для получения полной информации о разбиении списка или iterable на фрагменты ознакомьтесь с Как разбить список Python или Iterable на фрагменты.
Опять же, вы найдете множество решений этой проблемы. Приведенный ниже код показывает только одно из них. Обратите внимание, что вы не будете использовать какую-либо стандартную библиотеку или специализированный инструмент сторонних производителей. Вы создадите решение, основываясь на своих знаниях о списках:
>>> def split_list(list_object, chunk_size):
... chunks = []
... for start in range(0, len(list_object), chunk_size):
... stop = start + chunk_size
... chunks.append(list_object[start:stop])
... return chunks
...
>>> split_list([1, 2, 3, 4, 5, 6, 7, 8, 9], 3)
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
В этой функции вы берете список для разделения и определяете количество элементов в каждом результирующем фрагменте. Затем вы определяете новый пустой список для хранения фрагментов. Цикл for выполняет итерации по диапазону индексов, который варьируется от 0 до длины вашего входного списка. На каждой итерации выполняется переход к желаемому размеру блока.
Для извлечения фрагментов используется операция нарезки. Переменная цикла start определяет начальный индекс, в то время как переменная stop задает конечный индекс. Затем вы добавляете каждый фрагмент в свой список chunks, и все.
Использование списка в качестве стека или очереди
Вы можете использовать список Python для эмуляции структуры данных стека или очереди. Методы .append() и .pop() помогут вам в этой задаче. Например, чтобы имитировать стек или структуру данных последний пришедший первым вышедший (LIFO), вы можете использовать .append(), чтобы поместить элемент на вершину стека. Аналогично, вы можете использовать .pop() без аргументов для перемещения элементов из верхней части стека:
>>> stack = []
>>> stack.append("Copy")
>>> stack.append("Paste")
>>> stack.append("Remove")
>>> stack
['Copy', 'Paste', 'Remove']
>>> stack.pop()
'Remove'
>>> stack.pop()
'Paste'
>>> stack.pop()
'Copy'
>>> stack
[]
В этом примере вы представляете стек с помощью списка. В стеке будут храниться действия, которые вы можете отменить. Для начала вы создаете пустой список с именем stack. Затем вы помещаете гипотетические действия в стек с помощью .append(), что добавляет действия в правый конец списка.
Метод .pop() возвращает действия, чтобы вы могли их повторить. Этот метод также удаляет действия из правого конца списка в соответствии с порядком LIFO, который определяет структуру данных стека.
Примечание: Подробнее о том, что такое стеки и как их создавать в Python, читайте в статье Как реализовать стек Python.
В качестве альтернативы, если вы хотите эмулировать очередь или структуру данных "первый пришел-первый вышел" (FIFO), вы можете использовать .append() для размещения элементов в конце списка, который известен как операция поставить в очередь. Аналогично, вы можете использовать .pop() с 0 в качестве аргумента для возврата и удаления элементов из левого конца очереди, которая называется выводом из очереди:
>>> queue = []
>>> queue.append("John")
>>> queue.append("Jane")
>>> queue.append("Linda")
>>> queue
['John', 'Jane', 'Linda']
>>> queue.pop(0)
'John'
>>> queue.pop(0)
'Jane'
>>> queue.pop(0)
'Linda'
Этот список имитирует очередь людей, которые, возможно, прибывают в какое-либо место, чтобы получить какую-либо услугу. Метод .append() позволяет добавлять людей в конец очереди по мере их прибытия. Метод .pop() с 0 в качестве аргумента позволяет обрабатывать запросы пользователей с начала очереди, когда наступает их очередь на доступ к сервису. В целом, вы следуете принципу FIFO, который управляет очередями.
Примечание: Ознакомьтесь с Стеками, очередями и приоритетными очередями Python на практике, чтобы получить полное представление о стеках и очередях в Python.
Используя Python list, вы можете быстро воспользоваться преимуществами стандартной функциональности list для обеспечения базовых операций со стеком и очередями, таких как push, pop, постановка в очередь и удаление из очереди. Однако имейте в виду, что, хотя списки могут помочь вам имитировать стеки и очереди, они не оптимизированы для этих случаев использования. Использование списка в качестве очереди особенно плохо, потому что это может привести к ужасному замедлению очереди .
Решение о том, следует ли использовать списки
Как вы узнали из этого руководства, списки являются мощными, гибкими, универсальными и полнофункциональными структурами данных. Из-за их особенностей люди склонны использовать их неправильно. Да, они подходят для многих случаев использования, но иногда это не самый лучший доступный вариант.
В общем, вы должны использовать списки, когда вам нужно:
- Упорядочивайте свои данные: В списках сохраняется порядок вставки их элементов.
- Сохранение последовательности значений: Списки - отличный выбор, когда вам нужно сохранить последовательность связанных значений.
- Измените свои данные: Списки - это изменяемые типы данных, которые поддерживают множественные мутации.
- Доступ к случайным значениям по индексу: Списки обеспечивают быстрый и легкий доступ к элементам на основе их индекса.
Напротив, избегайте использования списков, когда вам нужно:
- Храните неизменяемые данные: В этом случае вам следует использовать кортеж. Они неизменяемы и более экономичны с точки зрения памяти.
- Представляют записи базы данных: В этом случае рассмотрите возможность использования кортежа или класса данных .
- Храните уникальные и неупорядоченные значения: В этом случае рекомендуется использовать набор или словарь. Наборы не допускают дублирования значений, а словари не могут содержать дублированные ключи.
- Выполните множество тестов на принадлежность, в которых элемент не имеет значения: В этом случае рассмотрите возможность использования
set. Наборы оптимизированы для такого типа операций. - Выполняйте расширенные операции с массивами и матрицей: В таких ситуациях рассмотрите возможность использования специализированных структур данных в NumPy.
- Управляйте своими данными в виде стека или очереди: В таких случаях рассмотрите возможность использования
dequeиз модуляcollectionsилиQueue,LifoQueue, илиPriorityQueue. Эти типы данных потокобезопасны и оптимизированы для быстрой вставки и удаления с обоих концов.
В зависимости от вашего конкретного сценария списки могут быть подходящим инструментом для работы, а могут и не подходить. Поэтому вы должны тщательно оценить свои потребности и рассмотреть расширенные структуры данных, подобные перечисленным выше.
Заключение
Теперь у вас есть глубокое, основательное понимание основных возможностей и функциональности Python списки. Списки есть везде. Они являются важной частью самого языка и присутствуют в стандартной библиотеке, сторонних пакетах и практически в каждом фрагменте кода на Python, который вы там найдете. Таким образом, изучение их - это фундаментальный навык, которым вы должны обладать.
В этом руководстве вы узнали, как:
- Создавайте новые списки в Python, используя различные подходы
- Доступ к одному или нескольким элементам в существующем списке
- Скопируйте, обновите, увеличьте, уменьшите и объедините существующие списки Python
- Сортировка, обратная и просмотр существующих списков с помощью встроенныхфункций и методов
- Используйте некоторые другие функции списков
Обладая всеми этими знаниями, вы будете готовы писать более качественный и эффективный код, используя преимущества списков Python. Вы также сможете принимать обоснованные решения о том, когда использовать списки в своем коде.
Бесплатный бонус: Нажмите здесь, чтобы загрузить пример кода, который сделает вас экспертом по list Python.> тип данных.
<статус завершения article-slug="python-list" class="btn-group mb-0" data-api-article-bookmark-url="/api/v1/articles/python-list/bookmark/" data-api-article-статус завершения-url="/api/v1/articles/python-list/completion_status/"> статус завершения> <кнопка поделиться bluesky-text="Интересная статья #Python от @realpython.com :" email-body="Ознакомьтесь с этой статьей о Python:%0A%0тип данных списка в%0APython: Глубокое погружение с примерами" email-subject="Статья о Python для вас" twitter-text="Интересная статья о #Python от @realpython:" url="https://realpython.com/python-list/" url-title="Список типов данных Python: Глубокое погружение с примерами"> кнопка поделиться>
Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Просмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Изучение типа данных list в Python на примерах
Back to Top