Модуль 4 · Словари, множества и кортежи

Справочник по Python · модуль 4 · словари, множества и кортежи

Глава 4.3

4.3

Методы словарей:
часть 1

Сначала — универсальные функции, которые принимают словарь как коллекцию. Главное при их использовании: решить, что именно обрабатывать — ключи, значения или пары. По умолчанию всегда берутся ключи, и это источник большинства ошибок.

Категория
Словари
Уровень
Базовый → средний
Связанные темы
Функции коллекций, сортировка, кортежи
Зачем это знать
Считать, сортировать и преобразовывать содержимое словаря

§1Функция или метод

Со словарём работают два вида инструментов, и различать их полезно по форме записи — как в главе 3.12.

ФормаПримерСмысл
универсальная функцияlen(data)работает со многими коллекциями
метод словаряdata.get(key)описывает операцию именно словаря

Функций из первой строки вы уже знаете почти все — они разбирались в главе 3.14 для списков. Со словарём они работают так же, но с одной оговоркой, которой посвящён следующий параграф.

§2По умолчанию — ключи

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

Схематри разных набора данных из одного словаря
prices = {"book": 900, "pen": 120}

prices            → "book", "pen"                ключи
prices.values()   → 900, 120                     значения
prices.items()    → ("book", 900), ("pen", 120)  пары

Это правило действует везде: в for, в sorted(), в max(), в sum(), в list(). Прежде чем вызвать любую функцию, стоит задать себе один вопрос: с чем она должна работать — и дописать .values() или .items(), если ответ не «с ключами».

§3len(): число пар

main.pyтри ключа — три пары
prices = {"book": 900, "pen": 120, "bag": 2400}
print(len(prices))
Выводчисло пар, а не отдельных значений
3

Здесь двусмысленности нет: ключей и значений поровну, поэтому len(prices), len(prices.values()) и len(prices.items()) дадут одно и то же.

§4min() и max()

А вот здесь выбор области решает всё.

main.pyдва разных вопроса
scores = {"Max": 82, "Anna": 95, "Oleg": 74}

print(max(scores))
print(max(scores.values()))
Выводпоследнее имя по алфавиту и наибольший балл
Oleg
95

Первая строка сравнивает строки-ключи по правилам главы 5.1 — то есть по кодовым точкам, и «Oleg» побеждает просто потому, что O идёт после A и M. К баллам это отношения не имеет. Вторая строка даёт максимальный балл, но уже не говорит, чей он.

§5Ключ с наибольшим значением

Обычно нужен именно третий вариант: не имя и не балл по отдельности, а имя того, у кого балл наибольший.

main.pyкандидаты — ключи, сравнение — по значениям
scores = {"Max": 82, "Anna": 95, "Oleg": 74}

print(max(scores, key=scores.get))
print(min(scores, key=scores.get))
Выводлучший и худший результат
Anna
Oleg

Разберём запись. Перебираются по-прежнему ключи, но параметр key сообщает: «сравнивай их не напрямую, а по результату scores.get от каждого». Метод get передаётся без скобок — как значение, а не как вызов. Подробно этот приём разбирается в главе 6.10.

ЗаписьЧто вернёт
max(scores)наибольший ключ
max(scores.values())наибольшее значение
max(scores, key=scores.get)ключ, у которого наибольшее значение

§6sum()

Складывать имеет смысл значения, поэтому values() здесь обязателен.

main.pyстоимость корзины
cart = {"book": 900, "pen": 120, "bag": 2400}
print(sum(cart.values()))
Выводобщая сумма
3420

Забытый values() здесь как раз заметен сразу — Python попытается складывать строки с числом.

main.pyсложение ключей
print(sum(cart))
Ошибкаsum() начинает с нуля и прибавляет ключи
TypeError: unsupported operand type(s) for +: 'int' and 'str'

Повезло не всегда: если ключи числовые, ошибки не будет — просто сложатся не те числа.

§7sorted(): три варианта

Функция возвращает список и не меняет словарь. Что окажется в списке — снова зависит от области.

