Структуры данных
Списки/массивы
Список работает как набор «безымянных переменных», называемых его элементами. Элементы нумеруются последовательными натуральными числами (начиная с нуля).
Элемент списка можно получить при помощи формулы список[номер]
, где
список
— формула, значением которой является интересующий нас список,
а номер
— формула, значением которой является интересующий нас номер
элемента.
Узнать количество элементов списка можно функцией len
.
Изменить элемент списка можно командой изменения. Например:
foo[3] = 5 ## привязать третий элемент к пятёрке
Очень важный момент: каждый списковый литерал создаёт новый список, а перепривязка переменной — нет.
foo = [1,2,3,4,5]
bar = foo # bar привязана к тому же списку, что и foo
bar[0] = 5 # теперь и foo[0] == 5
baz = [1,2,3,4,5] # а это -- уже совсем другой список
bar[1] = baz[1] # первый элемент списка bar теперь привязан к
# значению формулы baz[1], то есть числу 2
# (но не к первому элементу списка baz!)
Чтобы скопировать часть списка (то есть создать новый список с теми же привязками), можно воспользоваться срезом:
foo = [1, [2, 3], 4]
bar = foo[1:3] # копируем элементы списка с 1-го (включая) по 3-й (исключая)
bar[1] = 5 # foo[2] по-прежнему 4
bar[0][0] = 5 # теперь и foo[1][0] == 5: срез делает "неглубокую" копию
В срезе можно опустить один или оба индекса. Тогда они считаются равными
нулю и количеству элементов списка соответственно. В частности,
foo[:]
делает копию всего списка foo
.
Также допускаются срезы с тремя индексами (подробности — в документации).
Добавить новые элементы к списку можно при помощи метода
(атрибута-подпрограммы) append
: foo.append(123)
добавляет в конец
списка foo
элемент, привязанный к числу 123.
Убрать элемент с конца списка можно методом pop
вот так: foo.pop()
.
Хотя pop
можно применять и к конкретному номеру, удаление элементов
из списка занимает время, пропорциональное разности длины списка и
номера удаляемого элемента.
List comprehesion
Также часто можно встретить т.н. «теоретико-множественную» запись:
[x ** 2 for xs in ys for x in xs if x < 3]
В общем случае такая запись выглядит как расположенная внутри квадратных скобок
формула, за которой следует цепочка for ... in ...
или if ...
.
Вычисление такой формулы происходит ровно так же, как и вычисление переменной
result
в следующем коде:
result = []
for xs in ys:
for x in xs:
if x < 3:
result.append(x ** 2)
То есть соответствующие циклы и ветвления распологаются
в той же последовательности друг внутри друга, а формула становится
второй подформулой оператора вызова функции (первой подформулой которого
становится result.append
).
Естественно, никакой переменной result
при вычислении
«теоретико-множественных записей» не происходит.
Кортежи
Кортежи отличаются от списков только тем, что их структуру (длину и привязки элементов) нельзя изменять. Оформляются кортежные литералы круглыми скобками или же вообще просто запятыми:
foo = 1, 2, 3
bar = (1, 2, 3)
Впрочем, в непонятных ситуациях скобки лучше ставить. К тому же,
пустой кортеж ()
и одноэлементый кортеж (элемент,)
нельзя записать
без скобок.
Несмотря на неизменяемость кортежей, никто не мешает на основе список и кортежей делать, например, «списки постоянной длины»:
foo = ([1], [2], [3])
foo[1][0] = 5 # теперь foo == ([1], [5], [3])
# можно даже спрятать соответствующие формулы с [0] за функциями
def get_elem(a, i): return a[i][0]
def set_elem(a, i, v): a[i][0] = v
# теперь, не зная о внутренней структуре "списков постоянной длины", их
# "элементы" невозможно (зло)намеренно удлинить
Тексты
Ведут себя как кортежи одноэлементых текстов (как бы странно подобная
рекуррентная семантика ни выглядела). Например, "f"
, "foo"[0]
,
"foo"[0][0]
, "foo"[0][0][0]
и так далее — одно и то же.
В отличие от кортежей, операция конкатенации весьма эффективна.
То есть ожидаемой квадратичной зависимости времени работы от значения
переменной N
следующий код не проявляет:
text = ""
for x in range(N):
text += str(x)
Отметим также многострочные литералы, оформляющиеся тройными кавычками. Эти литералы зачастую используются вместо многострочных комментариев (которых в языке нет).
Ещё стоят упоминания появившиеся сравнительно недавно (на момент 2022 года)
в языке интерполирующие литералы, оформляющиеся буквой f
перед кавычками.
Внутри таких литералов можно заключать в фигурные скобки произволные
формулы, значения которых имеют текстовое представление.
То есть вместо str(3) + " + " + str(5) + " = " + str(3+5)
рекомендуется
писать f"{3} + {5} = {3+5}"
.
Байтовые кортежи
Функция bytes
позволяет преобразовать последовательность чисел, находящихся
в пределах от 0 до 255, в байтовый кортеж. Байтовые кортежи
требуются некоторыми функциями ввода-вывода.
Отметим, что в Python встроен кодировщик/декодировщик текстовой кодировки UTF-8:
bytes(текст, "utf-8")
— байты кодировки текстаstr(байты, "utf-8")
— текст, закодированный байтами
Словари
Позволяют произвольный неизменяемый (числа, тексты, кортежи) ключ привязать к произвольному значению.
{} ## пустой словарь
{ "foo": 3, 3: "bar", 4: None } ## ключ "foo" привязан к 3
## ключ 3 привязан к "bar"
## ключ 4 привязан к None
foo = {}
foo["foo"] = 3
foo[3] = "bar"
foo[4] = None ## теперь переменная foo привязана к словарю,
## эквивалентному таковому из второй строчки
del foo[3] ## привязки можно удалять; это -- быстрая операция
3 in foo ## наличие привязки можно проверить; это -- быстрая операция
len(foo) ## количество привязок можно посчитать; тоже быстрая операция
Следует быть очень аккуратным при использовании цикла for
:
если for
с массивом проходится по значениям (то есть по привязкам
чисел-ключей), то for
со словарём проходится по самим ключам.
Поэтому, к сожалению, зачастую нельзя подменять массивы словарями
без существенной переработки кода, использовавшего эти массивы.
Также нужно учитывать, что for
проходится по словарю в произвольном
порядке (конкретный произвол которого в районе версии языка 3.6 сильно менялся).
Множества
По поведению аналогичны словарям, у которых все ключи привязаны к None
.
Обладают отдельным синтаксисом и дополнительным набором
теоретико-множественных операций над ними.
set() ## пустое множество
{1, 2, "foo"} ## трёхэлементное множество
foo = set()
foo.add(1)
foo.add(2)
foo.add("foo") ## теперь переменная foo привязана к множеству,
## эквивалентному таковому из второй строчки