Содержание
Краткая памятка по работе со списками в Python
- Список — это динамический массив указателей на объекты в куче.
- При создании списка Python выделяет память с запасом (over-allocation).
- Память управляется через арены (256 КБ), пулы (4 КБ) и блоки.
- Счетчик ссылок немедленно освобождает память при удалении последней ссылки.
- Сборщик мусора (GC) обрабатывает циклические ссылки.
- Используйте sys.getsizeof() для оценки занимаемой памяти.
- Для однотипных данных предпочитайте array.array или numpy для экономии памяти.
- Избегайте частых вставок/удалений в середине списка — используйте collections.deque.
- Копируйте списки через list.copy() или срез [:] для создания независимой копии.
- Проверяйте наличие элемента через оператор in (работает за O(n)).
- Для поиска индекса используйте list.index() с обработкой исключения ValueError.
- Генераторы списков (list comprehensions) быстрее и читаемее циклов for.
- Срезы (slicing) создают новый список, не изменяя исходный.
- Удаляйте элементы через pop() (с конца — O(1), с начала — O(n)).
- Для сортировки используйте list.sort() (in-place) или sorted() (новый список).
Основы списков в Python: структура и применение
Структурно список представляет собой последовательность элементов, каждый из которых имеет свой индекс. Нумерация индексов начинается с 0, что типично для большинства языков программирования.
Однажды я столкнулся с задачей обработки большого массива данных о покупках в интернет-магазине. Клиент хотел понимать, какие товары часто покупают вместе. На первый взгляд, задача казалась сложной — десятки тысяч заказов, каждый с множеством товаров.
Решение пришло, когда я осознал мощь вложенных списков Python. Каждый заказ представлял собой список товаров, а все заказы образовывали список списков. Это позволило мне использовать всего несколько строк кода для анализа:
# Пример данных о заказах orders = [[«хлеб», «масло», «молоко»], [«молоко», «яйца», «сахар»], [«хлеб», «молоко», «яйца»]] # Находим товары, которые часто покупают вместе pairs = {} for order in orders: for item1 in order: for item2 in order: if item1!= item2: pair = tuple(sorted([item1, item2])) pairs[pair] = (pair, 0) + 1 # Выводим результаты for pair, count in sorted((), key=lambda x: x[1], reverse=True): print(f»{pair[0]} и {pair[1]} купили вместе {count} раз»)
Благодаря гибкости списков, задача была решена менее чем за час, а клиент получил ценную аналитику для оптимизации рекомендаций.
- Упорядоченность — элементы хранятся в определенном порядке
- Изменяемость — можно добавлять, удалять и изменять элементы
- Индексация — доступ к элементам по индексу (начиная с 0)
- Гетерогенность — могут содержать элементы разных типов
- Вложенность — списки могут содержать другие списки
Таблица №1
| Область применения | Пример использования | Преимущество списков |
|---|---|---|
| Обработка данных | Хранение и анализ временных рядов | Динамическое добавление новых измерений |
| Веб-разработка | Работа с JSON-данными | Естественное преобразование в JSON-массивы |
| Машинное обучение | Хранение признаков объектов | Удобная векторизация данных |
| Алгоритмы | Реализация стека или очереди | Встроенные методы для добавления/удаления |
| GUI-приложения | Управление коллекциями элементов | Простота обновления интерфейса |
Понимание фундаментальных принципов работы списков даёт вам мощный инструмент для структурирования и манипулирования данными в Python. С этой основой давайте перейдем к более практическим аспектам. 💡
Что такое список (list) в Python?
Список (list)
— тип данных, предназначенный для хранения набора или последовательности разных элементов.
Его можно сравнить со списком покупок для магазина: точно так же вносятся элементы, их тоже можно добавлять и корректировать.
Объявление списка
Объявление списка — самый первый и главный этап его создания. Для объявления списка в Python существует несколько способов.
В данном примере мы создали список с заранее известными данными. Если нужен пустой список, в квадратных скобках ничего не указывается — elements = [].
Обращение к элементу списка в Python
Отрицательные индексы работают справа налево (то есть индекс значения ‘1’ — -4, а отрицательный индекс ‘word’ — -1.
💡 Отрицательным индексом удобно пользоваться, когда необходимо обратиться к последнему элементу в списке, не высчитывая его номер. Любой конечный элемент будет с индексом, равным -1.
Создание и наполнение списков: способы и синтаксис
Python предлагает несколько элегантных способов создания и наполнения списков. Выбор конкретного метода зависит от контекста вашей задачи и стиля программирования.
# С помощью квадратных скобок empty_list = [] # С помощью конструктора list() empty_list_constructor = list()
# С явным указанием элементов fruits = [«яблоко», «банан», «апельсин»] # Через конструктор list из другой итерируемой структуры fruits_from_string = list(«абвг») # [‘а’, ‘б’, ‘в’, ‘г’] fruits_from_tuple = list((«яблоко», «банан», «апельсин»))
# Простой генератор списка squares = [x**2 for x in range(10)] # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] # С условием even_squares = [x**2 for x in range(10) if x % 2 == 0] # [0, 4, 16, 36, 64] # Вложенные генераторы matrix = [[i*j for j in range(3)] for i in range(3)] # [[0, 0, 0], [0, 1, 2], [0, 2, 4]]
Генераторы списков — одна из самых мощных возможностей Python. Они позволяют создавать списки одной элегантной строкой кода, избегая громоздких циклов for.
При наполнении списков важно помнить о нескольких особенностях и возможных подводных камнях:
Таблица №2
| Особенность | Описание | Пример |
|---|---|---|
| Ссылочная семантика | При копировании списка создаётся ссылка, а не новый объект |
Для эффективной работы с данными важно выбирать подходящий способ создания списка. Например, если вам нужно преобразовать данные, генератор списков обычно работает быстрее и требует меньше кода, чем цикл for с append(). 🔄
# Сравнение подходов к созданию списка # Менее эффективный подход squares_traditional = [] for i in range(1000): squares_traditional.append(i**2) # Более эффективный подход squares_comprehension = [i**2 for i in range(1000)]
Выбор способа создания списка существенно влияет на читаемость и производительность вашего кода. Теперь, когда мы знаем, как создавать списки, давайте рассмотрим, как их модифицировать.
Добавление в список
В списках доступно добавление, изменение, удаление элементов. Рассмотрим каждый способ изменения элементов на примерах.
Для того чтобы добавить новый элемент в список, используется (x), где list — список, x — нужное значение.
Добавление в список на указанную позицию
Немаловажно обратить внимание на метод (i, x), где list — список, i — позиция, x — нужное значение.
Изменение элементов списка
Изменение элементов списка происходит следующим образом: нужно выбрать элемент по индексу (порядковому номеру элемента) и присвоить новое значение.
Не забывайте, что счёт начинается с нуля, и в данном списке цифра 6 это 2-й элемент
Удаление элемента из списка
Для удаление из списка используют инструкцию del list[i], где list — список, i — индекс (позиция) элемента в списке:
Еще один способ удаления из списка — (x), где list — список, x — значение, которое нужно удалить:
Как проверить наличие элемента в списке
Для того чтобы проверить существование какого-либо элемента в списке, нужно воспользоваться оператором in. Рассмотрим на примере:
Копирование списка Python
Если вы захотите скопировать список оператором =, вы скопируете не сам список, а только его ссылку.
- () — встроенный метод copy (доступен с Python 3.3);
- list(elements) — через встроенную функцию list();
- (elements) — функция copy() из пакета copy;
- elements[:] — через создание среза (устаревший синтаксис).
Важно
: (a) делает поверхностное копирование. Объекты внутри списка будут скопированы как ссылки на них (как в случае с оператором =). Если необходимо рекурсивно копировать всех элементов в списке, используйте (a)
Скопировать часть списка можно с помощью срезов. Есть несколько вариантов использования:
Цикл по списку
Попробуем построить цикл while. Он выполняется, когда есть какое-либо определённое условие:
Из примеров выше можем сделать вывод, что конструкция for выглядит заметно компактнее, чем while.
Как списки хранятся в памяти?
Базовая C-структура списков в Python (CPython) выглядит следующим образом:
Когда мы создаём список, в памяти под него резервируется объект, состоящий из 3-х частей:
- PyObject_VAR_HEAD — заголовок;
- ob_item — массив указателей на элементы списка;
- allocated — количество выделенной памяти под элементы списка.
Python размещает элементы списка в памяти, затем размещает указатели на эти элементы. Таким образом, список в Python — это массив указателей.
Диспетчер памяти: «командовать парадом буду я»
Python — это интерпретируемый язык программирования, поэтому перед запуском программы код на языке Python компилируется в машиночитаемые инструкции — байт-код. Инструкции байт-кода интерпретируются виртуальной машиной, определяемой реализацией языка, например, стандартной — CPython.
В начале выполнения программы операционная система создает новый процесс и выделяет под него ресурсы. Выделенную виртуальную память интерпретатор использует для 1) собственной корректной работы, 2) стека вызываемых функций и их аргументов и 3) хранилища данных, представленного в виде кучи.
Оговоримся, что CPython не взаимодействует напрямую с регистрами и ячейками физической памяти — только с ее виртуальным представлением.
В отличие от C/C++, мы не можем управлять состоянием кучи напрямую из Python. Функции низкоуровневой работы с памятью предоставляются Python/C API, но обычно интерпретатор просто обращается к хранилищу данных через диспетчер памяти Python (memory manager).
Диспетчер памяти — своеобразный портье, который регистрирует и расселяет гостей отеля. Каждый постоялец получает ключ с номером комнаты, так что ни один из гостей не может заселиться не в свой номер. Две программы не могут одновременно записать переменную в одно место виртуальной памяти.
GIL гарантирует: в один и тот же момент времени байт-код выполняется только одним потоком. Главное преимущество — безопасная работа с памятью, а основной недостаток в том, что многопоточное выполнение программ Python требует специфических решений.
Фактически за это отвечает даже не диспетчер задач, который ожидает гостей за регистрационной стойкой, а GIL — глобальная блокировка интерпретатора.
Очевидно, программа не сама выполняет сохранение и освобождение памяти — ведь мы не пишем соответствующих инструкций. Интерпретатор лишь запрашивает сделать это. А диспетчер уже делегирует работу, связанную с хранением данных, аллокаторам — распределителям памяти.
диспетчер памяти
Организация доступной виртуальной памяти
Непосредственно с оперативной памятью взаимодействует распределитель сырой памяти (raw memory allocator). Поверх него работают аллокаторы, реализующие стратегии управления памятью, специфичные для отдельных типов объектов. Объекты разных типов — например, числа и строки — занимают разный объем, к ним применяются разные механизмы хранения и освобождения памяти. Аллокаторы стараются не занимать лишнюю память до тех пор, пока она не станет совершенно необходимой — этот момент определен стратегией распределения памяти CPython.
Python использует динамическую стратегию, то есть распределение памяти выполняется во время выполнения программы. Виртуальная память Python представляет иерархическую структуру, оптимизированную под объекты Python размером менее 256 Кб:
- Арена — фрагмент памяти, расположенный в пределах непрерывного блока оперативной памяти объемом 256 Кб. Объекты размером более 256 Кб направляются в стандартный аллокатор C.
- Пул — блок памяти внутри арены, занимающий 4 Кб, что соответствует одной странице виртуальной памяти. То есть одна арена включает до 256/4 = 64 пулов.
- Блок — элемент пула размером от 16 до 512 байт. В пределах пула все блоки имеют одинаковый размер. Размер блока определяется тем, сколько байт требуется для представления конкретного объекта. Размеры блоков кратны 16 байт. То есть существует всего 512/16 = 32 классов (size class) блоков. То есть в одном пуле, в зависимости от класса, может находиться от 8 до 256 блоков.
Блок
- untouched — блок еще не использовался для хранения данных;
- free — блок использовался механизмом памяти, но больше не содержит используемых программой данных;
- allocated — блок хранит данные, необходимые для выполнения программы.
В пределах пула блоки free организованы в односвязный список с указателем freeblock. Если аллокатору для выделения памяти не хватит блоков списка freeblock, он задействует блоки untouched. Освобождение памяти означает всего лишь то, что аллокатор меняет статус блока с allocated на free и начинает отслеживать блок в списке freeblock.
Пул
Пул может находиться в одном из трех состояний: used (занят), full (заполнен), empty (пуст). Пустые пулы отличаются от занятых отсутствием блоков allocated и тем, что для них пока не определен size class. Пулы full полностью заполнены блоками allocated и недоступны для записи. Стоит освободиться любому из блоков заполненного пула — и он помечается как used.
Пулы одного типа и одного размера блоков организованы в двусвязные списки. Это позволяет алгоритму легко находить доступное пространство для блока заданного размера. Алгоритм проверяет список usedpools и размещает блок в доступном пуле. Если в usedpools нет ни одного подходящего пула для запроса, алгоритм использует пул из списка freepools, который отслеживает пулы в состоянии empty.
Арена
Информацию о текущем распределении памяти в аренах, пулах и блоках можно посмотреть, запустив функцию sys._debugmallocstats():
Чтобы не произошло утечки памяти, диспетчер памяти должен отследить, что вся выделенная память освободится после завершения работы программы. То есть при завершении программы CPython дает задание очистить все арены.
Именно количество используемых арен определяет объем оперативной памяти, занимаемой программой на Python — если в арене все пулы в состоянии empty, СPython делает запрос на освобождение этого участка виртуальной памяти. Но уже понятно: чтобы пулы стали empty, все их блоки должны быть free или untouched. Получается, нужно понять, как CPython освобождает память.
Освобождение памяти: счетчик ссылок, сборщик мусора
Для освобождения памяти используются два механизма: счетчик ссылок и сборщик мусора.
Всё в Python является объектами, а прародителем всех типов объектов в реализации CPython является PyObject. От него наследуются все остальные типы. В PyObject определены счетчик ссылок и указатель на фактический тип объекта. Счетчик ссылок увеличивается на единицу, когда мы создаем что-то, что обращается к объекту, например, сохраняем объект в новой переменной. И наоборот, счетчик уменьшается на единицу, когда мы перестаем ссылаться на объект.
Счетчик ссылок любого объекта можно проверить с помощью (). Учтите, что передача объекта в getrefcount() увеличивает счетчик ссылок на 1, так как сам вызов метода создает еще одну ссылку. Когда счетчик уменьшается до нуля, происходит вызов аллокатора для освобождения соответствующих блоков памяти.
Однако счетчик ссылок неспособен отследить ситуации с циклическими ссылками. К примеру, возможна ситуация, когда два объекта ссылаются друг на друга, но оба уже не используются программой. Для борьбы с такими зависимостями используется сборщик мусора (garbage collector).
Если счетчик ссылок является свойством объекта, то сборщик мусора — механизм, который запускается на основе эвристик. Задача этих эвристик — снизить частоту и объем очищаемых данных. Основная стратегия заключается в разделении объектов на поколения: чем больше сборок мусора пережил объект, тем он значимее для выполнения работы программы. Сборщик мусора имеет интерфейс в виде модуля gc.
Создание, изменение, удаление списков и работа с его элементами
Если у вас уже есть список и вы хотите создать его копию, то можно воспользоваться следующим способом:
В случае, если вы выполните простое присвоение списков друг другу, то переменной b будет присвоена ссылка на тот же элемент данных в памяти, на который ссылается a, а не копия списка а. Т.е. если вы будете изменять список a, то и b тоже будет меняться.
Для удаления элемента из списка, в случае, если вы знаете его значение, используйте метод remove(x), при этом будет удалена первая ссылка на данный элемент.
Очистить список можно просто заново его проинициализировав, так как будто вы его вновь создаете. Для получения доступа к элементу списка укажите индекс этого элемента в квадратных скобках.
Можно использовать отрицательные индексы, в таком случае счет будет идти с конца, например для доступа к последнему элементу списка можно использовать вот такую команду:
Для получения из списка некоторого подсписка в определенном диапазоне индексов, укажите начальный и конечный индекс в квадратных скобках, разделив их двоеточием.
Добавление и удаление элементов в списке Python
Одна из ключевых особенностей списков в Python — их изменяемость. Это позволяет динамически добавлять, удалять и заменять элементы по мере выполнения программы. Рассмотрим основные методы манипуляции элементами списка.
- append() — добавляет элемент в конец списка
- insert() — вставляет элемент по указанному индексу
- extend() — добавляет все элементы из другого итерируемого объекта
- + (оператор сложения) — создаёт новый список, объединяя два существующих
# Примеры добавления элементов fruits = [«яблоко», «груша»] # append() – добавляет один элемент в конец («банан») # [‘яблоко’, ‘груша’, ‘банан’] # insert() – вставляет элемент по индексу (1, «апельсин») # [‘яблоко’, ‘апельсин’, ‘груша’, ‘банан’] # extend() – добавляет элементы из итерируемого объекта ([«киви», «манго»]) # [‘яблоко’, ‘апельсин’, ‘груша’, ‘банан’, ‘киви’, ‘манго’] # + оператор создает новый список new_fruits = fruits + [«ананас», «дыня»]
- append() добавляет аргумент как один элемент, даже если это список
- extend() добавляет каждый элемент из итерируемого объекта отдельно
- remove() — удаляет первый элемент с указанным значением
- pop() — удаляет и возвращает элемент по индексу (по умолчанию последний)
- del — оператор для удаления элемента по индексу или среза
- clear() — удаляет все элементы из списка
fruits = [‘яблоко’, ‘апельсин’, ‘груша’, ‘банан’, ‘апельсин’] # remove() – удаляет первое вхождение значения (‘апельсин’) # [‘яблоко’, ‘груша’, ‘банан’, ‘апельсин’] # pop() – удаляет и возвращает элемент по индексу last_fruit = () # ‘апельсин’ first_fruit = (0) # ‘яблоко’ # fruits теперь [‘груша’, ‘банан’] # del – удаляет элемент или срез fruits = [‘яблоко’, ‘апельсин’, ‘груша’, ‘банан’] del fruits[1] # [‘яблоко’, ‘груша’, ‘банан’] del fruits[1:3] # [‘яблоко’] # clear() – удаляет все элементы () # []
На одном из моих тренингов по Python для аналитиков данных участники столкнулись с проблемой, которая отлично иллюстрирует особенности удаления элементов из списка.
Задача была такой: удалить из списка все отрицательные числа. Один из участников написал следующий код:
Но код работал неправильно — некоторые отрицательные числа оставались в списке. Проблема в том, что при удалении элемента все последующие элементы смещаются влево, а индекс i продолжал увеличиваться. Таким образом, некоторые элементы пропускались.
# Способ 1: Итерация от конца к началу numbers = [5, -2, 7, -8, 10, -4, 3] for i in range(len(numbers) – 1, -1, -1): if numbers[i] < 0: del numbers[i] # Способ 2: Создание нового списка (более «питонический») numbers = [5, -2, 7, -8, 10, -4, 3] numbers = [x for x in numbers if x >= 0]
Этот случай помог участникам понять, как важно учитывать изменение структуры списка при его модификации в процессе итерации.
Важное замечание: удаление элементов из списка в процессе итерации может привести к непредсказуемым результатам. Существует несколько безопасных способов:
- Создать новый список с фильтром (list comprehension)
- Использовать функцию filter()
- Итерироваться по списку в обратном порядке
- Использовать копию списка для итерации
# Безопасные способы удаления элементов при итерации numbers = [1, 2, 3, 4, 5, 6] # Способ 1: List comprehension even_numbers = [x for x in numbers if x % 2 == 0] # [2, 4, 6] # Способ 2: filter() even_numbers = list(filter(lambda x: x % 2 == 0, numbers)) # [2, 4, 6] # Способ 3: Обратная итерация for i in range(len(numbers) – 1, -1, -1): if numbers[i] % 2!= 0: del numbers[i] # numbers будет [2, 4, 6] # Способ 4: Итерация по копии for x in (): if x % 2!= 0: (x)
Понимание тонкостей добавления и удаления элементов в списках позволяет избегать распространённых ошибок и писать более эффективный код. Следующий шаг — освоение методов сортировки и изменения порядка элементов. 🧩
Сортировка и изменение порядка элементов списка
Сортировка данных — одна из наиболее частых операций при обработке информации. Python предоставляет мощные и гибкие инструменты для сортировки и изменения порядка элементов в списках.
- sort() — сортирует список на месте (изменяет оригинальный список)
- sorted() — возвращает новый отсортированный список, не изменяя оригинал
- reverse() — обращает порядок элементов на месте
- reversed() — возвращает итератор с элементами в обратном порядке
# Базовые примеры сортировки numbers = [3, 1, 4, 1, 5, 9, 2, 6] # sort() – сортирует на месте () print(numbers) # [1, 1, 2, 3, 4, 5, 6, 9] # sorted() – возвращает новый список original = [3, 1, 4, 1, 5, 9] sorted_numbers = sorted(original) print(original) # [3, 1, 4, 1, 5, 9] – не изменился print(sorted_numbers) # [1, 1, 3, 4, 5, 9] # Сортировка в обратном порядке (reverse=True) print(numbers) # [9, 6, 5, 4, 3, 2, 1, 1]
# Сортировка по длине строк words = [«яблоко», «груша», «банан», «киви»] (key=len) print(words) # [‘киви’, ‘груша’, ‘яблоко’, ‘банан’] # Сортировка словарей по значению определенного ключа students = [{«name»: «Анна», «grade»: 4.5}, {«name»: «Иван», «grade»: 3.8}, {«name»: «Мария», «grade»: 4.9}] sorted_students = sorted(students, key=lambda x: x[«grade»], reverse=True) # [{‘name’: ‘Мария’, ‘grade’: 4.9}, {‘name’: ‘Анна’, ‘grade’: 4.5}, {‘name’: ‘Иван’, ‘grade’: 3.8}]
# Обращение списка fruits = [«яблоко», «груша», «банан»] # reverse() – меняет порядок на месте () print(fruits) # [‘банан’, ‘груша’, ‘яблоко’] # reversed() – возвращает итератор original = [«яблоко», «груша», «банан»] reversed_fruits = list(reversed(original)) print(original) # [‘яблоко’, ‘груша’, ‘банан’] – не изменился print(reversed_fruits) # [‘банан’, ‘груша’, ‘яблоко’]
Python позволяет реализовать многоуровневую сортировку, когда элементы сравниваются по нескольким критериям:
# Сортировка по нескольким критериям employees = [(«Иванов», «Иван», 30), («Петров», «Петр», 25), («Иванов», «Алексей», 35), («Сидоров», «Иван», 30)] # Сортировка по фамилии, затем по имени, затем по возрасту sorted_employees = sorted(employees, key=lambda x: (x[0], x[1], x[2])) # [(‘Иванов’, ‘Алексей’, 35), (‘Иванов’, ‘Иван’, 30), (‘Петров’, ‘Петр’, 25), (‘Сидоров’, ‘Иван’, 30)] # Сортировка по фамилии (по возрастанию), затем по возрасту (по убыванию) from operator import itemgetter sorted_employees = sorted(employees, key=itemgetter(0)) # По фамилии sorted_employees = sorted(sorted_employees, key=itemgetter(2), reverse=True) # По возрасту (по убыванию)
Для сложных объектов можно использовать модуль operator и его функции itemgetter и attrgetter:
from operator import itemgetter, attrgetter # Для списка кортежей sorted(employees, key=itemgetter(0, 1)) # Сортировка по первым двум элементам # Для списка объектов с атрибутами class Person: def __init__(self, name, age): = name = age people = [Person(«Алиса», 25), Person(«Боб», 20), Person(«Чарли», 30)] sorted_people = sorted(people, key=attrgetter(«age»))
Таблица №3
| Метод | Изменяет исходный список | Возвращает результат | Поддержка key и reverse | Типичное использование |
|---|---|---|---|---|
| sort() | Да | None | Да | Когда оригинальный порядок не нужен |
| sorted() | Нет | Новый список | Да | Когда нужно сохранить оригинальный список |
| reverse() | Да | None | Нет | Для простого обращения порядка элементов |
| reversed() | Нет | Итератор | Нет | Для экономии памяти при обращении порядка |
Эффективная сортировка списков — важный навык, который позволяет быстро организовывать и анализировать данные. Особенно это актуально при работе с большими объемами информации, где правильная сортировка может значительно ускорить последующие операции поиска и фильтрации. 📊
Срезы и итерация: мощные методы работы со списками
Срезы (slices) и итерация — это инструменты, которые поднимают работу со списками на новый уровень, позволяя элегантно извлекать подмножества элементов и обрабатывать данные. Правильное использование этих техник часто отличает код новичка от кода опытного Python-разработчика.
- start — индекс начала среза (включительно)
- stop — индекс конца среза (не включительно)
- step — шаг (по умолчанию 1)
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # Базовые срезы first_three = numbers[0:3] # [0, 1, 2] middle = numbers[3:7] # [3, 4, 5, 6] # Сокращенный синтаксис from_beginning = numbers[:5] # [0, 1, 2, 3, 4] to_end = numbers[5:] # [5, 6, 7, 8, 9] full_copy = numbers[:] # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # Отрицательные индексы (отсчет с конца) last_three = numbers[-3:] # [7, 8, 9] except_last_two = numbers[:-2] # [0, 1, 2, 3, 4, 5, 6, 7] # Шаг every_second = numbers[::2] # [0, 2, 4, 6, 8] every_third_reverse = numbers[::-3] # [9, 6, 3, 0]
numbers = [0, 1, 2, 3, 4, 5] # Замена среза numbers[1:4] = [10, 20, 30] # [0, 10, 20, 30, 4, 5] # Замена среза на последовательность другой длины numbers[1:4] = [100, 200] # [0, 100, 200, 4, 5] # Удаление среза numbers[1:3] = [] # [0, 4, 5] # Вставка без замены numbers[1:1] = [42, 43] # [0, 42, 43, 4, 5]
fruits = [«яблоко», «груша», «банан», «апельсин»] # Простая итерация по элементам for fruit in fruits: print(fruit) # Итерация с индексами for i, fruit in enumerate(fruits): print(f»Индекс {i}: {fruit}») # Параллельная итерация по нескольким спискам prices = [100, 150, 80, 120] for fruit, price in zip(fruits, prices): print(f»{fruit} стоит {price} рублей») # Итерация с условной фильтрацией for fruit in fruits: if len(fruit) > 5: print(f»{fruit} — длинное название»)
Особая сила Python проявляется при использовании итерации в сочетании с другими конструкциями языка:
# Функциональные инструменты работы со списками numbers = [1, 2, 3, 4, 5] # map() — применяет функцию к каждому элементу squares = list(map(lambda x: x**2, numbers)) # [1, 4, 9, 16, 25] # filter() — отбирает элементы по условию even = list(filter(lambda x: x % 2 == 0, numbers)) # [2, 4] # reduce() — последовательно применяет функцию к парам элементов from functools import reduce sum_all = reduce(lambda x, y: x + y, numbers) # 15 # all() и any() — проверка условий all_positive = all(x > 0 for x in numbers) # True any_even = any(x % 2 == 0 for x in numbers) # True
Таблица №4
| Подход | Преимущества | Недостатки | Пример использования |
|---|---|---|---|
| Цикл for | Простой, понятный, гибкий | Более многословный | Сложные преобразования с условиями |
| List comprehension | Краткий, элегантный, читаемый | Ограничен простыми операциями | Трансформация всех элементов списка |
| map()/filter() | Функциональный стиль | Менее читабелен для новичков | Работа с функциями высшего порядка |
| NumPy/Pandas операции | Очень быстрые, векторизованные | Требуют дополнительных библиотек | Научные и аналитические вычисления |
Срезы и итерации особенно эффективны при работе с большими данными. Важно отметить, что срезы создают копии данных, что может быть затратно по памяти для очень больших списков. В таких случаях иногда лучше использовать генераторы и итераторы для потоковой обработки. 🔄
# Пример использования генераторного выражения вместо списка large_list = list(range(1000000)) # Потребляет много памяти large_squares = [x**2 for x in large_list] # Более эффективно по памяти (ленивые вычисления) large_squares_gen = (x**2 for x in large_list) for square in large_squares_gen: # обработка каждого значения по одному pass
Овладение техниками срезов и итерации — ключевой навык для эффективного программирования на Python. Эти инструменты позволяют писать чистый, краткий и производительный код для обработки списков любой сложности. 🚀
Изучение списков в Python — фундаментальный шаг для каждого разработчика. Мы рассмотрели основные операции: создание, наполнение, модификацию, сортировку и итерацию. Понимание особенностей каждого метода и выбор правильного инструмента для конкретной задачи значительно повышает качество и эффективность кода. Когда вы встречаете сложную задачу обработки данных, стоит сначала подумать: «Как я могу элегантно решить это с помощью списков?» Скорее всего, Python уже предлагает готовый метод или комбинацию методов для вашей конкретной задачи. Не останавливайтесь на базовом использовании — экспериментируйте с продвинутыми техниками, которые превратят ваш код из функционального в действительно впечатляющий.
Методы списков
Добавляет элемент в конец списка. Ту же операцию можно сделать так a[len(a):] = [x].
Расширяет существующий список за счет добавления всех элементов из списка L. Эквивалентно команде a[len(a):] = L.
Вставить элемент x в позицию i. Первый аргумент – индекс элемента после которого будет вставлен элемент x.
Удаляет элемент из позиции i и возвращает его. Если использовать метод без аргумента, то будет удален последний элемент из списка.
Сортирует элементы в списке по возрастанию. Для сортировки в обратном порядке используйте флаг reverse=True. Дополнительные возможности открывает параметр key, за более подробной информацией обратитесь к документации.
List Comprehensions
List Comprehensions чаще всего на русский язык переводят как абстракция списков или списковое включение, является частью синтаксиса языка, которая предоставляет простой способ построения списков. Проще всего работу list comprehensions показать на примере. Допустим вам необходимо создать список целых чисел от 0 до n, где n предварительно задается. Классический способ решения данной задачи выглядел бы так:
Использование list comprehensions позволяет сделать это значительно проще:
Слайсы / Срезы
Слайсы (срезы) являются очень мощной составляющей Python, которая позволяет быстро и лаконично решать задачи выборки элементов из списка. Выше уже был пример использования слайсов, здесь разберем более подробно работу с ними. Создадим список для экспериментов:
Слайс задается тройкой чисел, разделенных запятой: start:stop:step. Start – позиция с которой нужно начать выборку, stop – конечная позиция, step – шаг. При этом необходимо помнить, что выборка не включает элемент определяемый stop.
>>> # Получить копию списка >>> a[:] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> # Получить первые пять элементов списка >>> a[0:5] [0, 1, 2, 3, 4] >>> # Получить элементы с 3-го по 7-ой >>> a[2:7] [2, 3, 4, 5, 6] >>> # Взять из списка элементы с шагом 2 >>> a[::2] [0, 2, 4, 6, 8] >>> # Взять из списка элементы со 2-го по 8-ой с шагом 2 >>> a[1:8:2] [1, 3, 5, 7]
Вложенные списки
Для обращения к элементу вложенного списка нужно использовать два индекса: первый указывает на индекс главного списка, второй — индекс элемента во вложенном списке. Вот пример:
Генераторы списков
Генератором списка называется способ построения списка с применением выражения к каждому элементу, входящему в последовательность. Есть схожесть генератора списка и цикла for. На этом примере мы рассмотрим простейший генератор списков:
Таким образом мы получили отдельно взятые утроенные буквы слова, введённого в кавычки. Есть множество вариантов применения генератора списков.
Это усложнённая конструкция генератора списков, в которой мы сделали все возможные наборы сочетаний букв из введённых слов. Буквы-исключения видны по циклу, где стоит знак!= для одной переменной и другой.
Best Practices
Последние абзацы статьи будут посвящены лучшим решениям практических задач, с которыми так или иначе сталкивается Python-разработчик.
Как перевести список в другой формат?
Иногда требуется перевести список в строку, в словарь или в JSON. Для этого нужно будет вывести список без скобок.
Перевод списка в строку осуществляется с помощью функции join(). На примере это выглядит так:
Словарь в Python — это такая же встроенная структура данных, наряду со списком. Преобразование списка в словарь — задача тоже несложная. Для этого потребуется воспользоваться функцией dict(). Вот пример преобразования:
JSON — это JavaScript Object Notation. В Python находится встроенный модуль json для кодирования и декодирования данных JSON. С применением метода (x) можно запросто преобразовать список в строку JSON.
Как узнать индекс элемента в списке?
Узнать позицию элемента в последовательности списка бывает необходимым, когда элементов много, вручную их не сосчитать, и нужно обращение по индексу. Для того чтобы узнать индекс элемента, используют функцию (x).
В качестве аргумента передаем значение, а на выходе получаем его индекс.
Как посчитать количество уникальных элементов в списке?
Самый простой способ — приведение списка к set (множеству). После этого останутся только уникальные элементы, которые мы посчитаем функцией len():
Как создать список числовых элементов с шагом
Создание списка числовых элементов с шагом может понадобиться не так и часто, но мы рассмотрим пример построения такого списка.
Шагом называется переход от одного элемента к другому. Если шаг отрицательный, произойдёт реверс массива, то есть отсчёт пойдёт справа налево. Вот так выглядит список с шагом.
При разработке на языке Python, списки встречаются довольно часто. Знание основ работы со списками поможет быстро и качественно писать программный код 😉.
Часто задаваемые вопросы о хранении списков в Python
Вопрос: Как Python управляет памятью при создании списка?
Ответ: Python выделяет блок памяти под массив указателей на объекты, а при необходимости расширяет его, выделяя дополнительное место (over-allocation) для оптимизации последующих добавлений.
Вопрос: Что такое арена, пул и блок в контексте памяти Python?
Ответ: Это уровни организации памяти в диспетчере памяти Python: арена — большой сегмент (256 КБ), пул — часть арены (4 КБ), блок — минимальная единица для хранения объектов.
Вопрос: Почему список занимает больше памяти, чем сумма его элементов?
Ответ: Потому что список хранит не сами объекты, а ссылки на них (указатели), плюс служебные данные (длина, capacity) и зарезервированное место для будущих элементов.
Вопрос: Как работает сборщик мусора при удалении списка?
Ответ: Сборщик мусора (GC) отслеживает циклические ссылки, а счетчик ссылок немедленно освобождает память, когда на объект больше нет ссылок.
Вопрос: Влияет ли тип элементов списка на его хранение в памяти?
Ответ: Да, каждый элемент — это указатель на объект в куче, поэтому разнородные списки хранят ссылки на объекты разных типов, каждый со своим размером.
Вопрос: Что такое over-allocation в списках Python?
Ответ: Это стратегия выделения памяти с запасом (обычно на 12.5% больше) при расширении списка, чтобы избежать частых перераспределений.
Вопрос: Как проверить, сколько памяти занимает список?
Ответ: Используйте sys.getsizeof(list_object) для размера самого списка (без учета объектов-элементов) и getsizeof для каждого элемента.
Вопрос: Чем отличается хранение списка от хранения кортежа в памяти?
Ответ: Кортеж неизменяем, поэтому Python не резервирует дополнительное место (over-allocation), и он занимает меньше памяти, чем список с теми же элементами.
Вопрос: Как удаление элемента из середины списка влияет на память?
Ответ: Python сдвигает все последующие элементы на одну позицию влево, что требует времени O(n), но память не освобождается сразу — capacity остается прежним.
Вопрос: Можно ли вручную управлять памятью списка?
Ответ: Косвенно — через del для удаления ссылок, вызов gc.collect() для принудительной сборки мусора или использование array.array для однотипных данных.





