main.pyключи, значения и пары
prices = {"book": 900, "pen": 120, "bag": 2400}

print(sorted(prices))
print(sorted(prices.values()))
print(sorted(prices.items()))
Выводтри разных списка
['bag', 'book', 'pen']
[120, 900, 2400]
[('bag', 2400), ('book', 900), ('pen', 120)]

Третья строка сортирует пары, а кортежи сравниваются по первому элементу — то есть по ключу. Чтобы упорядочить по значению, нужен key.

§8Сортировка по значению

main.pyдва способа получить один порядок
prices = {"book": 900, "pen": 120, "bag": 2400}

print(sorted(prices, key=prices.get))
print(sorted(prices.items(), key=lambda item: item[1]))
Выводключи по возрастанию цены и полные пары
['pen', 'book', 'bag']
[('pen', 120), ('book', 900), ('bag', 2400)]

Первый способ короче, но даёт только ключи. Второй сохраняет и значение: item — это пара (ключ, значение), а item[1] — её значение. Запись lambda разбирается в главе 6.9; пока достаточно читать её как «взять из пары второй элемент».

§9all() и any()

Эти функции сворачивают набор проверок в одно значение и почти всегда применяются к values().

main.pyсостояния заданий
completed = {"theory": True, "practice": False, "test": True}

print(all(completed.values()))
print(any(completed.values()))
Выводне всё сделано, но хоть что-то — да
False
True

Истинность считается по правилам главы 3.16, поэтому годятся не только True и False: пустая строка, ноль и пустой список тоже ложны. У пустой коллекции результаты запоминают отдельно: all([]) — True, any([]) — False.

§10enumerate() и reversed()

Обе функции опираются на порядок добавления пар, который словарь сохраняет.

main.pyнумерация ключей с конца
stages = {"draft": 1, "review": 2, "done": 3}

for number, key in enumerate(reversed(stages), start=1):
    print(number, key)
Выводобратный порядок добавления
1 done
2 review
3 draft

Здесь снова перебираются ключи: reversed(stages) разворачивает именно их. Для пар в обратном порядке нужен reversed(stages.items()).

§11zip(): из двух списков

Самый частый способ построить словарь из параллельных данных.

main.pyимена и баллы лежали отдельно
names = ["Max", "Anna", "Oleg"]
scores = [82, 95, 74]

print(dict(zip(names, scores)))
Выводтри пары
{'Max': 82, 'Anna': 95, 'Oleg': 74}

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

§12map() и filter()

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

main.pyскидка на значения и отбор пар
prices = {"book": 900, "pen": 120, "bag": 2400}

discounted = list(map(lambda price: price * 0.9, prices.values()))
expensive = dict(filter(lambda item: item[1] >= 500, prices.items()))

print(discounted)
print(expensive)
Выводсписок значений и новый словарь
[810.0, 108.0, 2160.0]
{'book': 900, 'bag': 2400}

Обратите внимание на разницу: map() по значениям теряет ключи, поэтому результат — список. filter() по парам их сохраняет, и результат можно снова свернуть в словарь. Ту же работу короче делает словарное включение из главы 4.10.

§13dict.fromkeys()

Метод создаёт словарь из набора ключей с одинаковым начальным значением.

main.pyзаготовка формы
fields = ("name", "email", "phone")
form = dict.fromkeys(fields, None)

print(form)
Выводтри ключа, значения пусты
{'name': None, 'email': None, 'phone': None}

Второй аргумент необязателен: без него все значения станут None. Обратите внимание на форму записи — dict.fromkeys() вызывается у самого типа, а не у существующего словаря.

§14Ловушка общего значения

Метод ставит во все пары один и тот же объект. Для None и чисел это незаметно — они неизменяемы. Для списка последствия наглядны.

main.pyдобавляем в один ключ
groups = dict.fromkeys(["a", "b"], [])
groups["a"].append(1)

print(groups)
Выводизменились оба
{'a': [1], 'b': [1]}

Это та же история, что с двумя именами одного списка из главы 3.10: список создан один раз, а ссылок на него теперь две. Решение — словарное включение, которое создаёт новый список на каждом шаге.

