Skip to content

shox-py/topic_10

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

10. Работа со списками

cover.svg

Добро пожаловать в тему списков!

В пятой теме мы уже поверхностно познакомились с ними, в этой теме мы углубимся в тему списков. Изучим следующие важные аспекты списков:


В Python список эквивалентен массивам в других языках программирования, но имеет дополнительные преимущества. Напоминаю, что списки являются изменяемыми и упорядоченными типами данных. Из-за этих особенностей они часто используются там, где необходимо изменение последовательности (добавлять, удалять и сортировать элементы).

Полезно знать:

Списки явлются ссылочными типами, в начальных темах вы видели создание двух переменных с одинаковыми значениями атомарного типа:

a: int = 24
b: int = 24

# В памяти компьютера создается один объект
print(id(a))  # 2888782906320
print(id(b))  # 2888782906320

а, у списков две разные переменные по одинаковому содержимому будут создавать два разных объекта в памяти компьютера.

nums_1: list[int] = [40, 30, 90, 60]
nums_2: list[int] = [40, 30, 90, 60]

print(id(nums_1))  # 2312068396544
print(id(nums_2))  # 2312068359936

# Списки равны по содержимому
print(nums_1 == nums_2)  # True

# Если сравнить их id, это разные объекты
print(nums_1 is nums_2)  # False

Индексы и срезы

Доступ к элементам списка (Индексация):

В Python списки также индексируются, что позволяет обращаться к отдельным элементам списка. Индексация начинается с 0, т.е. первый элемент списка имеет индекс 0, второй - индекс 1 и так далее. Также можно использовать отрицательную индексацию, где -1 соответствует последнему элементу списка, -2 - предпоследнему и так далее.

fruits = ['apple', 'banana', 'orange', 'pineapple', 'kiwi', 'lemon']

# Положительные индексы
print(fruits[0])  # apple
print(fruits[1])  # banana
print(fruits[2])  # orange
print(fruits[3])  # pineapple
print(fruits[4])  # kiwi
print(fruits[5])  # lemon

# -------------------------

# Отрицательные индексы
print(fruits[-1])  # lemon
print(fruits[-2])  # kiwi
print(fruits[-3])  # pineapple
print(fruits[-4])  # orange
print(fruits[-5])  # banana
print(fruits[-6])  # apple

Срезы списков (Slicing):

Иногда необходимо получить досутп сразу к нескольким элементам списка, в этих случаях мы можем применить срезы и для списков, в прошлой теме (в теме строк) мы более подробно разибрали срезов для строк, такой же принцип будет работать для списков.

Формат среза выглядит следующим образом:

line[start:stop:step]

где:

  • start - индекс элемента, с которого начинается срез (включительно).

    • line[start::step] - от start до конца с шагом step
    • line[start::] - от start до конца с шагом 1
  • stop - индекс элемента, до которого продолжается срез (не включительно).

    • line[:stop:step] - от начала до stop с шагом step
    • line[:stop:] - от начала до stop с шагом 1
  • step (необязательно) - шаг, с которым выбираются элементы (по умолчанию равен 1).

    • line[start:stop:] - от start до stop с шагом 1
    • line[start:stop:step] - от start до stop с шагом step
    • line[::step] - от начала до конца списка с шагом step
    • line[::] - от начало до конца с шагом 1 (другими словами, выводятся все элементы списка)
fruits: list[str, ...] = ['apple', 'banana', 'orange', 'pineapple', 'kiwi', 'lemon']

# Получим элементы с индекса 0 до 5 (не включительно)
print(fruits[0:5])  # ['apple', 'banana', 'orange', 'pineapple', 'kiwi']

# Получим элементты с индекса 3 до конца списка
print(fruits[3:])  # ['pineapple', 'kiwi', 'lemon']

# Получим элементы с индекса 1 до 5 с шагом 2
print(fruits[1:5:2])  # ['banana', 'pineapple']

# Получим каждый второй элемент списка
print(fruits[::2])  # ['apple', 'orange', 'kiwi']

Рассмотрим несколько примеров с отрицательными индексами:

fruits: list[str, ...] = ['apple', 'banana', 'orange', 'pineapple', 'kiwi', 'lemon']

# Получим все элементы списка в обратном порядке
print(fruits[::-1])  # ['lemon', 'kiwi', 'pineapple', 'orange', 'banana', 'apple']

# Получим элементы, начиная с последнего элемента и двигаясь
# в обратном направлении до индекса -4 (не включая его)
print(fruits[-1:-4:-1])  # ['lemon', 'kiwi', 'pineapple']

# Получим каждый второй элемент, начиная
# с последнего и двигаясь в обратном направлении
print(fruits[::-2])  # ['lemon', 'pineapple', 'banana']

# Получим элементы, начиная с последнего элемента и двигаясь
# в обратном направлении до индекса -6 (не включая его) с шагом -3
print(fruits[-1:-6:-3])  # ['lemon', 'orange']

Обращение к несуществующему индексу списка:

При обращении к элементам списка по индексу, индексы должны находиться в допустимом диапазоне, т.е. от 0 до длины списка минус 1. Если индекс находится за пределами этого диапазона, то будет возникать ошибка IndexError.

