#!/usr/bin/env python # coding: utf-8 # # Программирование и анализ данных # *Алла Тамбовцева, НИУ ВШЭ* # # *Данный ноутбук частично основан на [лекциях](http://math-info.hse.ru/s17/1) Щурова И.В., курс «Программирование на языке Python для сбора и анализа данных» (НИУ ВШЭ).* # ## Кортежи и словари # # ## Кортежи (tuples) # # Кортежи встречаются не только в программировании, но и в математике. В математике под кортежем обычно понимают упорядоченную совокупность элементов, то есть совокупность, порядок элементов которой фиксирован. В кортеже мы точно знаем, какой элемент является первым, какой – вторым, и так далее. # # Внешне кортежи несильно отличаются от списков. Единственное внешнее отличие – элементы кортежа заключаются в круглые, а не в квадратные скобки. # In[1]: my_tuple = (1, 2, 4, 6, 9) # К элементам кортежа можно обращаться точно так же, как к элементам списка: # In[2]: my_tuple[0] # Но, несмотря на кажущееся сходство, кортежи и списки – принципиально разные объекты. Главное отличие кортежей от списков заключается в том, что кортежи – неизменяемые объекты. Другими словами, изменять элементы кортежа нельзя. Проверим: # In[3]: my_tuple[2] = 65 # Иногда это свойство бывает полезным (некоторая «защита» от изменений), иногда – не очень, но для нас пока важно познакомиться с разными объектами в Python, чтобы потом не удивляться. Ведь многие более продвинутые функции могут возвращать результат или, наоборот, принимать на вход только кортежи или только списки. # При желании кортеж можно превратить в список: # In[4]: list(my_tuple) # И наоборот: # In[5]: tuple([1,2,3]) # Если посмотреть на методы, применяемые к кортежам (например, набрать `my_tuple.` и нажать *Tab*), то можно заметить, что методов для кортежей сильно меньше по сравнению с методами для списков. Во многом это связано с тем, что кортеж нельзя изменить. Но вот «склеивать» кортежи, создавая при этом новый, легко: # In[6]: (1, 3) + (7, 8) # ## Словари (dictionaries) # # Обсуждая словари в Python, удобно проводить аналогию с обычными словарями (бумажными или электронными). Что такое словарь? Перечень из пар: *слово-значение* или *слово-список значений*, если значений несколько. Вот и словарь в Python – это объект, структура данных, которая позволяет хранить пары соответствий. # # Давайте представим, что нам нужно создать словарь, который мы будем использовать для программки к мюзиклу "Notre Dame de Paris". Будем записывать в словарь `prog` пары соответствий *герой-актер*. # In[7]: prog = {'Gringoire' : 'Pelletier', 'Frollo' : 'Lavoie', 'Phoebus': 'Fiori'} # Первый элемент в каждой паре (до двоеточия) назвается ключом (*key*), второй элемент в каждой паре (после двоеточия) – значением (*value*). Посмотрим на словарь: # In[8]: prog # ### Обращение к элементам словаря # Как и в случае со списками или кортежами, к элементам словаря можно обращаться. Только выбор элемента производится не по индексу, а по ключу: сначала указываем название словаря, а потом в квадратных скобках – ключ, по которому мы хотим вернуть значение. Например, выясним, кто играет роль Феба: # In[9]: prog['Phoebus'] # А что будет, если мы запросим элемент по ключу, которого нет в словаре? # In[10]: prog['Esmeralda'] # В глубине души Python начинает грустно петь "Where is she, your Esmeralda?", но вместо эмоций выдает сухое *KeyError*. Ошибка ключа – ну нет в словаре элемента с ключом Esmeralda! # Теперь представьте себе такую ситуацию: у нас есть список героев (ключей) и мы хотим в цикле вернуть по ним в фамилии актеров (значения). Какого-то одного из героев нет. Что произойдет? На каком-то этапе Python выдаст ошибку, мы вывалимся из цикла, и на этом наша работа остановится. Обидно, да? Чтобы такого избежать, получать значение по ключу можно другим способом – используя метод `.get()`: # In[11]: prog.get('Esmeralda') # ни результата, ни ошибки # Если выведем результат на экран явно, с помощью `print()`, увидим, что в случае, если пары с указанным ключом в словаре нет, Python выдаст значение `None`: # In[12]: print(prog.get('Esmeralda')) # Удобство метода `.get()` заключается в том, что мы сами можем установить, какое значение будет возвращено, в случае, если пары с выбранным ключом нет в словаре. Так, вместо `None` мы можем вернуть строку `Not found`, и ломаться ничего не будет: # In[13]: prog.get('Esmeralda', 'Not found') # Возвращаемое значение в случае, если запись с указанным ключом отсутствует в словаре, необязательно должно быть строкой, можно было бы поставить какое-нибудь число или значение `False`: # In[14]: print(prog.get('Esmeralda', 99)) print(prog.get('Esmeralda', False)) # Но недостающий элемент мы всегда можем добавить! # In[15]: prog['Esmeralda'] = 'Segara' prog # **Внимание:** Если элемент с указанным ключом уже существует, новый с таким же ключом не добавится! Ключ – это уникальный идентификатор элемента. Если мы добавим в словарь новый элемент с уже существующим ключом, мы просто изменим старый – словари являются изменяемыми объектами. Например, так (изменения в программке `prog`): # In[16]: prog['Esmeralda'] = 'Noa' prog # Раз элементами словаря являются пары *ключ-значение*, наверняка есть способ выбрать из словаря ключи и значения отдельно. Действительно, для этого есть методы `.keys()` и `values()`. Вызовем сначала все ключи: # In[17]: prog.keys() # Объект, который мы только что увидели, очень похож на список. Но обычным списком на самом деле не является. Давайте попробуем выбрать первый элемент `prog.keys()`: # In[18]: keys = prog.keys() keys[0] # Не получается! Потому что полученный объект имеет специальный тип `dict_keys`, а не `list`. Но это всегда можно поправить, превратив объект `dict_keys` в список: # In[19]: list(keys)[0] # получается! # Аналогичным образом можно работать и со значениями: # In[20]: prog.values() # Словари могут состоять не только из строк, почти любые объекты могут быть ключами и значениями списка (забегая вперед, значениями – любые, ключами – только неизменяемые). Например, можно создать словарь оценок, состоящий из пар целых чисел, *числовой id студента-его оценка*. # In[21]: numbers = {1 : 7, 2 : 8, 3 : 9} # Обращаться к элементам мы будем, естественно, без кавычек, так как ключами являются числа: # In[22]: numbers[1] # оценка студента с id равным 1 # Словари могут состоять из элементов смешанного типа. Например, вместо числового id можно явно записать имя студента: # In[23]: marks = {"Петя": 6, "Вася": 9} # In[24]: marks["Петя"] # Ну, и раз уж питоновские словари так похожи на обычные, давайте представим, что у нас есть словарь, где все слова многозначные. Ключом будет слово, а значением ‒ целый список. # In[25]: my_dict = {'swear' : ['клясться', 'ругаться'], 'dream' : ['спать', 'мечтать']} # По ключу мы получим значение в виде списка: # In[26]: my_dict['swear'] # Так как значением является список, можем отдельно обращаться к его элементам: # In[27]: my_dict['swear'][0] # первый элемент # Можем пойти дальше и создать словарь, где значениями являются словари! Например, представим, что в некотором сообществе проходят выборы, и каждый участник может проголосовать за любое число кандидатов. Данные сохраняются в виде словаря, где ключами являются имена пользователей, а значениями – пары *кандидат-голос*. # In[28]: # '+' - за, '-' - против, 0 - воздержался votes = {'user1': {'cand1': '+', 'cand2': '-'}, 'user2' : {'cand1': 0, 'cand3' : '+'}} # In[29]: votes # По аналогии с вложенными списками по ключам мы сможем обратиться к значению в словаре, который сам является значением в `votes` (да, эту фразу нужно осмыслить): # In[30]: # берем значение, соответствующее ключу user1, в нем – ключу cand1 votes['user1']['cand1'] # А теперь давайте подумаем, как можно вывести на экран элементы словаря по очереди, в цикле. Первая попытка: # In[31]: for k in prog: print(k) # Попытка не совсем удалась: на экран были выведены только ключи. А как вывести пары? # # **Задача:** для каждого героя мюзикла из словаря `prog` вывести на экран сообщение вида # # Fiori plays the role of Phoebus # **Решение:** вспомним, что мы можем получать значения по ключу, указывая его в квадратных скобках, и проделаем это для всех ключей в словаре: # In[32]: for k in prog: print(prog[k], "plays the role of", k) # Упражнение полезное, но, на самом деле, существует специальный метод `.items()`, который позволяет обращаться сразу к парам элементов: # In[33]: for k, v in prog.items(): print(k, v) # Для того, чтобы вывести и ключ, и значение, нужно в цикле `for` перечислить две переменные через запятую. И совсем не обязательно называть их `k` и `v` или `key` и `value`; Python сам поймет, что первая переменная соответствует ключу, а вторая ‒ значению. Для примера решим поставленную выше задачу с помощью `items()`. # In[34]: for hero, actor in prog.items(): print(actor, "plays the role of", hero) # Если мы посмотрим на `prog.items()`, мы увидим, что этот объект очень похож на список, состоящий из кортежей: # In[35]: prog.items() # Как и случае с методами `.keys()` и `.values()`, полученный объект не является «обычным» списком. Поэтому при необходимости его нужно будет превратить в список: # In[36]: list(prog.items()) # Метод `.items()` полезен, когда мы хотим выбирать из словаря значения, удовлетворяющие определенным условиям. Для разнообразия возьмем другой словарь – словарь с парами *студент-оценка*: # In[37]: grades = {"Вася": 7, "Петя" : 9, "Коля" : 8, "Лена" : 8, "Василиса" : 10} # И выведем на экран имена тех студентов, у которых оценка равна 8: # In[38]: for name, grade in grades.items(): if grade == 8: print(name) # Только два человека: Коля и Лена. А как проверить, есть ли в словаре элемент с определенным ключом? Воспользоваться оператором `in`: # In[39]: "Коля" in grades.keys() # In[40]: "Ваня" in grades.keys()