Списки в python

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

Синтаксис

a = [2, 4, 6]
# print(b) выведет [2, 4, 6]

Как в случае других переменных, иногда нужно задать пустое значение списку (как 0 для чисел или ” для строк). Делается так:

a = []

В списке ЛУЧШЕ хранить один тип данных, а не кидать туда всё подряд по типу [3, 'test', 0, 'asd', False]. Так нельзя, ибо доступ к отдельным элементам списка затрудняется.

Элементы списка (a[0], a[1] и a[2]) - это, по сути, имена, указывающие, in this case, на int объекты со значениями 2, 4, 6.


.append() и .extend()

Чтобы добавить в конец списка элемент - нужно:

b.append(10)
# или
b.append('test')
# или
a = input()
b.append(a)

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

a = [4, 5, 6]
a.extend([1, 2, 3])
print(a)  # [4, 5, 6, 1, 2, 3]
 
# Не стоит делать так:
first_list = first_list + second_list
# либо:
first_list += second_list
# потому что такой метод работает МЕДЛЕННЕЕ, чем extend()

Как называются такие конструкции, почему используется точка?

Конструкция типа list.append() называется методом. Методы - это функции, которые связаны с определенным объектом и могут быть вызваны через оператор точки.

В примере list.append()append() - это метод объекта list, который добавляет новый элемент в конец списка. Оператор точки используется для вызова метода на конкретном объекте. В этом случае, метод append() вызывается на объекте list, чтобы добавить новый элемент в список.

Методы являются одним из способов организации и структурирования кода в ООП (объектно-ориентированном программировании). Они позволяют связать функциональность с определенным объектом, что упрощает и улучшает читаемость кода. Кроме того, методы позволяют изолировать и скрыть детали реализации функциональности, что делает код более модульным и поддерживаемым.


Индекс

Это номер позиции элемента в списке.
Индекс номера 5 в списке [1, 5, 45, 0] будет равен 2, то есть, второй элемент списка.

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

scores = [8, 5, 10, 7, 6]
 
# удвоить второй элемент можно так:
scores[1] *= 2  # [8, 10, 10, 7, 6]
 
# последний элемент
print(scores[-1])  # тоже самое можно вывести с помощью len():
# print(scores[len(scores) - 1])
# Таким образом в питоне можно работать со списками СПРАВА НАЛЕВО
# с помощью отрицательных чисел
 
 
# можно присваивать переменным значение элементов
tmp = scores[3]  # tmp = 7
 
 
#МЕТОД .index()👇
# узнать индекс элемента в списке можно так:
scores.index(10)  # 2

Здесь тоже, как и везде в программировании, отчет ведется с 0. Поэтому первый элемент списка имеет индекс 0, а не 1.


Строки

# Добавление в список значения переменной ПОСИМВОЛЬНО:
word = python
symbol_list = list(char for char in word)
# ['p', 'y', 't', 'h', 'o', 'n']
 
#либо попроще (вариант выше мне подсказал ИИ (а ниже - лектор))
symbol_list = list(word)

Срезы (slices)

Срезы позволяют брать нужный кусок списка.

Копии списка

Как раз чтобы избежать ошибок при копировании списков вместо:

some_list = another_list
# потому что в этом случае просто создается новая "ссылка" на список some_list
# копии списка нет

Нужно использовать такой слайс:

some_list = another_list[:]

Куски из списка

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

nums = [x for x in range(1, 101) if (x % 10 == 0)]
# [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
for element_index in range(2, 8):
	print(nums[element_index])
# [30, 40, 50, 60, 70]

Можно:

nums = [x for x in range(1, 101) if (x % 10 == 0)]
# [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
print(nums[2:8])
# [30, 40, 50, 60, 70]
 
# срез по индексам 2,3,4,5,6,7
# как и в range, последняя цифра не учитывается

Также если нужно взять срез до/с какого-то индекса нужно:

nums = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
 
print(nums[:4])  # срез до 4-го элемента
# [10, 20, 30, 40]
print(nums[6:])  # срез с 6-го элемента до конца списка
# [70, 80, 90, 100]

Шаг в срезе:

nums = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
 
print(nums[2:8:2])  # срез от 2-го до 8-го элемента с шагом 2
# [30, 50, 70]
print(nums[8:2:-2])
# [90, 70, 50]