fruits: list[str, ...] = ['apple', 'banana', 'orange', 'pineapple', 'kiwi', 'lemon']

# В данном случае, индекс 10 выходит за пределы длины списка
print(fruits[10])  # IndexError: list index out of range

Изменить значение элемента списка:

summer_times: list[str, ...] = ['June', 'July', 'August']

# Изменение значения элемента по индексу
summer_times[2] = 'Month №8'

print(summer_times)  # ['June', 'July', 'Month №8']

# Изменение значения элемента по отрицательному индексу
summer_times[-3] = 'Month №6'
print(summer_times)  # ['Month №6', 'July', 'Month №8']

Изменить диапазон значений списка:

fruits: list[str, ...] = ['apple', 'banana', 'orange', 'grape', 'kiwi']

# Изменяем диапазон значений с помощью среза
fruits[1:4] = ['pear', 'melon', 'strawberry']

print(fruits)  # ['apple', 'pear', 'melon', 'strawberry', 'kiwi']

Удалить несколько элементов списка:

furniture: list[str] = ['стул', 'стол', 'диван', 'гардероб', 'рабочий стол']

# Удаляем несколько элементов по индексам с помощью del
del furniture[1:4]

print(furniture)  # ['стул', 'рабочий стол']

Умножение списка элементов на число:

numbers: list[int] = [10, 20, 30]
multiplied_numbers: list[int, ...] = numbers * 3

print(multiplied_numbers)  # [10, 20, 30, 10, 20, 30, 10, 20, 30]

words: list[str] = ['hello', 'world']
multiplied_words: list[str] = words * 2

print(multiplied_words)  # ['hello', 'world', 'hello', 'world']

# Умножение списка на 0 эквивалентно очистке списка
empty_list: list = [1, 2, 3] * 0

print(empty_list)  # []

Объединение нескольких списков в один:

numbers: list[int] = [1, 2, 3]
chars: list[str] = ['a', 'b', 'c']
merged_items: list[int, str, ...] = numbers + chars

print(merged_items)  # [1, 2, 3, 'a', 'b', 'c']

print(numbers)  # [1, 2, 3]
print(chars)  # ['a', 'b', 'c']

Ещё один пример объединения(расширения существующего списка) списков с использованием оператора +:

first_part: list[str] = ['one', 'two', 'three']
second_part: list[str] = ['four', 'five', 'six']

first_part = first_part + second_part
# или так
# first_part += second_part
print(first_part)  # ['one', 'two', 'three', 'four', 'five', 'six']

Итерация списка

Итерация списка - другими словами это перебор всех элементов по порядку, в котором они хранятся в списке.

mix: list = [0.523, 1010, True, 'abcdefu', None]

# Перебираются элементы списка и выводятся в одну строку через дефис
for item in mix:
    print(item, end='-')

# Вывод
# 0.523-1010-True-abcdefu-None-
# В названиях фруктов есть лишние пробелы с обеих сторон,
# также название в разных регистрах
fruits: list[str] = [
    ' ApPLe  ',
    '  bAnANA ',
    'StrAWBerRy',
    '  oRanGE ',
    '  PineApple '
]

# Исправим это
for fruit in fruits:
    print(fruit.strip().capitalize())

Цикл for обычно более удобен и читаем, особенно когда итерация производится по всем элементам списка. Он автоматически управляет индексами и более выразителен в подобных сценариях, делая код более лаконичным и понятным.

Рассмотрим примеры, когда обычный перебор списка не может помочь нам решить некоторые задачи.

#  Представим, нам нужно заменить все слова "apple" в списке на "Яблоко"  
fruits: list[str] = [
    ' ApPLe  ',
    '  bAnANA ',
    'StrAWBerRy',
    'aPPlE  ',
    '  oRanGE ',
    ' apPlE',
    '  PineApple '
]

for i in range(len(fruits)):
    # Если i-ый элемент списка равен "apple"
    if fruits[i].strip().lower() == 'apple':
        # i-ый элемент заменим на "Яблоко"
        fruits[i] = 'Яблоко'

print(fruits)  # ['Яблоко', '  bAnANA ', 'StrAWBerRy', 'Яблоко', '  oRanGE ', 'Яблоко', '  PineApple ']

Такого же результата можно было добиться с использованием цикла while:

fruits: list[str] = [
    ' ApPLe  ',
    '  bAnANA ',
    'StrAWBerRy',
    'aPPlE  ',
    '  oRanGE ',
    ' apPlE',
    '  PineApple '
]

length: int = len(fruits)
i: int = 0
while i < length:
    if fruits[i].strip().lower() == 'apple':
        fruits[i] = 'Яблоко'

    i += 1  # Инкрементируем

print(fruits)  # ['Яблоко', '  bAnANA ', 'StrAWBerRy', 'Яблоко', '  oRanGE ', 'Яблоко', '  PineApple ']

Цикл while может быть полезен, когда у вас есть сложные условия или когда нужно управлять индексами вручную. Он предоставляет больше контроля над итерации и может быть предпочтительным, когда условия для итерации сложны.


N-мерные списки

