#!/usr/bin/env python # coding: utf-8 # # Основы программирования в Python # *Алла Тамбовцева, НИУ ВШЭ* # # *Данный ноутбук основан на [лекциях](http://math-info.hse.ru/s17/1) Щурова И.В., курс «Программирование на языке Python для сбора и анализа данных» (НИУ ВШЭ).* # ## Кортежи. Словари. # ## Кортежи (tuples) # # Кортежи встречаются не только в программировании, но и в математике. В математике под кортежем обычно понимают упорядоченное множество. Про множества мы немного говорили на прошлой лекции, множество ‒ это просто набор каких-то элементов, причем элементы множества не должны повторятся. Порядок элементов в множестве не важен: {1, 2, 3} и {3, 2, 1} ‒ это одно и то же множество. В кортеже мы точно знаем, какой элемент является первым, какой ‒ вторым, и так далее. # # Внешне кортежи несильно отличаются от списков. Единственное внешнее отличие ‒ элементы кортежа заключаются в круглые, а не в квадратные скобочки. # 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]) # ## Словари (dictionaries) # # Обсуждая словари в Python, удобно проводить аналогию с обычными словарями (бумажными или электронными). Что такое словарь? Перечень из пар: *слово-значение* или *слово-список значений*, если значений несколько. Вот и словарь в Python ‒ это объект, структура данных, которая позволяет хранить пары соответствий. # # Давайте представим, что нам нужно создать словарь, который мы будем использовать для программки к мюзиклу "Notre Dame de Paris" (да, меня не отпускает). Будем записывать в словарь `prog` пары соответствий *герой-актер*. # In[6]: prog = {'Gringoire' : 'Pelletier', 'Frollo' : 'Lavoie', 'Phoebus': 'Fiori'} # Первый элемент в каждой паре (до двоеточия) назвается ключом (*key*), второй элемент в каждой паре (после двоеточия) ‒ значением (*value*). Посмотрим на словарь: # In[7]: prog # ### Обращение к элементам словаря # Как и в случае со списками или кортежами, к элементам словаря можно обращаться. Только выбор элемента производится не по индексу, а по ключу: сначала указываем название словаря, а потом в квадратных скобках ‒ ключ, по которому мы хотим вернуть значение. Например, выясним, кто играет роль Феба: # In[8]: prog['Phoebus'] # А что будет, если мы запросим элемент по ключу, которого нет в словаре? # In[9]: prog['Esmeralda'] # В глубине души Python начинает грустно петь "Where is she, your Esmeralda?", но вместо эмоций выдает сухое *KeyError*. Ошибка ключа -- ну нет в словаре элемента с ключом Esmeralda! # Теперь представьте себе такую ситуацию: у нас есть список героев (ключей) и мы хотим в цикле вернуть по ним в фамилии актеров (значения). Какого-то одного из героев нет. Что произойдет? На каком-то этапе Python выдаст ошибку, мы вывалимся из цикла и на этом наша работа остановится. Обидно, да? Чтобы такого избежать, получать значение по ключу можно другим способом ‒ используя метод `.get()`: # In[10]: prog.get('Esmeralda') # ни результата, ни ошибки # Если выведем результат на экран явно, с помощью `print()`, увидим, что в случае, если пары с указанным ключом в словаре нет, Python выдаст значение `None`: # In[11]: print(prog.get('Esmeralda')) # Удобство метода `.get()` заключается в том, что мы сами можем установить, какое значение будет возвращено, в случае, если пары с выбранным ключом нет в словаре. Так, вместо `None` мы можем вернуть строку `Not found`, и ломаться ничего не будет: # In[12]: prog.get('Esmeralda', 'Not found') # Но недостающий элемент мы всегда можем добавить! # In[13]: prog['Esmeralda'] = 'Segara' prog # **Внимание:** Если элемент с указанным ключом уже существует, новый с таким же ключом не добавится! Ключ ‒ это уникальный идентификатор элемента. Если мы добавим в словарь новый элемент с уже существующим ключом, мы просто изменим старый ‒ словари являются изменяемыми объектами. Например, так (изменения в программе): # In[14]: prog['Esmeralda'] = 'Noa' prog # Раз элементами словаря являются пары *ключ-значение*, наверняка есть способ выбрать из словаря ключи и значения отдельно. Действительно, для этого есть методы `.keys()` и `values()`. Вызовем сначала все ключи: # In[15]: prog.keys() # Объект, который мы только что увидели, очень похож на список. Но обычным списком на самом деле не является. Давайте попробуем выбрать первый элемент `prog.keys()`: # In[16]: keys = prog.keys() keys[0] # Не получается! Потому что полученный объект имеет специальный тип `dict_keys`, а не `list`. Но это всегда можно поправить, превратив объект `dict_keys` в список: # In[17]: list(keys)[0] # получается! # Аналогичным образом можно работать и со значениями: # In[18]: prog.values() # Словари могут состоять не только из строк, почти любые объекты могут быть ключами и значениями списка (какие не могут ‒ обсудим позже). Например, можно создать словарь оценок, состоящий из пар *числовой id студента-его оценка*. # In[19]: numbers = {1 : 7, 2 : 8, 3 : 9} # Обращаться к элементам мы будем, естественно, без кавычек, так как ключами являются числа: # In[20]: numbers[1] # оценка студента с id равным 1 # Словари могут состоять из элементов смешанного типа. Например, вместо числового id можно явно записать имя студента: # In[21]: marks = {"Петя": 6, "Вася": 9} # In[22]: marks["Петя"] # Ну, и раз уж питоновские словари так похожи на обычные, давайте представим, что у нас есть словарь, где все слова многозначные. Ключом будет слово, а значением ‒ целый список. # In[23]: my_dict = {'swear' : ['клясться', 'ругаться'], 'dream' : ['спать', 'мечтать']} # По ключу мы получим значение в виде списка: # In[24]: my_dict['swear'] # Так как значением является список, можем отдельно обращаться к его элементам: # In[25]: my_dict['swear'][0] # первый элемент # А теперь давайте подумаем, как можно вывести на экран элементы словаря по очереди, в цикле. Первая попытка: # In[26]: for k in prog: print(k) # Попытка не совсем удалась: на экран были выведены только ключи. А как вывести пары? # # **Задача:** для каждого героя мюзикла из словаря `prog` вывести на экран сообщение вида # # Fiori plays the role of Phoebus # **Решение:** вспомним, что мы можем получать значения по ключу, указывая его в квадратных скобках, и проделаем это для всех ключей в словаре: # In[27]: for k in prog: print(prog[k], "plays the role of", k) # Упражнение полезное, но, на самом деле, существует специальный метод `.items()`, который позволяет обращаться сразу к парам элементов: # In[28]: for k, v in prog.items(): print(k, v) # Для того, чтобы вывести и ключ, и значение, нужно в цикле `for` перечислить две переменные через запятую. И совсем не обязательно называть их `k` и `v` или `key` и `value`; Python сам поймет, что первая переменная соответствует ключу, а вторая ‒ значению. Для примера решим поставленную выше задачу с помощью `items()`. # In[29]: for hero, actor in prog.items(): print(actor, "plays the role of", hero) # Если мы посмотрим на `prog.items()`, мы увидим, что этот объект очень похож на список, состоящий из кортежей: # In[30]: prog.items() # Как и случае с методами `.keys()` и `.values()`, полученный объект не является "обычным" списком. Поэтому при необходимости его нужно будет превратить в список: # In[31]: list(prog.items()) # Метод `.items()` полезен, когда мы хотим выбирать из словаря значения, удовлетворяющие определенным условиям. Для разнообразия возьмем другой словарь ‒ словарь с парами *студент-оценка*: # In[32]: grades = {"Вася": 7, "Петя" : 9, "Коля" : 8, "Лена" : 8, "Василиса" : 10} # И выведем на экран имена тех студентов, у которых оценка равна 8: # In[33]: for name, grade in grades.items(): if grade == 8: print(name) # Только два человека: Коля и Лена. А как проверить, есть ли в словаре элемент с определенным ключом? Воспользоваться уже знакомым оператором `in`: # In[34]: "Коля" in grades.keys() # In[35]: "Ваня" in grades.keys() # ### Создание словарей # Как создать словарь с нуля? Можно сначала создать пустой словарь, а затем добавлять в него элементы, то есть пары *ключ-значение*. # In[36]: my_dict = {} # пустой словарь # In[37]: my_dict[1] = 1 my_dict['hello'] = 'world' # In[38]: my_dict # уже не пустой # Чтобы понять, каким образом еще можно получить словарь, полезно вспомнить, какие объекты лежат в `dict_items()`. # In[39]: my_dict.items() # Как мы уже обсуждали, `dict_items()` ‒ это список кортежей. Значит, из списка кортежей можно, в свою очередь, получить словарь! # In[40]: my_new_dict = dict([(1, 2), ("Hello", "World")]) # In[41]: my_new_dict # А можно ли получить словарь на основе списков? Да, но для начала давайте посмотрим, как из элементов разных списков составить пары значений. Пусть у нас есть два списка: список имен и список оценок. # In[42]: students = ["Веня", "Сеня", "Каролина", "Сабрина"] grades = [6, 7, 8, 9] # Воспользуемся функцией `zip()`. # In[43]: list(zip(students, grades)) # Как следует из названия, функция `zip()` действует как "молния": соединяет две части, то есть два списка, делая из пары списков список пар. Удобно то, что эта функция может соединять не только два списка, но и больше. # In[44]: rating = [3, 2, 4, 1] # место в рейтинге # In[45]: list(zip(students, grades, rating)) # Кроме того, функция `zip()` полезна тем, что помогает создавать словари: # In[46]: gradebook = dict(list(zip(students, grades))) gradebook # ## Еще о списках: функция `enumerate()` # Представим себе такую задачу: у нас есть список студентов, и хотим выводить на экран порядковый номер студента в списке и его имя. # In[47]: students # Как этого добиться, используя уже знакомые способы? Можно подключить к решению этой задачи функцию `range()`и задать интервал от 0 до числа элементов в списке (как мы помним, правый конец исключается): # In[48]: for i in range(0, len(students)): print(i, students[i]) # Но, как всегда, есть готовый и более удобный способ ‒ функция `enumerate()`. Эта функция создает пары *индекс элемента-значение элемента*: # In[49]: list(enumerate(students)) # Поэтому работать в ней можно так же, как с методом `.items()` у словарей. Например, так: # In[50]: for i, v in enumerate(students): print(i, v) # Здесь `i` ‒ индекс элемента в списке, `v` ‒ его значение. И опять же, неважно, как называть эти переменные: Python знает, что если после перечисления переменных в `for`, то первая переменная отвечает за индекс, а вторая ‒ за значение.