Как хранить много значений и выбрать для них подходящую структуру
enumerate и zipСписок (list) — упорядоченная изменяемая последовательность. Записывается в квадратных скобках через запятую. Индексы и срезы — как у строк, но элементы можно менять, добавлять и удалять.
>>> scores = [78, 92, 65, 88]
>>> scores[0]
78
>>> scores[-1]
88
>>> len(scores)
4
>>> scores.append(70)
>>> scores
[78, 92, 65, 88, 70]
>>> scores[2] = 67
>>> scores
[78, 92, 67, 88, 70]
>>> scores.insert(0, 100)
>>> scores
[100, 78, 92, 67, 88, 70]
>>> scores.pop()
70
>>> scores.remove(100)
>>> scores
[78, 92, 67, 88]
>>> sum(scores), min(scores), max(scores)
(325, 67, 92)
>>> 92 in scores
True
| Операция | Что делает |
|---|---|
lst.append(x) | Добавить в конец |
lst.insert(i, x) | Вставить перед позицией i |
lst.pop(), lst.pop(i) | Удалить последний (или i-й) и вернуть его |
lst.remove(x) | Удалить первое вхождение значения x; нет такого — ValueError |
lst[i] = x | Заменить элемент |
lst.extend(other), lst + other | Дописать элементы другого списка / новый общий список |
len, sum, min, max, in | Длина, сумма, минимум, максимум, проверка вхождения |
В списке могут лежать значения разных типов, в том числе другие списки, но обычно в одном списке хранят однородные данные: оценки, имена, строки файла.
Есть два способа: функция sorted(список) возвращает новый отсортированный список и не трогает исходный; метод список.sort() сортирует на месте и возвращает None.
>>> names = ["Сидоров", "Анисимов", "Петров"]
>>> sorted(names)
['Анисимов', 'Петров', 'Сидоров']
>>> names
['Сидоров', 'Анисимов', 'Петров']
>>> names.sort()
>>> names
['Анисимов', 'Петров', 'Сидоров']
>>> result = names.sort()
>>> print(result)
None
>>> nums = [5, 2, 9, 1]
>>> sorted(nums, reverse=True)
[9, 5, 2, 1]
>>> words = ["кот", "гиппопотам", "ёж", "собака"]
>>> sorted(words, key=len)
['ёж', 'кот', 'собака', 'гиппопотам']
Ошибка новичка — names = names.sort(): после неё в names лежит None. reverse=True — по убыванию. key= — функция, по результату которой сравниваются элементы: key=len сортирует слова по длине, а не по алфавиту. Русские фамилии из примера отсортировались верно, но помните о «ё» из модуля 04.
Переменная в Python — имя, указывающее на объект. b = a не копирует список, а даёт тому же списку второе имя. Изменение через одно имя видно через другое:
>>> a = [1, 2, 3]
>>> b = a
>>> b.append(4)
>>> a
[1, 2, 3, 4]
>>> c = a.copy()
>>> c.append(5)
>>> a, c
([1, 2, 3, 4], [1, 2, 3, 4, 5])
>>> grid = [[0] * 3] * 2
>>> grid[0][0] = 9
>>> grid
[[9, 0, 0], [9, 0, 0]]
>>> grid = [[0] * 3 for _ in range(2)]
>>> grid[0][0] = 9
>>> grid
[[9, 0, 0], [0, 0, 0]]
Копия — a.copy() или a[:]. Со строками и числами такого эффекта нет: они неизменяемы, любая «правка» создаёт новый объект. С изменяемыми списками, словарями и множествами — есть.
Вторая половина сеанса — та же ловушка в двумерном списке. [[0] * 3] * 2 повторяет ссылку на один и тот же внутренний список, и запись в «первую строку» изменила обе. Таблицу создают генератором списка (раздел 07), тогда каждая строка — свой объект.
Кортеж (tuple) — неизменяемая последовательность в круглых скобках. Используется для небольших групп значений, которые вместе образуют одно целое: координаты, пара «имя — балл», запись из таблицы. Неизменяемость — защита: такой объект нельзя случайно испортить.
>>> point = (55.75, 37.62)
>>> point[0]
55.75
>>> lat, lon = point
>>> lon
37.62
>>> point[0] = 0
Traceback (most recent call last):
File "<python-input-4>", line 1, in <module>
point[0] = 0
~~~~~^^^
TypeError: 'tuple' object does not support item assignment
>>> name, *rest = "Иванов Иван Иванович".split()
>>> rest
['Иван', 'Иванович']
>>> for i, city in enumerate(["Москва", "Казань", "Омск"], start=1):
... print(i, city)
...
1 Москва
2 Казань
3 Омск
>>> for name, score in zip(["Анна", "Борис"], [87, 64]):
... print(f"{name}: {score}")
...
Анна: 87
Борис: 64
Распаковка раскладывает последовательность по переменным: lat, lon = point. Звёздочка собирает «всё остальное» в список. Обмен a, b = b, a из модуля 02 — тоже распаковка кортежа.
enumerate выдаёт пары «номер, элемент» — не нужно вести счётчик вручную. zip идёт по нескольким последовательностям параллельно и останавливается на самой короткой.
Словарь (dict) хранит пары «ключ — значение» и находит значение по ключу мгновенно, сколько бы пар ни было. Записывается в фигурных скобках: {ключ: значение, …}. Ключ — неизменяемое значение (строка, число, кортеж), каждый ключ встречается один раз.
>>> phone = {"Анна": "+7 921 111-22-33", "Борис": "+7 911 444-55-66"}
>>> phone["Анна"]
'+7 921 111-22-33'
>>> phone["Вера"] = "+7 950 777-88-99"
>>> len(phone)
3
>>> phone["Глеб"]
Traceback (most recent call last):
File "<python-input-4>", line 1, in <module>
phone["Глеб"]
~~~~~^^^^^^^^
KeyError: 'Глеб'
>>> phone.get("Глеб")
>>> phone.get("Глеб", "нет номера")
'нет номера'
>>> "Борис" in phone
True
>>> del phone["Борис"]
>>> for name, number in phone.items():
... print(f"{name:6} {number}")
...
Анна +7 921 111-22-33
Вера +7 950 777-88-99
>>> list(phone.keys())
['Анна', 'Вера']
| Операция | Что делает |
|---|---|
d[k] | Значение по ключу; ключа нет — KeyError |
d.get(k, по_умолчанию) | Значение или по_умолчанию (без него — None) |
d[k] = v | Добавить пару или заменить значение |
del d[k], d.pop(k) | Удалить пару |
k in d | Есть ли ключ (не значение) |
d.keys(), d.values(), d.items() | Ключи, значения, пары — для перебора |
Словарь помнит порядок добавления ключей. for k in d перебирает ключи; пары удобнее перебирать через items() с распаковкой. phone.get("Глеб") вернул None — интерактивный режим None не печатает.
Значением может быть что угодно, в том числе список. Ведомость «имя → оценки»:
# Ведомость: имя -> список оценок
journal = {
"Анна": [5, 4, 5, 5],
"Борис": [3, 4, 3],
"Вера": [4, 5, 4, 4, 5],
"Глеб": [],
}
best_name, best_avg = None, 0
for name, marks in journal.items():
if not marks:
print(f"{name:6} оценок нет")
continue
avg = sum(marks) / len(marks)
print(f"{name:6} {len(marks)} оц. средний {avg:.2f}")
if avg > best_avg:
best_name, best_avg = name, avg
print(f"Лучший средний балл: {best_name} ({best_avg:.2f})")
$ python3 grades.py
Анна 4 оц. средний 4.75
Борис 3 оц. средний 3.33
Вера 5 оц. средний 4.40
Глеб оценок нет
Лучший средний балл: Анна (4.75)
Длинный словарь записывают по паре на строку; запятая после последней пары разрешена и упрощает добавление новых строк.
Частая задача — посчитать, сколько раз встречается каждое значение. Приём counts[k] = counts.get(k, 0) + 1 работает и для первого появления ключа:
text = input("Текст: ").lower()
counts = {}
for ch in text:
if ch.isalpha():
counts[ch] = counts.get(ch, 0) + 1
top = sorted(counts, key=counts.get, reverse=True)
for letter in top[:5]:
print(letter, counts[letter], "#" * counts[letter])
$ python3 freq.py
Текст: Карл у Клары украл кораллы, а Клара у Карла украла кларнет
а 12 ############
л 9 #########
к 8 ########
р 8 ########
у 4 ####
sorted(counts, key=counts.get, reverse=True) сортирует ключи по значению: для каждой буквы sorted вызывает counts.get(буква) и сравнивает полученные числа. При равенстве («к» и «р» по 8) сохраняется порядок, в котором ключи появились в словаре. В стандартной библиотеке для этой задачи есть готовый collections.Counter, но приём со словарём нужно уметь написать самому.
Множество (set) — набор уникальных значений без порядка. Проверка x in множество мгновенная, как поиск ключа в словаре, а в списке приходится просматривать все элементы.
>>> tags = {"python", "linux", "python", "git"}
>>> tags
{'git', 'linux', 'python'}
>>> len(tags)
3
>>> group_a = {"Анна", "Борис", "Вера"}
>>> group_b = {"Вера", "Глеб"}
>>> group_a | group_b
{'Вера', 'Глеб', 'Борис', 'Анна'}
>>> group_a & group_b
{'Вера'}
>>> group_a - group_b
{'Борис', 'Анна'}
>>> "Глеб" in group_a
False
>>> set("абракадабра")
{'д', 'р', 'к', 'а', 'б'}
>>> empty = set()
>>> type({})
<class 'dict'>
set("абракадабра") может вывестись в другом порядке. Если порядок важен — сортируйте: sorted(множество). Пустое множество — только set(): {} создаёт пустой словарь.| — объединение, & — пересечение (кто в обеих группах), - — разность (кто в первой, но не во второй). Удаление повторов с сохранением порядка — множество «уже видели» плюс список результата:
emails = ["anna@mail.ru", "boris@ya.ru", "Anna@Mail.ru", "vera@bk.ru", "boris@ya.ru"]
seen = set()
unique = []
for e in emails:
key = e.lower()
if key not in seen:
seen.add(key)
unique.append(e)
print(unique)
$ python3 dedupe.py
['anna@mail.ru', 'boris@ya.ru', 'vera@bk.ru']
Генератор списка — короткая запись цикла, который строит новый список: [выражение for x in последовательность if условие]. Условие необязательно. Так же записываются словари и множества — в фигурных скобках.
>>> nums = [3, -1, 8, 0, -5, 12]
>>> [n * 2 for n in nums]
[6, -2, 16, 0, -10, 24]
>>> [n for n in nums if n > 0]
[3, 8, 12]
>>> [w.upper() for w in "раз два три".split()]
['РАЗ', 'ДВА', 'ТРИ']
>>> {n: n ** 2 for n in range(1, 6)}
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
>>> sum(n for n in nums if n > 0)
23
Последняя строка — без квадратных скобок: выражение-генератор отдаёт числа функции sum по одному, не создавая промежуточный список. Генератор хорош, пока читается одной строкой; если внутри появляются вложенные условия и циклы — пишите обычный for.
| Задача | Структура |
|---|---|
| Последовательность, порядок важен, бывают повторы | list |
| Фиксированная группа значений, запись | tuple |
| Найти значение по ключу: имя → телефон | dict |
| Уникальные значения, быстрая проверка «есть ли» | set |
sorted(x) возвращает новый список, x.sort() меняет список и возвращает Noneb = a — второе имя того же списка; копия — a.copy()a, b = пара, перебор пар — enumerate, zip, items()d[k] падает на отсутствующем ключе, d.get(k, умолчание) — нет; подсчёт — d.get(k, 0) + 1{} — пустой словарь, не множество[f(x) for x in s if условие] — пока помещается в строку