Многомерные списки представляют собой списки, где элементами могут быть другие списки. Например, матрицы для представления игровых полей, трехмерных моделей в компьютерной графике, пиксельных данных изображений и других структурированных данных.

1d_2d_3d_arrays.png

Представим месяцы разбиты по сезонам, тогда их можно написать следующим образом:

seasons: list[list[str]] = [
    ['December', 'January', 'February'],
    ['March', 'April', 'May'],
    ['June', 'July', 'August'],
    ['September', 'October', 'November']
]

Это двумерная матрица (список) размером 4x3 (4 строки и 3 столбца).

Первой задачей будет вывести для каждого сезона соответствующие месяцы. Вот как это можно сделать:

seasons: list[list[str]] = [
    ['December', 'January', 'February'],
    ['March', 'April', 'May'],
    ['June', 'July', 'August'],
    ['September', 'October', 'November']
]

seasons_name: list[str] = ['winter', 'spring', 'summer', 'autumn']

for row in range(len(seasons)):
    print(f'This season {seasons_name[row]}')
    for col in range(len(seasons[row])):
        print(seasons[row][col], end=' ')
    print(end='\n\n')

Сначала мы создали дополнительный одномерный список seasons_name, в котором хранятся названия сезонов. Затем во внешнем цикле for начинаем перебирать элементы (строки row) списка seasons. Обратите внимание, мы идем по длине этого списка range(len(seasons)), что позволяет нам получить индексы 0, 1, 2, 3.

В теле этого цикла выводим текущий сезон, используя значения из списка seasons_name. Например, при row равном 0 выводим 'winter' из списка seasons_name. Затем создаем внутренний цикл (вложенный во внешний), в котором мы обращаемся к элементу списка seasons, который на текущей итерации находится под индексом row. По длине этого внутреннего списка мы начинаем итерировать (range(len(seasons[row]))), получая индексы 0, 1, 2.

Внутри вложенного цикла мы обращаемся к элементу seasons[row][col], чтобы получить конкретный месяц. Например, seasons[0][0] обозначает первый месяц первого сезона. Выводим этот элемент в консоль с пробелом в качестве разделителя, чтобы месяцы отображались в одной строке. По завершении вложенного цикла переходим на новую строку с помощью print(end='\n\n') для вывода следующего сезона на новой строке.

Обычно разработчики используют буквы латинского алфавита, такие как i, j, k, l и т.д., в качестве переменных при работе с вложенными циклами. Это стандартные обозначения индексов, широко применяемые в программировании.

На практике вы можете столкнуться с разными уровнями вложенности списков. Один из примеров — это трехмерные списки, например, куб.

cube_arr: list[list[list[int]]] = [
    [
        [7, 1],
        [9, 4],
        [2, 3],
    ],
    [
        [4, 7],
        [0, 8],
        [8, 0],
    ],
    [
        [1, 7],
        [3, 2],
        [6, 9],
    ],
    [
        [5, 4],
        [8, 5],
        [4, 7],
    ]
]

Размеры этого массива (списка) 4x3x2 (4 слоя, 3 строки и столбцов 2).

Список cube_arr содержит 4 вложенных списка. Каждый из этих вложенных списков состоит из 3 списков, каждый из которых содержит 2 элемента.

Как можно обходить этот куб?

Теперь у нас три уровня вложенности списков, двумерные вложенные циклы не могут справиться с этой задачей, поскольку требуется трехмерный подход к итерации по данным.

Вот пример обхода такого списка:

cube_arr: list[list[list[int]]] = [
    [
        [7, 1],
        [9, 4],
        [2, 3],
    ],
    [
        [4, 7],
        [0, 8],
        [8, 0],
    ],
    [
        [1, 7],
        [3, 2],
        [6, 9],
    ],
    [
        [5, 4],
        [8, 5],
        [4, 7],
    ]
]

# Цикл для первого уровня
for i in range(len(cube_arr)):

    # Цикл для второго уровня
    for j in range(len(cube_arr[i])):

        # Цикл для третьего уровня
        for k in range(len(cube_arr[i][j])):
            print(cube_arr[i][j][k], end=' ')
        print()
    print()

Это представляет трехуровневую итерацию, сначала мы идем по первому уровню списка i, затем по второму j, и, наконец, по третьему k. Это позволяет получить доступ ко всем элементам куба.

Если вы хорошо понимаете работу с двумерными списками (списками списков), вы можете легко адаптировать этот подход для работы с трехмерными и более сложными уровнями вложенности списков. Это обеспечит более полный и глубокий доступ к данным в многомерных массивах.


Списки могут содержать элементы любых других типов данных

В предыдущем разделе мы видели, как списки могут хранить другие списки, однако это не единственное их применение. Списки также могут содержать элементы различных типов данных.

Рассмотрим примеры:

  • Пример списка, содержащего разные типы данных:
from typing import Any

# list[Any, ...] указывает, что список mix_arr содержит элементы любого типа 
# и может иметь любую длину
mix_arr: list[Any, ...] = [
    10, 20, 30,  # Целые число
    'abc', 'def', 'ghi',  # Строковые значения
    1.43, -9.23, 0.125,  # Числа с плавающей точкой
    False, True,  # Булевы значения
    None,  # Значение None,
    int, float, str,  # Встроенные функции
    [], [], [],  # Списки
    (), (), (),  # Кортежи
    set(), set(), set(),  # Множества
    {}, {}, {}  # Словари
]
  • Пример списка, содержащего словари:
