tuple - кортежи

Кортежи очень похожи на списки, так как тоже являются последовательностями ссылок на объекты языка Python, но в отличие от списков они не могут быть изменены. Литералами кортежей являются круглые скобки и что бы создать кортеж достаточно перечислить его элементы через запятую и поместить в ():

>>> t = (1, 2, 3, 4)
>>> t
(1, 2, 3, 4)

Чтобы создать пустой кортеж достаточно указать пару пустых круглых скобок:

>>> t = ()
>>> t
()
>>>
>>> type(t)
<class 'tuple'>

Но что бы создать кортеж с одним элементом, после него придется указать запятую:

>>> t = (1,)
>>> t
(1,)

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

>>> (1 + 1)
2

А запятые:

>>> 1 + 1,
(2,)
>>>
>>> (1 + 1,)
(2,)

Поэтому, очень часто скобки вообще не используются:

>>> t = 1, 2, 3, 4
>>> t
(1, 2, 3, 4)

Однако, в зависимости от контекста, круглые скобки указывать все же приходится, например, запись вида func(x, y) будет означать что данной функции передано два аргумента, а запись func((x, y)) будет означать, что функции передан кортеж.

Кортежи могут быть созданы с помощью функции tuple(). Если вызвать данную функцию без аргументов, то она вернет пустой кортеж:

>>> t = tuple()
>>> t
()

Если передать tuple() итерируемый объект, то она попытается преобразовать его в кортеж:

>>> tuple('abcd')
('a', 'b', 'c', 'd')
>>>
>>> tuple(range(5))
(0, 1, 2, 3, 4)
>>>
>>> tuple(dict(a=1, b=2, c=3))
('a', 'b', 'c')

Ну а если передать tuple() кортеж, то будет возвращена его поверхностная копия.

Кортежи не могут быть созданы с помощью генераторов, так как генераторы заключенные в круглые скобки являются "выражениями генераторами"

>>> (i**2 for i in range(10))
<generator object <genexpr> at 0x013DC680>

Поскольку кортежи являются последовательностями, то они поддерживают оператор извлечения среза [START:STOP:STEP] (и индексируются так же как и списки), так же они поддерживают оператор проверки на вхождение in и not in, функцию измерения размера (длины) len(), а так же механиз итерирования с помощью конструкции for... in....


Неизменяемость кортежей

Кортежи, в отлие от списков являются неизменяемыми:

>>> l = [0, 1, 2]
>>> l[0] = 111
>>> l
[111, 1, 2]
>>>
>>>
>>> t = (1, 2, 3)
>>> t[0] = 111
TypeError: 'tuple' object does not support item assignment

Но так же как и строки, они могут служить "сырьем" для новых объектов:

>>> t
(1, 2, 3)
>>>
>>> t = (111,) + t[1:]
>>> t
(111, 2, 3)
>>>
>>>
>>> t = t[0:1] + (222,) + t[2:]
>>> t
(111, 222, 3)

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

>>> t = list(t)
>>> t[2] = 333
>>> t = tuple(t)
>>> t
(111, 222, 333)

Однако, кортеж является неизменяемым только до тех пор пока сам состоит из неизменяемых объектов:

>>> t = (1, 2, [3, 4])
>>> 
>>> t[2][1] = 4444
>>>
>>> t
(1, 2, [3, 4444])

Именно по этой причине, кортежи иногда не могут быть ключами словарей и элементами множеств.

Так как кортежи являются неизменяемыми, то они имеют всего два метода: .count(x) и .index(x).


Распаковка и упаковка кортежей

Кортежи (и списки) могут учавствовать в операциях присваивания:

>>> a, b, c = 1, 2, 3
>>> a, b, c
(1, 2, 3)

Данный механизм очень удобен для обмена значениями между переменными:

>>> a, b, c = c, b, a
>>> a, b, c
(3, 2, 1)

Если слева от оператора = элементов меньше чем справа, то можно воспользоваться оператором * для указания элементу которому может быть присвоено более одного значения:

>>> *a, b, c = (1, 2, 3, 4)
>>> a, b, c
([1, 2], 3, 4)
>>>
>>> a, *b, c = (1, 2, 3, 4)
>>> a, b, c
(1, [2, 3], 4)
>>>
>>> a, b, *c = (1, 2, 3, 4)
>>> a, b, c
(1, 2, [3, 4])

Использование помеченных * переменных, так же позволяет указывать слева от = больше элементов чем справа:

>>> *a, b, c, d, e = (1, 2, 3, 4)
>>> a, b, c, d
([], 1, 2, 3)
>>>
>>> a, b, *c, d, e = (1, 2, 3, 4)
>>> a, b, c, d
(1, 2, [], 3)

Процесс упвковки кортежей, позволяет связываеть воедино разные переменные:

>>> a, b, c, x, y, z = 1, 2, 3, 4, 5, 6
>>>
>>> t1 = a, x
>>>
>>> t2 = x, c, z, b
>>>
>>> t1
(1, 4)
>>>
>>> t2
(4, 3, 6, 2)

Механизм распаковки кортежей может использоваться в конструкциях for... in...:

>>> for x, y in ((1, 2), (1, 3), (1, 4)):
...     print(x, '+', y, '=', x + y)
...
1 + 2 = 3
1 + 3 = 4
1 + 4 = 5

Это может быть очень удобно при работе с некоторыми генераторами:

>>> for x, y in zip('abcd', range(1, 5)):
...     print(x*y)
...
a
bb
ccc
dddd

Именованные кортежи

Кортежи являются коллекциями и могут хранить абсолютно любые объекты языка Python, включая другие кортежи. Если использовать много вложенных кортежей, то легко возникает путаница с индексами элементов и работа с такими кортежами теряет осмысленность:

>>> t = (('mod_1', 8.71, (-1.32, 23.87)), ('mod_2', 5.12, (-0.41, 19.86)))
>>> t
(('mod_1', 8.71, (-1.32, 23.87)), ('mod_2', 5.12, (-0.41, 19.86)))

С одной стороны, можно смириться с этим и пользоваться обычными целочисленными индексами:

>>> t[0][2][1]
23.87

С другой стороны, можно попытаться придать индексам какие-то осмысленные обозначения:

>>> model_1, model_2 = 0, 1
>>> name, mean, min_max = 0, 1, 2
>>> minimum, maximum = 0, 1

Теперь обращение к элементам t может выглядеть чуть логичнее:

>>> t[model_1][min_max][maximum]
23.87

Но самый простой способ это использование встроенного модуля collections из стандартной библиотеки. Благодаря ему "очень легко" создать структуру кортежа из примера выше:

>>> import collections
>>>
>>> models = collections.namedtuple('models', 'model_1 model_2')
>>> params = collections.namedtuple('params', 'name mean min_max')
>>> limit = collections.namedtuple('limit', 'minimum maximum')

Потом, точно также "очень легко" создать сам именованный кортеж:

>>> Models = models(params('mod_1', 8.71, limit(-1.32, 23.87)),
...                 params('mod_2', 5.12, limit(-0.41, 19.86)))

А вот извлекать элементы из такого кортежа, действительно легко:

>>> Models.model_1.min_max.maximum
23.87

Зачем же может понадобиться такая сложная штука? Дело в том, что иногда, вечером вы можете не понять код, который написали утром. Комментарии, строки документирования, и любые другие возможности, включая именованные кортежи призваны сделать ваш код легким для понимания. Да, постучать по клавиатуре, придется чуть дольше, но это лучше и не так обидно, чем начинать все сначала, после того как спустя неделю смысл кода окончательно забылся.