Замена с помощью срезов:

nums = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
 
# Замена первых 3-х элементов на единицы
nums[:3] = [1, 1, 1]
# [1, 1, 1, 40, 50, 60, 70, 80, 90, 100]
 
# Замена первых 3-х элементов на одну единицу
nums[:3] = [1]
# [1, 40, 50, 60, 70, 80, 90, 100]

Можно взять срез из среза (стэкать их):

nums = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
 
print(nums[2:8:2])
# [30, 50, 70]
 
print(nums[2:8:2][::-1])
# [70, 50, 30]

Сдвиг списка

Так, чтобы сдвинуть на 3 шага список numbers (получить [4, 5, 6, 7, 1, 2, 3] из [1, 2, 3, 4, 5, 6, 7]) - нужно сложить слайсы ([N:] + [:N]) с нужным шагом (3, в нашем случае):

numbers = [1, 2, 3, 4, 5, 6, 7]
 
numbers[3:] + numbers[:3]  # = [4, 5, 6, 7, 1, 2, 3]
# [4, 5, 6, 7] + [1, 2, 3]   = [4, 5, 6, 7, 1, 2, 3]
 
# В итоге:
# [1, 2, 3, 4, 5, 6, 7] -> [4, 5, 6, 7, 1, 2, 3]

Чтобы поменять результат - можно сделать шаг отрицательным и тестить.

Переворачивание списка

Чтобы перевернуть список нужно выбрать весь список (:) и после еще одного двоеточия указать шаг -1 [::-1].
Без второго двоеточия получится “[:-1] весь срез без последнего элемента”:

# [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
 
print(nums[::-1])  # REVERSE
# [100, 90, 80, 70, 60, 50, 40, 30, 20, 10]
 
print(nums[:-1])  # whole list without last element
# [10, 20, 30, 40, 50, 60, 70, 80, 90]

Метод insert

Позволяет добавлять элемент в список по указанному индексом позицию.
Для этого в скобках some_list.insert() указываются индекс и элемент, который нужно вставить:

prog_langs = ['Python', 'Java', 'JS', 'SQL']
prog_langs.insert(1, 'C++')
 
print(prog_langs)
# ['Python', 'C++', 'Java', 'JS', 'SQL']

Метод remove

Позволяет удалять первый найденный элемент в списке по его имени.
Для этого в скобках some_list.remove() указывается элемент, который нужно удалить:

prog_langs = ['Python', 'Java', 'JS', 'SQL']
prog_langs.remove('Python')
 
print(prog_langs)
# ['Java', 'JS', 'SQL']

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

В случаях, когда стоит задача удалить несколько элементов, например, числа 0 из всего списка - метод remove() не подходит потому, что он сам по себе работает не эффективно для такого рода задач. Он после поиска в списке нужного элемента и его удаления сдвигает весь список налево, чтобы заполнить пустоту.


Оператор del

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

>>> nums = [111, 222, 333, 444, 555]
>>> del nums[-1]
>>> nums
[111, 222, 333, 444]
 
>>> nums = [111, 222, 333, 444, 555]
>>> del nums[1]
>>> nums
[111, 333, 444, 555]

Метод count()

Чтобы узнать, например, кол-во цифры 7 в списке нужно:

numbers = [1, 2, 5, 7, 6, 7, 3]
 
numbers.count(7)  # 2

Функция list()

Добавление строки посимвольно в список:

word = 'Python!'
symbols = list(word)
 
print(symbols)  # ['P', 'y', 't', 'h', 'o', 'n', '!']

Заполнить список с помощью range():

numbers = list(range(5))  # [0, 1, 2, 3, 4]
numbers = list(range(1, 4)) # [1, 2, 3, 4]

Вложенные списки

Пример вложенного списка (и его генерации для наглядности):

numbers = []
num = 1
 
for _ in range(3):
	numbers.append(list(range(num, num + 3)))
	num += 3
 
print(numbers)
# [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Чтобы получить элемент из вложенного списка нужно сначала обратиться к вложенному списку как к элементу (индексом в квадратных скобках) и рядом же поставит квадратные скобки и указать индекс нужного элемента:

word_list = [['a', 0], ['b', 0], ['c', 0]]
 