posts: list[dict[str, dict]] = [

    {
        "posts": {

            "id_100": {
                "title": "Важные новости",
                "likes_count": 734,
                "comments_count": 5,
            },

            "id_101": {
                "title": "Спортивные новости",
                "likes_count": 192,
                "comments_count": 46,
            },

            "id_102": {
                "title": "Новости про ИИ",
                "likes_count": 999999,
                "comments_count": 9999,
            },

        }
    },

    {
        "users": {

            "104356": {
                "first_name": "Ivan",
                "last_name": "Ivanov",
                "birth_date": "01.01.1970",
                "email": "ivanov@mail.com",
            },

            "1942452": {
                "first_name": "Petr",
                "last_name": "Petrov",
                "birth_date": "01.02.1971",
                "email": "petrov@mail.com",
            },

            "1458398": {
                "first_name": "Sidor",
                "last_name": "Sidorov",
                "birth_date": "02.03.1972",
                "email": "sidorov@mail.com",
            }

        }
    }
]
  • Пример списка, содержащего кортежи:
img_rgb: list[tuple[tuple[int, int, int], ...]] = [
    (
        (255, 0, 0), (0, 255, 0), (0, 0, 255)
    ),

    (
        (255, 255, 0), (255, 0, 255), (0, 255, 255)
    ),

    (
        (128, 128, 128), (0, 0, 0), (255, 255, 255)
    )
]
  • Пример списка, содержащего функции:
from typing import Callable

# list[Callable, ...] указывает, что список builtin_funcs
# содержит элементы вызываемого типа и может иметь любую длину.
builtin_funcs: list[Callable, ...] = [
    print, id, type, isinstance, pow, round, range,
    min, max, sum, len, list, tuple, dict, set, frozenset,
    enumerate, all, any, map, zip, filter, next, iter,
]

Как видно из приведенных примеров, списки могут содержать не только элементы одного типа, но и комбинации различных типов данных, таких как числа, строки, функции, контейнеры (списки, кортежи, множества, словари) и другие.


Методы списков

Методы представляют функции, привязанные к определенному типу данных, доступные для объектов этого типа. Например, методы строк недоступны для списков и наоборот.

Методы могут принимать аргументы или не принимать их вовсе. Рассмотрим основные методы списков и их особенности:


list.append() - добавляет элемент в конец списка.

Метод append() принимает один аргумент (элемент для добавления) и добавляет его в конец списка.

months: list[str, ...] = ['January', 'February']

months.append('March')
print(months)  # ['January', 'February', 'March']

Если передать коллекцию (список, кортеж и т.д.) в качестве аргумента, она добавится целиком в список.

months.append(['April', 'May', 'June'])

print(months)  # ['January', 'February', 'March', ['April', 'May', 'June']]

Метод ничего не возвращает, поэтому присваивание результата months = months.append('June') вернет None.

months = months.append('June')

print(months)  # None

list.clear() - удаляет все элементы из списка.

Метод очищает текущий экземпляр списка. На практике используется, например, при реализации абстрактных типов, таких как стеки или очереди.

months: list[str, ...] = ['January', 'February']
print(f"{months}\nID: {id(months)}")
# ['January', 'February']
# ID: 2481832355584

months.clear()
print(f"{months}\nID: {id(months)}")
# []
# ID: 2481832355584

Объект остается тем же, но теперь он пустой.

Однако, если вы попытаетесь очистить список другим способом, например months = [], вы создадите новый объект списка.

months: list[str, ...] = ['January', 'February']
print(f"{months}\nID: {id(months)}")
# ['January', 'February']
# ID: 2481832355584

months = []
print(f"{months}\nID: {id(months)}")
# []
# ID: 2178574284416

Это важно понимать, что в этом случае вы получаете другой объект, а не тот что было изначально.

Так же этот метод ничего не возвращает, поэтому нельзя использовать следующий код.

months: list[str, ...] = ['January', 'February']

months = months.clear()

print(months)  # None

list.extend() - расширяет список, добавляя элементы из другого списка или итерируемого объекта.

Этот метод очень напоминает .append(), но отличается тем, что принимает только итерируемый объект в качестве аргумента.

months: list[str, ...] = ['January', 'February']

months.extend(['April', 'May', 'June'])

print(months)  # ['January', 'February', 'April', 'May', 'June']

Обратите внимание, метод принимает только итерируемый объект в качестве аргумента. Попытка передать объект другого типа вызовет ошибку TypeError.

months: list[str, ...] = ['January', 'February']

months.extend(199)  # TypeError: 'int' object is not iterable
print(months)

или

months: list[str, ...] = ['January', 'February']

months.extend(True)  # TypeError: 'bool' object is not iterable
print(months)

Если передать строку, каждый символ строки будет добавлен как отдельный элемент списка.

months: list[str, ...] = ['January', 'February']

months.extend('March')
print(months)  # ['January', 'February', 'M', 'a', 'r', 'c', 'h']

