{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Программирование на Python\n", "\n", "*Алла Тамбовцева, НИУ ВШЭ*" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Словари (dictionaries)\n", "\n", "Обсуждая словари в Python, удобно проводить аналогию с обычными словарями (бумажными или электронными). Что такое словарь? Перечень из пар: *слово-значение* или *слово-список значений*, если значений несколько. Вот и словарь в Python – это объект, структура данных, которая позволяет хранить пары соответствий.\n", "\n", "Давайте представим, что нам нужно создать словарь, который мы будем использовать для программки к мюзиклу \"Notre Dame de Paris\". Будем записывать в словарь `prog` пары соответствий *герой-актер*." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "prog = {'Gringoire' : 'Pelletier', \n", " 'Frollo' : 'Lavoie', 'Phoebus': 'Fiori'}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Первый элемент в каждой паре (до двоеточия) назвается ключом (*key*), второй элемент в каждой паре (после двоеточия) – значением (*value*). Посмотрим на словарь:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'Gringoire': 'Pelletier', 'Frollo': 'Lavoie', 'Phoebus': 'Fiori'}\n" ] } ], "source": [ "print(prog)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Обращение к элементам словаря" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как и в случае со списками или кортежами, к элементам словаря можно обращаться. Только выбор элемента производится не по индексу, а по ключу: сначала указываем название словаря, а потом в квадратных скобках – ключ, по которому мы хотим вернуть значение. Например, выясним, кто играет роль Феба:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Fiori\n" ] } ], "source": [ "print(prog['Phoebus'])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "А что будет, если мы запросим элемент по ключу, которого нет в словаре?" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "ename": "KeyError", "evalue": "'Esmeralda'", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mprog\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;34m'Esmeralda'\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mKeyError\u001b[0m: 'Esmeralda'" ] } ], "source": [ "prog['Esmeralda']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В глубине души Python начинает грустно петь \"Where is she, your Esmeralda?\", но вместо эмоций выдает сухое *KeyError*. Ошибка ключа – ну нет в словаре элемента с ключом Esmeralda! " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь представьте себе такую ситуацию: у нас есть список героев (ключей) и мы хотим в цикле вернуть по ним в фамилии актеров (значения). Какого-то одного из героев нет. Что произойдет? На каком-то этапе Python выдаст ошибку, мы вывалимся из цикла, и на этом наша работа остановится. Обидно, да? Чтобы такого избежать, получать значение по ключу можно другим способом – используя метод `.get()`:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "prog.get('Esmeralda') # ни результата, ни ошибки" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если выведем результат на экран явно, с помощью `print()`, увидим, что в случае, если пары с указанным ключом в словаре нет, Python выдаст значение `None`:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "None\n" ] } ], "source": [ "print(prog.get('Esmeralda'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Удобство метода `.get()` заключается в том, что мы сами можем установить, какое значение будет возвращено, в случае, если пары с выбранным ключом нет в словаре. Так, вместо `None` мы можем вернуть строку `Not found`, и ломаться ничего не будет:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Not found'" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "prog.get('Esmeralda', 'Not found')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Возвращаемое значение в случае, если запись с указанным ключом отсутствует в словаре, необязательно должно быть строкой, можно было бы поставить какое-нибудь число или значение `False`:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "99\n", "False\n" ] } ], "source": [ "print(prog.get('Esmeralda', 99))\n", "print(prog.get('Esmeralda', False))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Методы на словарях" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Но недостающий элемент мы всегда можем добавить! " ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'Gringoire': 'Pelletier', 'Frollo': 'Lavoie', 'Phoebus': 'Fiori', 'Esmeralda': 'Segara'}\n" ] } ], "source": [ "prog['Esmeralda'] = 'Segara'\n", "print(prog)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для добавления более одной записи, более одной пары ключ-значение, на словарях определен метод `.update()`:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'Gringoire': 'Pelletier', 'Frollo': 'Lavoie', 'Phoebus': 'Fiori', 'Esmeralda': 'Noa', 'Quasimodo': 'Garou'}\n" ] } ], "source": [ "prog.update({\"Quasimodo\": \"Garou\", \n", " \"Esmeralda\" : \"Noa\"})\n", "print(prog)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Внимание:** Если элемент с указанным ключом уже существует, новый с таким же ключом не добавится! Ключ – это уникальный идентификатор элемента. Если мы добавим в словарь новый элемент с уже существующим ключом, мы просто изменим старый – словари являются изменяемыми объектами. \n", "\n", "Теперь удалим записи по ключу (иного пути нет, индексы не пойдут). Сделать это можно двумя способами. Первый способ – использовать оператор `del`:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'Gringoire': 'Pelletier', 'Frollo': 'Lavoie', 'Phoebus': 'Fiori', 'Esmeralda': 'Noa'}\n" ] } ], "source": [ "del prog[\"Quasimodo\"]\n", "print(prog)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Второй способ – более «словарный», это метод `.pop()`:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'Gringoire': 'Pelletier', 'Frollo': 'Lavoie', 'Phoebus': 'Fiori'}\n" ] } ], "source": [ "prog.pop(\"Esmeralda\")\n", "print(prog)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Обратите внимание: метод `.pop()` на словарях, как и метод `.pop()` на списках, не просто удаляет запись, но и возвращает значение, которое соответствует удаляемому ключу, так что его можно сохранить перед удалением." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Раз элементами словаря являются пары *ключ-значение*, наверняка есть способ выбрать из словаря ключи и значения отдельно. Действительно, для этого есть методы `.keys()` и `values()`. Вызовем сначала все ключи:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "dict_keys(['Gringoire', 'Frollo', 'Phoebus'])\n" ] } ], "source": [ "print(prog.keys())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Объект, который мы только что увидели, очень похож на список. Но обычным списком на самом деле не является. Давайте попробуем выбрать первый элемент `prog.keys()`:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [ { "ename": "TypeError", "evalue": "'dict_keys' object is not subscriptable", "output_type": "error", "traceback": [ "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mkeys\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mprog\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mkeys\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mkeys\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", "\u001b[0;31mTypeError\u001b[0m: 'dict_keys' object is not subscriptable" ] } ], "source": [ "keys = prog.keys()\n", "keys[0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Не получается! Потому что полученный объект имеет специальный тип `dict_keys`, а не `list`. Но это всегда можно поправить, превратив объект `dict_keys` в список:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'Gringoire'" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(keys)[0] # получается!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Аналогичным образом можно работать и со значениями:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "dict_values(['Pelletier', 'Lavoie', 'Fiori'])\n" ] } ], "source": [ "print(prog.values())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Словари могут состоять не только из строк, почти любые объекты могут быть ключами и значениями списка (забегая вперед, значениями – любые, ключами – только неизменяемые). Например, можно создать словарь оценок, состоящий из пар целых чисел, *числовой id студента-его оценка*." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": [ "numbers = {1 : 7, 2 : 8, 3 : 9}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Обращаться к элементам мы будем, естественно, без кавычек, так как ключами являются числа:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "7" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "numbers[1] # оценка студента с id равным 1" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Словари могут состоять из элементов смешанного типа. Например, вместо числового id можно явно записать имя студента:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [], "source": [ "marks = {\"Петя\": 6, \"Вася\": 9}" ] }, { "cell_type": "code", "execution_count": 21, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "6\n" ] } ], "source": [ "print(marks[\"Петя\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ну, и раз уж питоновские словари так похожи на обычные, давайте представим, что у нас есть словарь, где все слова многозначные. Ключом будет слово, а значением ‒ целый список. " ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [], "source": [ "my_dict = {'swear' : ['клясться', 'ругаться'], \n", " 'dream' : ['спать', 'мечтать']}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "По ключу мы получим значение в виде списка:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "['клясться', 'ругаться']" ] }, "execution_count": 23, "metadata": {}, "output_type": "execute_result" } ], "source": [ "my_dict['swear']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Так как значением является список, можем отдельно обращаться к его элементам:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'клясться'" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "my_dict['swear'][0] # первый элемент" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Можем пойти дальше и создать словарь, где значениями являются словари! Например, представим, что в некотором сообществе проходят выборы, и каждый участник может проголосовать за любое число кандидатов. Данные сохраняются в виде словаря, где ключами являются имена пользователей, а значениями – пары *кандидат-голос*." ] }, { "cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": [ "# '+' - за, '-' - против, 0 - воздержался\n", "votes = {'user1': {'cand1': '+', 'cand2': '-'},\n", " 'user2' : {'cand1': 0, 'cand3' : '+'}} " ] }, { "cell_type": "code", "execution_count": 26, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'user1': {'cand1': '+', 'cand2': '-'}, 'user2': {'cand1': 0, 'cand3': '+'}}" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "votes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "По аналогии с вложенными списками по ключам мы сможем обратиться к значению в словаре, который сам является значением в `votes` (да, эту фразу нужно осмыслить):" ] }, { "cell_type": "code", "execution_count": 27, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'+'" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# берем значение, соответствующее ключу user1, в нем – ключу cand1\n", "votes['user1']['cand1'] " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "А теперь давайте подумаем, как можно вывести на экран элементы словаря по очереди, в цикле. Первая попытка:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Gringoire\n", "Frollo\n", "Phoebus\n" ] } ], "source": [ "for k in prog:\n", " print(k)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Попытка не совсем удалась: на экран были выведены только ключи. А как вывести пары? \n", "\n", "**Задача:** для каждого героя мюзикла из словаря `prog` вывести на экран сообщение вида\n", "\n", " Fiori plays the role of Phoebus" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Решение:** вспомним, что мы можем получать значения по ключу, указывая его в квадратных скобках, и проделаем это для всех ключей в словаре:" ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Pelletier plays the role of Gringoire\n", "Lavoie plays the role of Frollo\n", "Fiori plays the role of Phoebus\n" ] } ], "source": [ "for k in prog:\n", " print(prog[k], \"plays the role of\", k)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Упражнение полезное, но, на самом деле, существует специальный метод `.items()`, который позволяет обращаться сразу к парам элементов:" ] }, { "cell_type": "code", "execution_count": 30, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Gringoire Pelletier\n", "Frollo Lavoie\n", "Phoebus Fiori\n" ] } ], "source": [ "for k, v in prog.items():\n", " print(k, v)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для того, чтобы вывести и ключ, и значение, нужно в цикле `for` перечислить две переменные через запятую. И совсем не обязательно называть их `k` и `v` или `key` и `value`; Python сам поймет, что первая переменная соответствует ключу, а вторая ‒ значению. Для примера решим поставленную выше задачу с помощью `items()`." ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Pelletier plays the role of Gringoire\n", "Lavoie plays the role of Frollo\n", "Fiori plays the role of Phoebus\n" ] } ], "source": [ "for hero, actor in prog.items():\n", " print(actor, \"plays the role of\", hero)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если мы посмотрим на `prog.items()`, мы увидим, что этот объект очень похож на список, состоящий из кортежей:" ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dict_items([('Gringoire', 'Pelletier'), ('Frollo', 'Lavoie'), ('Phoebus', 'Fiori')])" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "prog.items()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как и случае с методами `.keys()` и `.values()`, полученный объект не является «обычным» списком. Поэтому при необходимости его нужно будет превратить в список:" ] }, { "cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "[('Gringoire', 'Pelletier'), ('Frollo', 'Lavoie'), ('Phoebus', 'Fiori')]" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "list(prog.items())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Метод `.items()` полезен, когда мы хотим выбирать из словаря значения, удовлетворяющие определенным условиям. Для разнообразия возьмем другой словарь – словарь с парами *студент-оценка*:" ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [], "source": [ "grades = {\"Вася\": 7, \"Петя\" : 9, \"Коля\" : 8, \"Лена\" : 8, \n", " \"Василиса\" : 10}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "И выведем на экран имена тех студентов, у которых оценка равна 8:" ] }, { "cell_type": "code", "execution_count": 35, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Коля\n", "Лена\n" ] } ], "source": [ "for name, grade in grades.items():\n", " if grade == 8:\n", " print(name)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Только два человека: Коля и Лена. А как проверить, есть ли в словаре элемент с определенным ключом? Воспользоваться оператором `in`:" ] }, { "cell_type": "code", "execution_count": 36, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "True" ] }, "execution_count": 36, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"Коля\" in grades.keys()" ] }, { "cell_type": "code", "execution_count": 37, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "False" ] }, "execution_count": 37, "metadata": {}, "output_type": "execute_result" } ], "source": [ "\"Ваня\" in grades.keys()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 2 }