# первым идет индекс вложенного списка
# и потом - индекс элемента в этом списке
print(word_list[1][0])  # b
print(word_list[0][1])  # 0
print(word_list[2][0])  # c

List comprehensions

Еще называют генераторами списков, представлением списков, списковыми включениями. Этот механизм юзает итерирование с помощью ключевых слов for/in для генерации списков.
К слову существуют еще dict comprehensions, set comprehensions и т.д.

Синтаксис

[выражение for элемент in итерабельный объект]
[выражение for элемент in итерабельный объект if условие]

Примеры

Простой список из range()
# ВМЕСТО:
numbers = []
for x in range(10):
	numbers.append(x)
 
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
 
# НУЖНО:
numbers = [x for x in range(10)]
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Простой список из range() с выражением
# ВМЕСТО:
for x in range(10):
	squares.append(x ** 2)
 
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
 
# НУЖНО:
squares = [(x ** 2) for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# скобки для читаемости

Структура записи генерации списков (2-й пример):
x**2 - это expression (выражение)
for x in - member (переменная цикла)
range(10) - iterable (итерируемое - функция, строка, список…)

Список из строки
double = [(x * 2) for x in 'abc']
# ['aa', 'bb', 'cc']

Пример функции

some_list = [some_func(some_key, another_key) for x in another_list]
# возвращает значения из функции (работает как x здесь)

Как добавить 3 input-а сразу в список

words = [input("Enter the word: ") for _ in range(3)]
# или
words = [input(f'Enter the word #{word}: ') for word in range(3)]
 
# ['asd', 'xcv', 'gfhj']
Условия (в представлении списков)

Вместо:

odd_squares = []
for x in range(10):
	if x % 2 != 0:
		odd_squares.append(x ** 2)
 
# [1, 9, 25, 49, 81]

Нужно:

odd_squares = [(x ** 2) for x in range(10) if (x % 2 != 0)]
# [1, 9, 25, 49, 81]

Структура фильтрации элементов:
Условие добавили в конце представления, то есть как фильтр.
if (x % 2 != 0) - условие

Можно даже юзать else:

odd_n_even_squares = [((x ** 2) if (x % 2 != 0) else (x ** 3))
					  for x in range(10)]
# [0, 1, 8, 9, 64, 25, 216, 49, 512, 81]
 
# то есть, (x ** 2) если (x % 2 != 0)
# иначе (x ** 3)
# скобки для читаемости

Структура выбора элементов:
<expression> если <условие> иначе <else_expression>
(x + 1) if <условие> else (x - 1)

Потом уже member и iterable.

Можно также записать готовое значение в expression:

some_list = ['some_string' if <условие> else 'another_string'
			 for _ in range(10)]
Создание списка кортежей
>>> rows = range(1,4)
>>> cols = range(1,3)
>>> cells = [(row, col) for row in rows for col in cols]
>>> for cell in cells:
		print(cell)
 
(1, 1)
(1, 2)
(2, 1)
(2, 2)
(3, 1)
(3, 2)
 
# Распаковка кортежа:
>>> for row, col in cells:
		print(row, col)
 
1 1
1 2
2 1
2 2
3 1
3 2

.reverse()

Функция .reverse() изменяет список, но не возвращает его значения:

>>> marxes = ['AAAAA', 'NNNNN', 'ZZZZZ']
>>> marxes.reverse()
>>> marxes
['ZZZZZ', 'NNNNN', 'AAAAA']

Размножение элементов с помощью оператора +

>>> ["blah"] * 3
['blah', 'blah', 'blah']

.sort() и sorted()

.sort() сортирует сам список.
sorted() возвращает отсортированную копию списка.

>>> marxes = ['Groucho', 'Chico', 'Harpo']
>>> sorted_marxes = sorted(marxes)
>>> sorted_marxes
['Chico', 'Groucho', 'Harpo']
 
>>> marxes.sort()
>>> marxes
['Chico', 'Groucho', 'Harpo']
 
>>> numbers = [2, 1, 4.0, 3]
>>> numbers.sort()
>>> numbers
[1, 2, 3, 4.0]

Reverse сортировка:

>>> numbers = [2, 1, 4.0, 3]
>>> numbers.sort(reverse=True)
>>> numbers
[4.0, 3, 2, 1]

Изменение одинаковых списков при присваивании с помощью оператора =

>>> a = [1, 2, 3]
>>> b = a
 
>>> a[0] = 'surprise'
 
>>> a
['surprise', 2, 3]
>>> b
['surprise', 2, 3]

Список b тоже изменился, ибо он просто ссылается на тот же список объектов, что и a.
Решение: Следующая глава


Копирование списков - .copy(), list(), слайсы и .deepcopy()

.copy(), list(), слайсы

Оригинальный список снова будет присвоен переменной а. Создам b с .copy(), c - с list(), а d - с помощью слайсов:

>>> a = [1, 2, 3]
>>> b = a.copy()
>>> c = list(a)
>>> d = a[:]
 
>>> a[0] = 'integer lists are boring'
>>> a
['integer lists are boring', 2, 3]
>>> b
[1, 2, 3]
>>> c
[1, 2, 3]
>>> d
[1, 2, 3]

.deepcopy()

В таких ситуациях:

>>> a = [1, 2, [8, 9]]
>>> b = a.copy()
>>> c = list(a)
>>> d = a[:]
 
# И при изменении элементов подсписка изменения произведутся и в других списках:
>>> a[2][1] = 10
>>> a
[1, 2, [8, 10]]
>>> b
[1, 2, [8, 10]]
>>> c
[1, 2, [8, 10]]
>>> d
[1, 2, [8, 10]]

нужен .deepcopy(), чтобы полностью скопировать список, иначе вложенные списки продолжат указывать на те же объекты, а не будут копией.
Короче, нужна полная “глубокая” копия:

>>> import copy
>>> a = [1, 2, [8, 9]]
>>> b = copy.deepcopy(a)
>>> a
[1, 2, [8, 9]]
>>> b
[1, 2, [8, 9]]
>>> a[2][1] = 10
>>> a
[1, 2, [8, 10]]
>>> b
[1, 2, [8, 9]]

Функция .deepcopy() работает с вложенными списками, словарями и т.п.


zip()

Функция zip() создает итератор, который объединяет элементы из нескольких источников данных. Работает со списками, кортежами, множествами и словарями для создания списков или кортежей, включающих все эти данные.

>>> list1 = [111, 222, 333, 444]
>>> list2 = [222, 333, 555, 999]
 
>>> zip(list1, list2)
<zip object at 0x7f48a1be4440>
 
>>> list(zip(list1, list2))
[(111, 222), (222, 333), (333, 555), (444, 999)]
 
# Или словарь:
>>> english = 'Monday', 'Tuesday', 'Wednesday'
>>> french = 'Lundi', 'Mardi', 'Mercredi'
>>> dict(zip(english, french))
{'Monday': 'Lundi', 'Tuesday': 'Mardi', 'Wednesday': 'Mercredi'}

Часто используемый сценарий - параллельное итерирование по нескольким последовательностям:

>>> days = ['Monday', 'Tuesday', 'Wednesday']
>>> fruits = ['banana', 'orange', 'peach']
>>> drinks = ['coffee', 'tea', 'beer']
>>> desserts = ['tiramisu', 'ice cream', 'pie', 'pudding']
 
>>> for day, fruit, drink, dessert in zip(days, fruits, drinks, desserts):
		print(day, ": drink", drink, "eat", fruit, "enjoy", dessert)
 
Monday : drink coffee eat banana enjoy tiramisu
Tuesday : drink tea eat orange enjoy ice cream
Wednesday : drink beer eat peach enjoy pie

Самый длинный список не проитерировался до конца

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


LIFO и FIFO

Если вы используете функцию .append(), чтобы добавить новые элементы в конец списка, и функцию .pop(), чтобы удалить что-либо из конца того же списка, вы работаете со структурой данных, известной как очередь LIFO (last in, first out - «последним пришел, первым ушел»). Ее чаще называют стеком. Вызов .pop(0) создает очередь FIFO (first in, first out - «первым пришел, первым ушел»). Это удобный способ собирать данные по мере их поступления и работать либо с самыми старыми (FIFO), либо с самыми новыми (LIFO).


Соус (не весь): Книга “Простой Python Глава 7. “Кортежи и списки

python