Строковый тип является итерируемым, передавая строку в качестве аргумента вы говорите методу, добавлять каждый элемент строки в конец списка как отдельный элемент (расширить список элементами строки).

Если вам необходимо расширить один список элементами другого списка, то вы использовать оператор + (сложение) вместо метода list.extend().

months: list[str, ...] = ['January', 'February']
print(f"{months}\nID: {id(months)}")
# ['January', 'February']
# ID: 1651361474304

spring: list[str, ...] = ['March', 'April', 'May']

months.extend(spring)
print(f"{months}\nID: {id(months)}")
# ['January', 'February', 'March', 'April', 'May']
# ID: 1651361474304

summer: list[str, ...] = ['June', 'July', 'August']

months += summer
print(f"{months}\nID: {id(months)}")
# ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August']
# ID: 1651361474304

Оператор + работает, не только между списками, но и с другими итерируемыми объектами.

months += (100, 101, 102, 103)
print(f"{months}\nID: {id(months)}")
# ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 100, 101, 102, 103]
# ID: 2035148678912

months += 'May'
print(f"{months}\nID: {id(months)}")
# ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 100, 101, 102, 103, 'M', 'a', 'y']
# ID: 1876261955328

list.count() - возвращает количество вхождений определенного элемента в списке.

Этот метод подобен методу строк и позволяет посчитать количество вхождений определенного элемента в список.

chars: list[str, ...] = ['D', 'e', 'v', 'e', 'l', 'o', 'p', 'e', 'r']

search_item_count: int = chars.count('e')

print(search_item_count)  # 3

list.index() - возвращает индекс первого вхождения определенного элемента в списке.

chars: list[str, ...] = ['D', 'e', 'v', 'e', 'l', 'o', 'p', 'e', 'r']

search_item_idx: int = chars.index('e')

print(search_item_idx)  # 1

Если искомый элемент не существует в списке, получите ошибку ValueError.

chars: list[str, ...] = ['D', 'e', 'v', 'e', 'l', 'o', 'p', 'e', 'r']

search_item_idx: int = chars.index('g')  # ValueError: 'g' is not in list

print(search_item_idx)

list.insert() - вставляет элемент в определенную позицию списка.

Метод принимает два аргумента, первый - это индекс, в который нужно вставить элемент, а второй - сам элемент.

Рассмотрим примеры:

solfege: list[str, ...] = ['re', 'mi', 'sol', 'la']
solfege.insert(0, 'do')
print(solfege)  # ['do', 're', 'mi', 'sol', 'la']

Элемент вставляется в указанную позицию, при этом элементы справа от вставленного сдвигаются на одну позицию вправо.

solfege: list[str, ...] = ['do', 're', 'mi', 'sol', 'la']
solfege.insert(3, 'fa')
print(solfege)  # ['do', 're', 'mi', 'fa', 'sol', 'la']
solfege: list[str, ...] = ['do', 're', 'mi', 'fa', 'sol', 'la', 'ti']
solfege.insert(-2, 'negative index')
print(solfege)  # ['do', 're', 'mi', 'fa', 'sol', 'negative index', 'la', 'ti']

Если переданный положительный индекс выходит за пределы диапазона индексов списка, вставляемый элемент добавляется в конец списка.

solfege: list[str, ...] = ['do', 're', 'mi', 'fa', 'sol', 'la']
solfege.insert(73, 'ti')
print(solfege)  # ['do', 're', 'mi', 'fa', 'sol', 'la', 'ti']

Если передать отрицательный индекс, который выходит за диапазон допустимых индексов, вставляемый элемент добавляется в начало списка.

solfege: list[str, ...] = ['do', 're', 'mi', 'fa', 'sol', 'la', 'ti']
solfege.insert(-19, 'test')
print(solfege)  # ['test', 'do', 're', 'mi', 'fa', 'sol', 'la', 'ti']

list.pop() - удаляет элемент с определенной позиции и возвращает его. Если индекс не указан, удаляется последний элемент и возвращается.

Этот метод может быть вызван с аргументом или без него. Если передан индекс, то элемент с этим индексом удаляется и возвращается.

Рассмотрим примеры:

teletubbies: list[str, ...] = ['Tinky Winky', 'Po', 'Laa-Laa', 'Dipsy']

var_po: str = teletubbies.pop(1)

print(teletubbies)  # ['Tinky Winky', 'Laa-Laa', 'Dipsy']
print(var_po)  # 'Po'

Если метод вызывается без аргумента, то удаляется и возвращается последний элемент.

teletubbies: list[str, ...] = ['Tinky Winky', 'Po', 'Laa-Laa', 'Dipsy']

var_dipsy: str = teletubbies.pop()

print(teletubbies)  # ['Tinky Winky', 'Po', 'Laa-Laa']
print(var_dipsy)  # 'Dipsy'

Также можно передать отрицательный индекс:

teletubbies: list[str, ...] = ['Tinky Winky', 'Po', 'Laa-Laa', 'Dipsy']

var_laa: str = teletubbies.pop(-2)

print(teletubbies)  # ['Tinky Winky', 'Po', 'Dipsy']
print(var_laa)  # 'Laa-Laa'