main.pyнезависимые списки
groups = {key: [] for key in ["a", "b"]}
groups["a"].append(1)

print(groups)
Выводзатронут только один ключ
{'a': [1], 'b': []}

§15get() не создаёт ключ

Метод get() разбирался в главе 4.2, но одну его особенность стоит подчеркнуть отдельно: он только читает.

main.pyпосле чтения словарь прежний
user = {"name": "Max"}
user.get("age", 0)

print(user)
Выводключ age не появился
{'name': 'Max'}

Запасное значение существует только внутри выражения. Поэтому счётчик из главы 4.2 и записывают в две операции: counts[word] = counts.get(word, 0) + 1 — сначала прочитать, потом присвоить. Метод, который читает и сразу создаёт ключ, называется setdefault() и разбирается в главе 4.4.

§16items() — представление

items() возвращает не список, а представление — динамическое окно в словарь.

main.pyобращение по индексу
prices = {"book": 900, "pen": 120}
print(prices.items()[0])
Ошибкаэто не список
TypeError: 'dict_items' object is not subscriptable

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

main.pyобычный перебор пар
for name, price in prices.items():
    print(f"{name}: {price} ₽")
Выводдве пары
book: 900 ₽
pen: 120 ₽

Если нужен именно список — с индексами и срезами, — представление оборачивают: list(prices.items()). Чем «динамическое» отличается от обычного и почему это важно, разбирается в главе 4.4.

§17Частые ошибки

Что написаноЧто произойдётКак правильно
sum(prices) при строковых ключахTypeError: складываются ключиsum(prices.values())
sum(prices) при числовых ключахошибки нет, сложатся не те числавсегда указывать область явно
max(scores) для лучшего балласравнятся имена по алфавитуmax(scores, key=scores.get)
max(scores, key=scores.get())TypeError: метод вызван, а не переданkey=scores.get без скобок
sorted(prices.items()) для порядка по ценеотсортируется по ключамдобавить key=lambda item: item[1]
prices.items()[0]TypeError: представление не индексируетсяlist(prices.items())[0]
dict.fromkeys(keys, [])все ключи разделят один список{key: [] for key in keys}
ждать, что get() создаст ключсловарь не изменитсяприсвоить значение или setdefault()

§18Проверьте себя

Что перебирает sorted(data), если data — словарь?

Ключи. Для значений нужен sorted(data.values()), для пар — sorted(data.items()).

Чем max(scores) отличается от max(scores, key=scores.get)?

Первое сравнивает сами ключи и вернёт последний по алфавиту. Второе тоже перебирает ключи, но сравнивает их по значениям, и вернёт ключ с наибольшим значением.

Почему key=scores.get пишется без скобок?

Передаётся сама функция, а вызывать её для каждого элемента будет max(). Со скобками метод выполнился бы сразу и без аргумента.

Меняет ли get() словарь?

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

Почему dict.fromkeys(keys, []) опасен?

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

Почему prices.items()[0] не работает?

items() возвращает представление, а не список: у него нет индексов. Нужен list(prices.items())[0].

§19Шпаргалка

Размерlen(data)
Сумма значенийsum(data.values())
Максимум значенияmax(data.values())
Ключ максимумаmax(data, key=data.get)
По значениюsorted(data, key=data.get)
Из двух списковdict(zip(keys, values))
Заготовкаdict.fromkeys(keys, 0)
Список парlist(data.items())

Итог главы

Универсальные функции работают со словарём так же, как со списком, но по умолчанию перебирают ключи — и решение «ключи, значения или пары» нужно принимать перед каждым вызовом. Параметр key позволяет перебирать одно, а сравнивать по другому: так находят ключ с наибольшим значением и сортируют словарь по значениям. dict.fromkeys() кладёт во все пары один и тот же объект, поэтому для изменяемых значений нужно включение, а get() только читает и ключей не создаёт.

Глава 4.3 · Методы словарей I · Справочник по Python Автор: Макаров Максим Николаевич