Кортежи встречаются не только в программировании, но и в математике. В математике под кортежем обычно понимают упорядоченное множество. Про множества мы немного говорили на прошлой лекции, множество ‒ это просто набор каких-то элементов, причем элементы множества не должны повторятся. Порядок элементов в множестве не важен: {1, 2, 3} и {3, 2, 1} ‒ это одно и то же множество. В кортеже мы точно знаем, какой элемент является первым, какой ‒ вторым, и так далее.
Внешне кортежи несильно отличаются от списков. Единственное внешнее отличие ‒ элементы кортежа заключаются в круглые, а не в квадратные скобочки.
my_tuple = (1, 2, 4, 6, 9)
К элементам кортежа можно обращаться точно так же, как к элементам списка:
my_tuple[0]
1
Но, несмотря на кажущееся сходство, кортежи и списки ‒ принципиально разные объекты. Главное отличие кортежей от списков заключается в том, что кортежи ‒ неизменяемые структуры данных. Другими словами, изменять элементы кортежа нельзя. Проверим:
my_tuple[2] = 65
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-3-a0083389ad43> in <module>() ----> 1 my_tuple[2] = 65 TypeError: 'tuple' object does not support item assignment
Иногда это свойство бывает полезным (некоторая "защита" от изменений), иногда ‒ не очень, но для нас пока важно познакомиться с разными объектами в Python, чтобы потом не удивляться. Ведь многие более продвинутые функции могут возвращать результат или, наоборот, принимать на вход только кортежи или только списки.
При желании кортеж можно превратить в список:
list(my_tuple)
[1, 2, 4, 6, 9]
И наоборот:
tuple([1,2,3])
(1, 2, 3)
Обсуждая словари в Python, удобно проводить аналогию с обычными словарями (бумажными или электронными). Что такое словарь? Перечень из пар: слово-значение или слово-список значений, если значений несколько. Вот и словарь в Python ‒ это объект, структура данных, которая позволяет хранить пары соответствий.
Давайте представим, что нам нужно создать словарь, который мы будем использовать для программки к мюзиклу "Notre Dame de Paris" (да, меня не отпускает). Будем записывать в словарь prog
пары соответствий герой-актер.
prog = {'Gringoire' : 'Pelletier', 'Frollo' : 'Lavoie', 'Phoebus': 'Fiori'}
Первый элемент в каждой паре (до двоеточия) назвается ключом (key), второй элемент в каждой паре (после двоеточия) ‒ значением (value). Посмотрим на словарь:
prog
{'Frollo': 'Lavoie', 'Gringoire': 'Pelletier', 'Phoebus': 'Fiori'}
Как и в случае со списками или кортежами, к элементам словаря можно обращаться. Только выбор элемента производится не по индексу, а по ключу: сначала указываем название словаря, а потом в квадратных скобках ‒ ключ, по которому мы хотим вернуть значение. Например, выясним, кто играет роль Феба:
prog['Phoebus']
'Fiori'
А что будет, если мы запросим элемент по ключу, которого нет в словаре?
prog['Esmeralda']
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-9-a18d7b346a4e> in <module>() ----> 1 prog['Esmeralda'] KeyError: 'Esmeralda'
В глубине души Python начинает грустно петь "Where is she, your Esmeralda?", но вместо эмоций выдает сухое KeyError. Ошибка ключа -- ну нет в словаре элемента с ключом Esmeralda!
Теперь представьте себе такую ситуацию: у нас есть список героев (ключей) и мы хотим в цикле вернуть по ним в фамилии актеров (значения). Какого-то одного из героев нет. Что произойдет? На каком-то этапе Python выдаст ошибку, мы вывалимся из цикла и на этом наша работа остановится. Обидно, да? Чтобы такого избежать, получать значение по ключу можно другим способом ‒ используя метод .get()
:
prog.get('Esmeralda') # ни результата, ни ошибки
Если выведем результат на экран явно, с помощью print()
, увидим, что в случае, если пары с указанным ключом в словаре нет, Python выдаст значение None
:
print(prog.get('Esmeralda'))
None
Удобство метода .get()
заключается в том, что мы сами можем установить, какое значение будет возвращено, в случае, если пары с выбранным ключом нет в словаре. Так, вместо None
мы можем вернуть строку Not found
, и ломаться ничего не будет:
prog.get('Esmeralda', 'Not found')
'Not found'
Но недостающий элемент мы всегда можем добавить!
prog['Esmeralda'] = 'Segara'
prog
{'Esmeralda': 'Segara', 'Frollo': 'Lavoie', 'Gringoire': 'Pelletier', 'Phoebus': 'Fiori'}
Внимание: Если элемент с указанным ключом уже существует, новый с таким же ключом не добавится! Ключ ‒ это уникальный идентификатор элемента. Если мы добавим в словарь новый элемент с уже существующим ключом, мы просто изменим старый ‒ словари являются изменяемыми объектами. Например, так (изменения в программе):
prog['Esmeralda'] = 'Noa'
prog
{'Esmeralda': 'Noa', 'Frollo': 'Lavoie', 'Gringoire': 'Pelletier', 'Phoebus': 'Fiori'}
Раз элементами словаря являются пары ключ-значение, наверняка есть способ выбрать из словаря ключи и значения отдельно. Действительно, для этого есть методы .keys()
и values()
. Вызовем сначала все ключи:
prog.keys()
dict_keys(['Gringoire', 'Esmeralda', 'Phoebus', 'Frollo'])
Объект, который мы только что увидели, очень похож на список. Но обычным списком на самом деле не является. Давайте попробуем выбрать первый элемент prog.keys()
:
keys = prog.keys()
keys[0]
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-16-5481656211fb> in <module>() 1 keys = prog.keys() ----> 2 keys[0] TypeError: 'dict_keys' object does not support indexing
Не получается! Потому что полученный объект имеет специальный тип dict_keys
, а не list
. Но это всегда можно поправить, превратив объект dict_keys
в список:
list(keys)[0] # получается!
'Gringoire'
Аналогичным образом можно работать и со значениями:
prog.values()
dict_values(['Pelletier', 'Noa', 'Fiori', 'Lavoie'])
Словари могут состоять не только из строк, почти любые объекты могут быть ключами и значениями списка (какие не могут ‒ обсудим позже). Например, можно создать словарь оценок, состоящий из пар числовой id студента-его оценка.
numbers = {1 : 7, 2 : 8, 3 : 9}
Обращаться к элементам мы будем, естественно, без кавычек, так как ключами являются числа:
numbers[1] # оценка студента с id равным 1
7
Словари могут состоять из элементов смешанного типа. Например, вместо числового id можно явно записать имя студента:
marks = {"Петя": 6, "Вася": 9}
marks["Петя"]
6
Ну, и раз уж питоновские словари так похожи на обычные, давайте представим, что у нас есть словарь, где все слова многозначные. Ключом будет слово, а значением ‒ целый список.
my_dict = {'swear' : ['клясться', 'ругаться'], 'dream' : ['спать', 'мечтать']}
По ключу мы получим значение в виде списка:
my_dict['swear']
['клясться', 'ругаться']
Так как значением является список, можем отдельно обращаться к его элементам:
my_dict['swear'][0] # первый элемент
'клясться'
А теперь давайте подумаем, как можно вывести на экран элементы словаря по очереди, в цикле. Первая попытка:
for k in prog:
print(k)
Gringoire Esmeralda Phoebus Frollo
Попытка не совсем удалась: на экран были выведены только ключи. А как вывести пары?
Задача: для каждого героя мюзикла из словаря prog
вывести на экран сообщение вида
Fiori plays the role of Phoebus
Решение: вспомним, что мы можем получать значения по ключу, указывая его в квадратных скобках, и проделаем это для всех ключей в словаре:
for k in prog:
print(prog[k], "plays the role of", k)
Pelletier plays the role of Gringoire Noa plays the role of Esmeralda Fiori plays the role of Phoebus Lavoie plays the role of Frollo
Упражнение полезное, но, на самом деле, существует специальный метод .items()
, который позволяет обращаться сразу к парам элементов:
for k, v in prog.items():
print(k, v)
Gringoire Pelletier Esmeralda Noa Phoebus Fiori Frollo Lavoie
Для того, чтобы вывести и ключ, и значение, нужно в цикле for
перечислить две переменные через запятую. И совсем не обязательно называть их k
и v
или key
и value
; Python сам поймет, что первая переменная соответствует ключу, а вторая ‒ значению. Для примера решим поставленную выше задачу с помощью items()
.
for hero, actor in prog.items():
print(actor, "plays the role of", hero)
Pelletier plays the role of Gringoire Noa plays the role of Esmeralda Fiori plays the role of Phoebus Lavoie plays the role of Frollo
Если мы посмотрим на prog.items()
, мы увидим, что этот объект очень похож на список, состоящий из кортежей:
prog.items()
dict_items([('Gringoire', 'Pelletier'), ('Esmeralda', 'Noa'), ('Phoebus', 'Fiori'), ('Frollo', 'Lavoie')])
Как и случае с методами .keys()
и .values()
, полученный объект не является "обычным" списком. Поэтому при необходимости его нужно будет превратить в список:
list(prog.items())
[('Gringoire', 'Pelletier'), ('Esmeralda', 'Noa'), ('Phoebus', 'Fiori'), ('Frollo', 'Lavoie')]
Метод .items()
полезен, когда мы хотим выбирать из словаря значения, удовлетворяющие определенным условиям. Для разнообразия возьмем другой словарь ‒ словарь с парами студент-оценка:
grades = {"Вася": 7, "Петя" : 9, "Коля" : 8, "Лена" : 8,
"Василиса" : 10}
И выведем на экран имена тех студентов, у которых оценка равна 8:
for name, grade in grades.items():
if grade == 8:
print(name)
Коля Лена
Только два человека: Коля и Лена. А как проверить, есть ли в словаре элемент с определенным ключом? Воспользоваться уже знакомым оператором in
:
"Коля" in grades.keys()
True
"Ваня" in grades.keys()
False
Как создать словарь с нуля? Можно сначала создать пустой словарь, а затем добавлять в него элементы, то есть пары ключ-значение.
my_dict = {} # пустой словарь
my_dict[1] = 1
my_dict['hello'] = 'world'
my_dict # уже не пустой
{1: 1, 'hello': 'world'}
Чтобы понять, каким образом еще можно получить словарь, полезно вспомнить, какие объекты лежат в dict_items()
.
my_dict.items()
dict_items([(1, 1), ('hello', 'world')])
Как мы уже обсуждали, dict_items()
‒ это список кортежей. Значит, из списка кортежей можно, в свою очередь, получить словарь!
my_new_dict = dict([(1, 2), ("Hello", "World")])
my_new_dict
{1: 2, 'Hello': 'World'}
А можно ли получить словарь на основе списков? Да, но для начала давайте посмотрим, как из элементов разных списков составить пары значений. Пусть у нас есть два списка: список имен и список оценок.
students = ["Веня", "Сеня", "Каролина", "Сабрина"]
grades = [6, 7, 8, 9]
Воспользуемся функцией zip()
.
list(zip(students, grades))
[('Веня', 6), ('Сеня', 7), ('Каролина', 8), ('Сабрина', 9)]
Как следует из названия, функция zip()
действует как "молния": соединяет две части, то есть два списка, делая из пары списков список пар. Удобно то, что эта функция может соединять не только два списка, но и больше.
rating = [3, 2, 4, 1] # место в рейтинге
list(zip(students, grades, rating))
[('Веня', 6, 3), ('Сеня', 7, 2), ('Каролина', 8, 4), ('Сабрина', 9, 1)]
Кроме того, функция zip()
полезна тем, что помогает создавать словари:
gradebook = dict(list(zip(students, grades)))
gradebook
{'Веня': 6, 'Каролина': 8, 'Сабрина': 9, 'Сеня': 7}
enumerate()
¶Представим себе такую задачу: у нас есть список студентов, и хотим выводить на экран порядковый номер студента в списке и его имя.
students
['Веня', 'Сеня', 'Каролина', 'Сабрина']
Как этого добиться, используя уже знакомые способы? Можно подключить к решению этой задачи функцию range()
и задать интервал от 0 до числа элементов в списке (как мы помним, правый конец исключается):
for i in range(0, len(students)):
print(i, students[i])
0 Веня 1 Сеня 2 Каролина 3 Сабрина
Но, как всегда, есть готовый и более удобный способ ‒ функция enumerate()
. Эта функция создает пары индекс элемента-значение элемента:
list(enumerate(students))
[(0, 'Веня'), (1, 'Сеня'), (2, 'Каролина'), (3, 'Сабрина')]
Поэтому работать в ней можно так же, как с методом .items()
у словарей. Например, так:
for i, v in enumerate(students):
print(i, v)
0 Веня 1 Сеня 2 Каролина 3 Сабрина
Здесь i
‒ индекс элемента в списке, v
‒ его значение. И опять же, неважно, как называть эти переменные: Python знает, что если после перечисления переменных в for
, то первая переменная отвечает за индекс, а вторая ‒ за значение.