Если передать индекс, который выходит за допустимый диапазон индексов списка, возникнет ошибка IndexError.

teletubbies: list[str, ...] = ['Tinky Winky', 'Po', 'Laa-Laa', 'Dipsy']

var_some_item: str = teletubbies.pop(19)  # IndexError: pop index out of range

print(teletubbies)
print(var_some_item)

Если метод вызывается у пустого списка, также возникнет ошибка IndexError.

empty: list[int, ...] = []
empty.pop()  # IndexError: pop from empty list

list.remove() - удаляет первое вхождение элемента с определенным значением из списка.

nums: list[int, ...] = [10, 20, 90, 30, 50, 40, 30, 70, 30]
nums.remove(30)
print(nums)  # [10, 20, 90, 50, 40, 30, 70, 30]

Метод удаляет только первое вхождение значения в списке. В примере было удалено число 30.

Если удаляемого элемента нет в списке, возникает ошибка ValueError.

nums: list[int, ...] = [10, 20, 90, 30, 50, 40, 30, 70, 30]
nums.remove(60)  # ValueError: list.remove(x): x not in list
print(nums)

list.sort() - сортирует элементы списка по возрастанию (или по заданному ключу при необходимости).

Сортировка списка в порядке возрастания.

nums: list[int, ...] = [10, 20, 90, 30, 50, 40, 30, 70, 30]
nums.sort()
print(nums)  # [10, 20, 30, 30, 30, 40, 50, 70, 90]
animals: list[str, ...] = ['cat', 'dog', 'rabbit', 'dog', 'guinea pig', 'fox', 'kangaroo']
animals.sort()
print(animals)  # ['cat', 'dog', 'dog', 'fox', 'guinea pig', 'kangaroo', 'rabbit']

Если в параметр reverse передать значение True, список будет отсортирован в порядке убывания.

nums: list[int, ...] = [10, 20, 90, 30, 50, 40, 30, 70, 30]
nums.sort(reverse=True)
print(nums)  # [90, 70, 50, 40, 30, 30, 30, 20, 10]
animals: list[str, ...] = ['cat', 'dog', 'rabbit', 'dog', 'guinea pig', 'fox', 'kangaroo']
animals.sort(reverse=True)
print(animals)  # ['rabbit', 'kangaroo', 'guinea pig', 'fox', 'dog', 'dog', 'cat']

Если список содержит элементы, которые нельзя сравнить между собой, возникнет ошибка TypeError.

mix: list[str | int, ...] = ['abc', 'def', 10, 'ghi', 20]
mix.sort()  # TypeError: '<' not supported between instances of 'int' and 'str'

list.reverse() - изменяет порядок элементов списка на обратный.

nums: list[int, ...] = [30, 10, 40, 50, 20]
nums.reverse()

print(nums)  # [20, 50, 40, 10, 30]

Есть способ получить обратный порядок без использования .reverse() - с помощью срезов.

nums: list[int, ...] = [30, 10, 40, 50, 20]
nums = nums[::-1]

print(nums)  # [20, 50, 40, 10, 30]

Основное различие между .reverse() и срезами заключается в том, что .reverse() изменяет порядок элементов в текущем списке, тогда как срезы создают новый список с обратным порядком элементов.

nums: list[int, ...] = [30, 10, 40, 50, 20]
print('ID:', id(nums))  # ID: 2898074756864
nums.reverse()

print('ID:', id(nums))  # ID: 2898074756864

nums = nums[::-1]
print('ID:', id(nums))  # ID: 2898075066240

Это важно учитывать при использовании этих способов.


list.copy() - возвращает копию списка.

Метод создает копию списка, при этом создается новый объект.

nums_arr: list[int | list, ...] = [
    10, 20, 30, [1, 4, 9, 16],
]

new_nums_arr: list[int | list, ...] = nums_arr.copy()

print("ID:", id(nums_arr))  # ID: 2954236141440
print("ID:", id(new_nums_arr))  # ID: 2954235836032

При изменении значения элемента в новом списке, оно изменяется только в новом списке, оставляя исходный список неизменным.

new_nums_arr[0] = 777

print(nums_arr)  # [10, 20, 30, [1, 4, 9, 16]]
print(new_nums_arr)  # [777, 20, 30, [1, 4, 9, 16]]

Есть нюанс, если в списке присутствуют другие ссылочные типы (например, вложенные списки, словари), изменение элементов вложенного списка приведет к изменению значений в обоих списках.

new_nums_arr[3][1] = 999

print(nums_arr)  # [10, 20, 30, [1, 999, 9, 16]]
print(new_nums_arr)  # [777, 20, 30, [1, 999, 9, 16]]

Это называется поверхностной копией списка. Если необходимо избежать такого поведения, чтобы изменения в копии не затрагивали оригинальный список, можно воспользоваться функцией deepcopy() из модуля copy.

from copy import deepcopy

nums_arr: list[int | list, ...] = [
    10, 20, 30, [1, 4, 9, 16],
]

new_nums_arr: list[int | list, ...] = deepcopy(nums_arr)

new_nums_arr[3][1] = 999

print(nums_arr)  # [10, 20, 30, [1, 4, 9, 16]]
print(new_nums_arr)  # [10, 20, 30, [1, 999, 9, 16]]

Узнайте больше о глубоком копировании списков в Python.

На русском:

На английском:


Генераторы списков

Если этот раздел изучите тщательно, то остальные генераторы, такие как генераторы множеств и словарей будет легче воспринимать. У всех этих генераторов логика работы одинаковая.

Ранее мы видели, как применить итерацию для перебора списка в простом примере.

n = int(input('Сколько чисел хотите ввести?: '))

numbers: list[int, ...] = []
for _ in range(n):
    num: int = int(input('Введите число: '))
    numbers.append(num)

print(numbers)

Этот код прост и легко читается. Ту же программу можно написать, используя генераторы списков.

Как выглдяит синтаксис генераторов списков:

[<выражение или операция с переменной> for <переменная> in <итерируемый объект>]

В выражении генератора списков можно использовать различные элементы, такие как:

  • переменные
  • функции
  • тернарный опреатор
  • обычные операции со значением

Перепишем выше приведенный пример с помощью генераторов списков.

n = int(input('Сколько чисел хотите ввести?: '))

numbers: list[int, ...] = [int(input('Введите число: ')) for _ in range(n)]

print(numbers)

Код стал более компактным, однако, читаемость кода немного ухудшилась. Это несущественно для опытных разработчиков, но новичкам может быть сложнее понять такой код с первого раза.

print([int(input('Введите число: ')) for _ in range(int(input('Сколько чисел хотите ввести?: ')))])

Теперь избавились и от одноразовых переменных. Все три примера будут работать одинаково, но не стоит пренебрегать читаемостью кода.

"Читаемость имеет значение"

"Readability counts."

Поэтому придерживайтесь этого принципа.

Если ваш код легко читается с использованием генераторов списков, пишите код с их применением. Если видите, что читаемость ухудшилась, лучше используйте итерации.

Кроме синтаксиса, генераторы отличаются от итераторов по скорости работы - они работают быстрее. Давайте сравним скорость работы итераторов и генераторов списков.

  • Итераторы
from time import time

n = 1_000_000

start_time: float = time()

nums: list[int, ...] = []
for i in range(n):
    nums.append(i)

end_time: float = time()

print('Время выполнения:', end_time - start_time)

# Вывод
# Время выполнения: 0.11000514030456543
  • Генераторы
from time import time

n = 1_000_000

start_time: float = time()

nums: list[int, ...] = [i for i in range(n)]

end_time: float = time()

print('Время выполнения:', end_time - start_time)

# Вывод
# Время выполнения: 0.055390119552612305

Как видно, генераторы списков работают чуть быстрее, примерно в два раза, чем обычные итераторы.

Рассмотрим различные примеры, для лучшего понимания. Приведу примеры в двух форматах с применением обычных циклов (итераторов) и генераторов списков.

Представим, нам нужно получить от пользователя целые числа, разделенные пробелами в одной строке, а затем преобразовать их в целочисленный тип.

  • Итераторы
# Тестовые данные: 15 54 26 0 36 -32 5 12
nums: list[str, ...] = input().split()

result: list[int, ...] = []
for num in nums:
    num = int(num)
    result.append(num)

print(nums)  # ['15', '54', '26', '0', '36', '-32', '5', '12']
print(result)  # [15, 54, 26, 0, 36, -32, 5, 12]
  • Генераторы
# Тестовые данные: 15 54 26 0 36 -32 5 12

result: list[int, ...] = [int(num) for num in input().split()]
print(result)  # [15, 54, 26, 0, 36, -32, 5, 12]

Перейдем к более сложному примеру: Что если пользователь вводит не числовые символы?

С использованием итераторов все просто, добавим условие if для проверки и готово.

  • Итераторы
# Тестовые данные: 15 abc 54 26 ghi 0 36 jkl -32 $$$ 5 *23ad_ 12
nums: list[str, ...] = input().split()

result: list[int, ...] = []
for num in nums:
    if num.isdigit():
        num = int(num)
        result.append(num)

print(nums)  # ['15', 'abc', '54', '26', 'ghi', '0', '36', 'jkl', '-32', '$$$', '5', '*23ad_', '12']
print(result)  # [15, 54, 26, 0, 36, 5, 12]

Отлично, все получилось! Теперь рассмотрим, как использовать условный оператор if в генераторах списков.

Синтаксис использования условий в генераторах списков выглядит следующим образом:

[<выражение или операция с переменной> for <переменная> in <итерируемый объект> if <условие>]
  • Генераторы
# Тестовые данные: 15 abc 54 26 ghi 0 36 jkl -32 $$$ 5 *23ad_ 12

result: list[int, ...] = [int(num) for num in input().split() if num.isdigit()]
print(result)  # [15, 54, 26, 0, 36, 5, 12]

Все получилось!


Заметьте, что в обоих примерах в результирующий список не попало число -32. Это связано с тем, что метод строки str.isdigit() возвращает False при проверке чисел со знаками.

print('-32'.isdigit())  # False
print('+32'.isdigit())  # False

Добавим проверку на наличие знака + или - и последующих цифр после него

  • Итераторы
# Тестовые данные: 15 abc 54 26 ghi 0 36 jkl -32 $$$ 5 *23ad_ 12
nums: list[str, ...] = input().split()

result: list[int, ...] = []
for num in nums:
    # Проверяем, состоит ли строка из цифр
    # ИЛИ
    # Строка начинается с символа + или - И за ними следуют цифры
    if (
        num.isdigit()
        or (num.startswith(('+', '-')) and num[1:].isdigit())
    ):
        num = int(num)
        result.append(num)

print(nums)  # ['15', 'abc', '54', '26', 'ghi', '0', '36', 'jkl', '-32', '$$$', '5', '*23ad_', '12']
print(result)  # [15, 54, 26, 0, 36, -32, 5, 12]

Такое условие даже в итераторах довольно трудно воспринимается. Рассмотрим пример с использованием генераторов списков.

  • Генераторы
# Тестовые данные: 15 abc 54 26 ghi 0 36 jkl -32 $$$ 5 *23ad_ 12

result: list[int, ...] = [
    int(num) for num in input().split()
    if num.isdigit() or (num.startswith(('+', '-')) and num[1:].isdigit())
]
print(result)  # [15, 54, 26, 0, 36, -32, 5, 12]

Логическая строка с генератором списков была разделена на несколько физических строк, чтобы соответствовать рекомендациям PEP8 относительно максимальной длины строки кода.


В результирующий список мы можем добавим не только само значение, а что-то другое исходя из условий.

Рассмотрим довольно попульярный пример. Предположим у нас есть список целих чисел, нужно добавить в список соответствующие обозночения "четное" если число четное, "нечетное" в противном случае.

  • Итераторы
nums: list[int, ...] = [23, 46, 32, 65, 87, 10]

result: list[str, ...] = []
for num in nums:
    if num % 2 == 0:
        result.append('Четное')
    else:
        result.append('Нечетное')

print(result)  # ['Нечетное', 'Четное', 'Четное', 'Нечетное', 'Нечетное', 'Четное']

С итератором все довольно просто. Как это применить в генераторах, рассмотрим ещё один синтаксис генераторов.

[ < значение_1 > if < условие > else < значение_2 >
for < переменная > in < итерируемый объект >]
nums: list[int, ...] = [23, 46, 32, 65, 87, 10]

result: list[str, ...] = ['Четное' if num % 2 == 0 else 'Нечетное' for num in nums]

print(result)  # ['Нечетное', 'Четное', 'Четное', 'Нечетное', 'Нечетное', 'Четное']

Мы знаем, что можно использовать вложенные итераторы для решения различных задач. И здесь важно заметить, что также существуют вложенные генераторы списков.

Синтаксис вложенных генераторов списков выглядит следующим образом:

[
  for <переменная_внешнего_цикла> in <внешний_итерируемый_объект>
  for <переменная_внутреннего_цикла> in <внутренний_итерируемый_объект>
]

Также в них можно использовать условные блоки if и тернарный оператор, это полный синтаксис:

[
    <значение_1> if <условие_1> else <значение_2>
    for <переменная_внешнего_цикла> in <внешний_итерируемый_объект>
    for <переменная_внутреннего_цикла> in <внутренний_итерируемый_объект>
    if <условие_2>
]

Предположим у нас есть двумерный список, и мы хотим создать одномерный список элементов из вложенных списков. То есть, нам нужно "распаковать" вложенные списки в одномерный список.

  • Итераторы
months: list[list[str, ...], ...] = [
    ['Декабрь', 'Январь', 'Февраль'],
    ['Март', 'Апрель', 'Май'],
    ['Июнь', 'Июль', 'Август'],
    ['Сентябрь', 'Октябрь', 'Ноябрь'],
]

result: list[str, ...] = []

for i in range(len(months)):
    for j in range(len(months[i])):
        result.append(months[i][j])

print(result)
# ['Декабрь', 'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь']
  • Генераторы
months: list[list[str, ...], ...] = [
    ['Декабрь', 'Январь', 'Февраль'],
    ['Март', 'Апрель', 'Май'],
    ['Июнь', 'Июль', 'Август'],
    ['Сентябрь', 'Октябрь', 'Ноябрь'],
]

result: list[str, ...] = [months[i][j] for i in range(len(months)) for j in range(len(months[i]))]

print(result)
# ['Декабрь', 'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь']

Первый вариант более прост в понимании по сравнению с использованием генератора списков. Выбор способа зависит от конкретной ситуации, но важно помнить, что читаемость кода имеет значение.


Мы рассмотрели один из наиболее популярных типов данных в программировании и надеемся, что вы получили ценные знания из этого раздела. В ходе изучения ознакомились с основными приемами работы и ключевыми аспектами списков. Однако есть ещё много других тонкостей, которые вы будете постигать со временем.

Надеемся, что представленный материал оказался для вас полезным! Даже если кажется, что многие детали не укладываются в памяти, не волнуйтесь. Запоминание всех нюансов - постепенный процесс. Важно иметь под рукой справочник для быстрого освежения памяти.

Следующая тема будет посвящена кортежам в Python.


About

10. Работа со списками

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages