Списки и кортежи в Python

Оглавление

Списки и кортежи являются, пожалуй, самыми универсальными и полезными типами данных в Python. Вы найдете их практически в каждой нетривиальной программе на Python.

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

Списки Python

Вкратце, список - это коллекция произвольных объектов, чем-то похожая на массив во многих других языках программирования, но более гибкая. Списки определяются в Python путем заключения последовательности объектов, разделенных запятыми, в квадратные скобки ([]), как показано ниже:

>>> a = ['foo', 'bar', 'baz', 'qux']

>>> print(a)
['foo', 'bar', 'baz', 'qux']
>>> a
['foo', 'bar', 'baz', 'qux']

Важными характеристиками списков Python являются следующие:

  • Списки упорядочены.
  • Списки могут содержать любые произвольные объекты.
  • К элементам списка можно обращаться по индексу.
  • Списки могут быть вложены на произвольную глубину.
  • Списки являются изменяемыми.
  • Списки динамические.

Ниже более подробно рассматривается каждая из этих особенностей.

Списки упорядочены

Список - это не просто коллекция объектов. Это упорядоченная коллекция объектов. Порядок, в котором вы указываете элементы при определении списка, является врожденной характеристикой этого списка и сохраняется на протяжении всего его существования. (Вы увидите тип данных Python, который не является упорядоченным, в следующем уроке по словарям.)

Списки, в которых одинаковые элементы расположены в разном порядке, не являются одинаковыми:

>>> a = ['foo', 'bar', 'baz', 'qux']
>>> b = ['baz', 'qux', 'bar', 'foo']
>>> a == b
False
>>> a is b
False

>>> [1, 2, 3, 4] == [4, 1, 3, 2]
False

Списки могут содержать произвольные объекты

Список может содержать любой набор объектов. Все элементы списка могут быть одного типа:

>>> a = [2, 4, 6, 8]
>>> a
[2, 4, 6, 8]

Или элементы могут быть разных типов:

>>> a = [21.42, 'foobar', 3, 4, 'bark', False, 3.14159]
>>> a
[21.42, 'foobar', 3, 4, 'bark', False, 3.14159]

Списки могут содержать даже сложные объекты, такие как функции, классы и модули, о которых вы узнаете в следующих уроках:

>>> int
<class 'int'>
>>> len
<built-in function len>
>>> def foo():
...     pass
...
>>> foo
<function foo at 0x035B9030>
>>> import math
>>> math
<module 'math' (built-in)>

>>> a = [int, len, foo, math]
>>> a
[<class 'int'>, <built-in function len>, <function foo at 0x02CA2618>,
<module 'math' (built-in)>]

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

>>> a = []
>>> a
[]

>>> a = [ 'foo' ]
>>> a
['foo']

>>> a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
... 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
... 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60,
... 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
... 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58,
59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96,
97, 98, 99, 100]

(Список с единственным объектом иногда называют списком синглтонов.)

Объекты списка не обязательно должны быть уникальными. Данный объект может появляться в списке несколько раз:

>>> a = ['bark', 'meow', 'woof', 'bark', 'cheep', 'bark']
>>> a
['bark', 'meow', 'woof', 'bark', 'cheep', 'bark']

Элементы списка могут быть доступны по индексу

Доступ к отдельным элементам списка можно получить с помощью индекса в квадратных скобках. Это в точности аналогично доступу к отдельным символам в строке. Индексация списков основана на нулях, как и в случае со строками.

Рассмотрите следующий список:

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']

Индексы для элементов в a показаны ниже:

Diagram of a Python list Список индексов

Вот код на языке Python для доступа к некоторым элементам a:

>>> a[0]
'foo'
>>> a[2]
'baz'
>>> a[5]
'corge'

Практически все, что касается индексации строк, аналогично работает и для списков. Например, отрицательный индекс списка отсчитывается от конца списка:

Diagram of a Python list

Отрицательная индексация списков

>>> a[-1]
'corge'
>>> a[-2]
'quux'
>>> a[-5]
'bar'

Нарезка также работает. Если a - список, то выражение a[m:n] возвращает часть a от индекса m до индекса n, но не включая его:

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']

>>> a[2:5]
['baz', 'qux', 'quux']

Другие особенности нарезки строк аналогично работают и для нарезки списков:

  • Можно указывать как положительные, так и отрицательные индексы:

    >>> a[-5:-2]
    ['bar', 'baz', 'qux']
    >>> a[1:4]
    ['bar', 'baz', 'qux']
    >>> a[-5:-2] == a[1:4]
    True
    
  • Опущение первого индекса начинает срез с начала списка, а опущение второго индекса расширяет срез до конца списка:

    >>> print(a[:4], a[0:4])
    ['foo', 'bar', 'baz', 'qux'] ['foo', 'bar', 'baz', 'qux']
    >>> print(a[2:], a[2:len(a)])
    ['baz', 'qux', 'quux', 'corge'] ['baz', 'qux', 'quux', 'corge']
    
    >>> a[:4] + a[4:]
    ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']
    >>> a[:4] + a[4:] == a
    True
    
  • Вы можете указать строку - как положительную, так и отрицательную:

    >>> a[0:6:2]
    ['foo', 'baz', 'quux']
    >>> a[1:6:2]
    ['bar', 'qux', 'corge']
    >>> a[6:0:-2]
    ['corge', 'qux', 'bar']
    
  • Синтаксис для обратного преобразования списка работает так же, как и для строк:

    >>> a[::-1]
    ['corge', 'quux', 'qux', 'baz', 'bar', 'foo']
    
  • Синтаксис [:] работает и для списков. Однако есть важное различие между тем, как эта операция работает со списком, и тем, как она работает со строкой.

    Если s - строка, s[:] возвращает ссылку на тот же объект:

    >>> s = 'foobar'
    >>> s[:]
    'foobar'
    >>> s[:] is s
    True
    

    И наоборот, если a - список, a[:] возвращает новый объект, который является копией a:

    >>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']
    >>> a[:]
    ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']
    >>> a[:] is a
    False
    

Несколько операторов и встроенных функций Python также могут использоваться со списками по аналогии со строками:

  • Операторы in и not in:

    >>> a
    ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']
    
    >>> 'qux' in a
    True
    >>> 'thud' not in a
    True
    
  • Операторы конкатенации (+) и репликации (*):

    >>> a
    ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']
    
    >>> a + ['grault', 'garply']
    ['foo', 'bar', 'baz', 'qux', 'quux', 'corge', 'grault', 'garply']
    >>> a * 2
    ['foo', 'bar', 'baz', 'qux', 'quux', 'corge', 'foo', 'bar', 'baz',
    'qux', 'quux', 'corge']
    
  • Функции len(), min() и max():

    >>> a
    ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']
    
    >>> len(a)
    6
    >>> min(a)
    'bar'
    >>> max(a)
    'qux'
    

Не случайно строки и списки ведут себя так одинаково. Они оба являются частными случаями более общего типа объектов, называемого iterable, с которым вы познакомитесь более подробно в предстоящем уроке по определенной итерации.

Кстати, в каждом из приведенных примеров список всегда присваивается переменной перед выполнением операции над ним. Но вы можете оперировать и с литералом списка:

>>> ['foo', 'bar', 'baz', 'qux', 'quux', 'corge'][2]
'baz'

>>> ['foo', 'bar', 'baz', 'qux', 'quux', 'corge'][::-1]
['corge', 'quux', 'qux', 'baz', 'bar', 'foo']

>>> 'quux' in ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']
True

>>> ['foo', 'bar', 'baz'] + ['qux', 'quux', 'corge']
['foo', 'bar', 'baz', 'qux', 'quux', 'corge']

>>> len(['foo', 'bar', 'baz', 'qux', 'quux', 'corge'][::-1])
6

Аналогичным образом можно поступить и со строковым литералом:

>>> 'If Comrade Napoleon says it, it must be right.'[::-1]
'.thgir eb tsum ti ,ti syas noelopaN edarmoC fI'

Списки могут быть вложенными

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

Рассмотрим этот (по общему признанию, надуманный) пример:

>>> x = ['a', ['bb', ['ccc', 'ddd'], 'ee', 'ff'], 'g', ['hh', 'ii'], 'j']
>>> x
['a', ['bb', ['ccc', 'ddd'], 'ee', 'ff'], 'g', ['hh', 'ii'], 'j']

Структура объекта, на который ссылается x, показана ниже:

Nested lists diagram Вложенный список

x[0], x[2] и x[4] - строки, каждая длиной в один символ:

>>> print(x[0], x[2], x[4])
a g j

Но x[1] и x[3] - это подсписки:

>>> x[1]
['bb', ['ccc', 'ddd'], 'ee', 'ff']

>>> x[3]
['hh', 'ii']

Чтобы получить доступ к элементам в подсписке, просто добавьте дополнительный индекс:

>>> x[1]
['bb', ['ccc', 'ddd'], 'ee', 'ff']

>>> x[1][0]
'bb'
>>> x[1][1]
['ccc', 'ddd']
>>> x[1][2]
'ee'
>>> x[1][3]
'ff'

>>> x[3]
['hh', 'ii']
>>> print(x[3][0], x[3][1])
hh ii

x[1][1] - это еще один подсписок, поэтому добавление еще одного индекса дает доступ к его элементам:

>>> x[1][1]
['ccc', 'ddd']
>>> print(x[1][1][0], x[1][1][1])
ccc ddd

Нет никаких ограничений, кроме объема памяти вашего компьютера, на глубину или сложность, с которой списки могут быть вложены таким образом.

Весь обычный синтаксис, касающийся индексов и нарезки, применим и к подспискам:

>>> x[1][1][-1]
'ddd'
>>> x[1][1:3]
[['ccc', 'ddd'], 'ee']
>>> x[3][::-1]
['ii', 'hh']

Однако помните, что операторы и функции применяются только к списку на указанном вами уровне и не являются рекурсивными. Рассмотрим, что произойдет, если вы запросите длину x с помощью len():

>>> x
['a', ['bb', ['ccc', 'ddd'], 'ee', 'ff'], 'g', ['hh', 'ii'], 'j']
>>> len(x)
5

>>> x[0]
'a'
>>> x[1]
['bb', ['ccc', 'ddd'], 'ee', 'ff']
>>> x[2]
'g'
>>> x[3]
['hh', 'ii']
>>> x[4]
'j'

x имеет всего пять элементов - три строки и два подсписка. Отдельные элементы в подсписках не учитываются при определении длины x.

Вы столкнетесь с аналогичной ситуацией при использовании оператора in:

>>> 'ddd' in x
False
>>> 'ddd' in x[1]
False
>>> 'ddd' in x[1][1]
True

'ddd' не является ни одним из элементов в x или x[1]. Он является только непосредственно элементом вложенного списка x[1][1]. Отдельный элемент в подсписке не считается элементом родительского списка(ов).

Списки являются изменяемыми

Большинство типов данных, с которыми вы до сих пор сталкивались, были атомарными типами. Например, объекты Integer или Float - это примитивные единицы, которые нельзя разбить на части. Эти типы неизменяемы, что означает, что их нельзя изменить после присвоения. Нет особого смысла думать об изменении значения целого числа. Если вам нужно другое целое число, вы просто присваиваете ему другое значение.

В отличие от него, тип string является составным типом. Строки сводятся к более мелким частям - символам-компонентам. Возможно, имеет смысл подумать об изменении символов в строке. Но это невозможно. В Python строки также неизменяемы.

Список - это первый изменяемый тип данных, с которым вы столкнулись. После создания списка его элементы можно добавлять, удалять, сдвигать и перемещать по своему усмотрению. Python предоставляет широкий спектр способов модификации списков.

Модификация одного значения списка

Одиночное значение в списке можно заменить с помощью индексации и простого присваивания:

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']
>>> a
['foo', 'bar', 'baz', 'qux', 'quux', 'corge']

>>> a[2] = 10
>>> a[-1] = 20
>>> a
['foo', 'bar', 10, 'qux', 'quux', 20]

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

>>> s = 'foobarbaz'
>>> s[2] = 'x'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

Элемент списка можно удалить с помощью команды del:

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']

>>> del a[3]
>>> a
['foo', 'bar', 'baz', 'quux', 'corge']

Модификация нескольких значений списка

Что делать, если вы хотите изменить несколько смежных элементов в списке за один раз? Python позволяет это сделать с помощью функции slice assignment, которая имеет следующий синтаксис:

a[m:n] = <iterable>

Опять же, пока что думайте об итерируемом как о списке. Это присваивание заменяет указанный фрагмент a на <iterable>:

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']

>>> a[1:4]
['bar', 'baz', 'qux']
>>> a[1:4] = [1.1, 2.2, 3.3, 4.4, 5.5]
>>> a
['foo', 1.1, 2.2, 3.3, 4.4, 5.5, 'quux', 'corge']
>>> a[1:6]
[1.1, 2.2, 3.3, 4.4, 5.5]
>>> a[1:6] = ['Bark!']
>>> a
['foo', 'Bark!', 'quux', 'corge']

Количество вставленных элементов не обязательно должно быть равно количеству замененных. Python просто увеличивает или уменьшает список по мере необходимости.

Вы можете вставить несколько элементов вместо одного - просто используйте фрагмент, обозначающий только один элемент:

>>> a = [1, 2, 3]
>>> a[1:2] = [2.1, 2.2, 2.3]
>>> a
[1, 2.1, 2.2, 2.3, 3]

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

>>> a = [1, 2, 3]
>>> a[1] = [2.1, 2.2, 2.3]
>>> a
[1, [2.1, 2.2, 2.3], 3]

Вы также можете вставлять элементы в список, ничего не удаляя. Просто укажите фрагмент вида [n:n] (фрагмент нулевой длины) на нужный индекс:

>>> a = [1, 2, 7, 8]
>>> a[2:2] = [3, 4, 5, 6]
>>> a
[1, 2, 3, 4, 5, 6, 7, 8]

Вы можете удалить несколько элементов из середины списка, присвоив соответствующий фрагмент пустому списку. Вы также можете использовать оператор del с тем же фрагментом:

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']
>>> a[1:5] = []
>>> a
['foo', 'corge']

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']
>>> del a[1:5]
>>> a
['foo', 'corge']

Предоставление или добавление элементов в список

Дополнительные элементы можно добавить в начало или конец списка с помощью оператора конкатенации + или оператора присваивания += с дополнениями:

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']

>>> a += ['grault', 'garply']
>>> a
['foo', 'bar', 'baz', 'qux', 'quux', 'corge', 'grault', 'garply']

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']

>>> a = [10, 20] + a
>>> a
[10, 20, 'foo', 'bar', 'baz', 'qux', 'quux', 'corge']

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

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']
>>> a += 20
Traceback (most recent call last):
  File "<pyshell#58>", line 1, in <module>
    a += 20
TypeError: 'int' object is not iterable

>>> a += [20]
>>> a
['foo', 'bar', 'baz', 'qux', 'quux', 'corge', 20]

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

Строки также являются итерируемыми. Но посмотрите, что происходит, когда вы конкатенируете строку в список:

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux']
>>> a += 'corge'
>>> a
['foo', 'bar', 'baz', 'qux', 'quux', 'c', 'o', 'r', 'g', 'e']

Возможно, этот результат - не совсем то, что вы ожидали. Когда строка итерируется, результатом является список составляющих ее символов. В приведенном выше примере то, что будет конкатенировано в список a, будет списком символов в строке 'corge'.

Если вы действительно хотите добавить в конец списка только одну строку 'corge', вам нужно указать ее как список синглтонов:

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux']
>>> a += ['corge']
>>> a
['foo', 'bar', 'baz', 'qux', 'quux', 'corge']

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

Методы, изменяющие список

Наконец, Python предоставляет несколько встроенных методов, которые можно использовать для модификации списков. Информация об этих методах подробно описана ниже.

Примечание: Строковые методы, которые вы видели в предыдущем уроке, не изменяли целевую строку напрямую. Это связано с тем, что строки неизменяемы. Вместо этого строковые методы возвращают новый строковый объект, который изменяется в соответствии с указаниями метода. При этом исходная целевая строка остается неизменной:

>>> s = 'foobar'
>>> t = s.upper()
>>> print(s, t)
foobar FOOBAR

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

a.append(<obj>)

Добавляет объект в список.

a.append(<obj>) добавляет объект <obj> в конец списка a:

>>> a = ['a', 'b']
>>> a.append(123)
>>> a
['a', 'b', 123]

Помните, что методы списка изменяют целевой список на месте. Они не возвращают новый список:

>>> a = ['a', 'b']
>>> x = a.append(123)
>>> print(x)
None
>>> a
['a', 'b', 123]

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

>>> a = ['a', 'b']
>>> a + [1, 2, 3]
['a', 'b', 1, 2, 3]

Метод .append() так не работает! Если итерируемый объект добавляется к списку с помощью .append(), он добавляется как один объект:

>>> a = ['a', 'b']
>>> a.append([1, 2, 3])
>>> a
['a', 'b', [1, 2, 3]]

Таким образом, с помощью .append() вы можете добавить строку как единое целое:

>>> a = ['a', 'b']
>>> a.append('foo')
>>> a
['a', 'b', 'foo']

a.extend(<iterable>)

Расширяет список объектами из итерируемой таблицы.

Да, это, вероятно, то, что вы думаете. .extend() также добавляет в конец списка, но ожидается, что аргумент будет итерируемым. Элементы в <iterable> добавляются по отдельности:

>>> a = ['a', 'b']
>>> a.extend([1, 2, 3])
>>> a
['a', 'b', 1, 2, 3]

Другими словами, .extend() ведет себя как оператор +. Точнее, поскольку он изменяет список на месте, он ведет себя как оператор +=:

>>> a = ['a', 'b']
>>> a += [1, 2, 3]
>>> a
['a', 'b', 1, 2, 3]

a.insert(<index>, <obj>)

Вставляет объект в список.

a.insert(<index>, <obj>) вставляет объект <obj> в список a по указанному адресу <index>. После вызова метода a[<index>] становится <obj>, а оставшиеся элементы списка сдвигаются вправо:

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']
>>> a.insert(3, 3.14159)
>>> a[3]
3.14159
>>> a
['foo', 'bar', 'baz', 3.14159, 'qux', 'quux', 'corge']

a.remove(<obj>)

Удаляет объект из списка.

a.remove(<obj>) удаляет объект <obj> из списка a. Если <obj> не находится в a, возникает исключение:

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']
>>> a.remove('baz')
>>> a
['foo', 'bar', 'qux', 'quux', 'corge']

>>> a.remove('Bark!')
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    a.remove('Bark!')
ValueError: list.remove(x): x not in list

a.pop(index=-1)

Удаляет элемент из списка.

Этот метод отличается от .remove() двумя способами:

  1. Вы указываете индекс удаляемого элемента, а не сам объект.
  2. Метод возвращает значение: элемент, который был удален.

a.pop() просто удаляет последний элемент в списке:

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']

>>> a.pop()
'corge'
>>> a
['foo', 'bar', 'baz', 'qux', 'quux']

>>> a.pop()
'quux'
>>> a
['foo', 'bar', 'baz', 'qux']

Если указан необязательный параметр <index>, то элемент с этим индексом удаляется и возвращается. <index> может быть отрицательным, как при индексации строк и списков:

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']

>>> a.pop(1)
'bar'
>>> a
['foo', 'baz', 'qux', 'quux', 'corge']

>>> a.pop(-3)
'qux'
>>> a
['foo', 'baz', 'quux', 'corge']

<index> по умолчанию соответствует -1, поэтому a.pop(-1) эквивалентен a.pop().

Списки динамические

Это учебное пособие началось с перечисления шести определяющих характеристик списков Python. Последняя из них заключается в том, что списки являются динамическими. Вы видели множество примеров этого в разделах выше. Когда элементы добавляются в список, он растет по мере необходимости:

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']

>>> a[2:2] = [1, 2, 3]
>>> a += [3.14159]
>>> a
['foo', 'bar', 1, 2, 3, 'baz', 'qux', 'quux', 'corge', 3.14159]

Аналогично, список уменьшается для удаления элементов:

>>> a = ['foo', 'bar', 'baz', 'qux', 'quux', 'corge']
>>> a[2:3] = []
>>> del a[0]
>>> a
['bar', 'qux', 'quux', 'corge']

Python Tuples

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

Произношение варьируется в зависимости от того, кого вы спросите. Некоторые произносят его как "too-ple" (рифмуя с "Mott the Hoople"), а другие - как "tup-ple" (рифмуя с "supple"). Я склоняюсь к последнему варианту, поскольку он, предположительно, имеет то же происхождение, что и "квинтупл", "секступл", "октупл" и так далее, а все, кого я знаю, произносят их так, как будто они рифмуются с "supple".

Определение и использование кортежей

Кортежи идентичны спискам во всех отношениях, за исключением следующих свойств:

  • Кортежи определяются путем заключения элементов в круглые скобки (()) вместо квадратных скобок ([]).
  • Кортежи неизменяемы.

Вот короткий пример, показывающий определение кортежа, индексацию и нарезку:

>>> t = ('foo', 'bar', 'baz', 'qux', 'quux', 'corge')
>>> t
('foo', 'bar', 'baz', 'qux', 'quux', 'corge')

>>> t[0]
'foo'
>>> t[-1]
'corge'
>>> t[1::2]
('bar', 'qux', 'corge')

Никогда не бойтесь! Наш любимый механизм разворота строк и списков работает и для кортежей:

>>> t[::-1]
('corge', 'quux', 'qux', 'baz', 'bar', 'foo')

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

Все, что вы узнали о списках - они упорядочены, могут содержать произвольные объекты, могут быть индексированы и нарезаны, могут быть вложенными - справедливо и для кортежей. Но они не могут быть изменены:

>>> t = ('foo', 'bar', 'baz', 'qux', 'quux', 'corge')
>>> t[2] = 'Bark!'
Traceback (most recent call last):
  File "<pyshell#65>", line 1, in <module>
    t[2] = 'Bark!'
TypeError: 'tuple' object does not support item assignment

Зачем использовать кортеж вместо списка?

  • При работе с кортежем программа выполняется быстрее, чем с эквивалентным списком. (Это, вероятно, не будет заметно, если список или кортеж мал).

  • Иногда вы не хотите, чтобы данные изменялись. Если предполагается, что значения в коллекции останутся неизменными в течение всей жизни программы, использование кортежа вместо списка защитит от случайного изменения.

  • Существует еще один тип данных Python, с которым вы скоро столкнетесь, - словарь, который требует в качестве одного из своих компонентов значения неизменяемого типа. Кортеж может быть использован для этой цели, в то время как список - нет.

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

>>> a = 'foo'
>>> b = 42
>>> a, 3.14159, b
('foo', 3.14159, 42)

Python отображает ответ в круглых скобках, поскольку неявно интерпретирует входные данные как кортеж.

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

>>> t = ()
>>> type(t)
<class 'tuple'>
>>> t = (1, 2)
>>> type(t)
<class 'tuple'>
>>> t = (1, 2, 3, 4, 5)
>>> type(t)
<class 'tuple'>

Но что происходит, когда вы пытаетесь определить кортеж с одним элементом:

>>> t = (2)
>>> type(t)
<class 'int'>

Doh! Поскольку круглые скобки также используются для определения старшинства операторов в выражениях, Python оценивает выражение (2) как просто целое число 2 и создает объект int. Чтобы сообщить Python, что вы действительно хотите определить кортеж синглтонов, поставьте запятую в конце выражения (,) непосредственно перед закрывающей скобкой:

>>> t = (2,)
>>> type(t)
<class 'tuple'>
>>> t[0]
2
>>> t[-1]
2

Вероятно, вам не придется часто определять кортеж-одиночку, но способ должен быть.

При отображении кортежа-одиночки Python включает запятую, чтобы напомнить вам, что это кортеж:

>>> print(t)
(2,)

Назначение, упаковка и распаковка кортежей

Как вы уже видели выше, литеральный кортеж, содержащий несколько элементов, может быть присвоен одному объекту:

>>> t = ('foo', 'bar', 'baz', 'qux')

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

tuple packing Упаковка кортежей

>>> t
('foo', 'bar', 'baz', 'qux')
>>> t[0]
'foo'
>>> t[-1]
'qux'

Если этот "упакованный" объект впоследствии присваивается новому кортежу, отдельные элементы "распаковываются" в объекты кортежа:

tuple unpacking

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

>>> (s1, s2, s3, s4) = t
>>> s1
'foo'
>>> s2
'bar'
>>> s3
'baz'
>>> s4
'qux'

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

>>> (s1, s2, s3) = t
Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>
    (s1, s2, s3) = t
ValueError: too many values to unpack (expected 3)

>>> (s1, s2, s3, s4, s5) = t
Traceback (most recent call last):
  File "<pyshell#17>", line 1, in <module>
    (s1, s2, s3, s4, s5) = t
ValueError: not enough values to unpack (expected 5, got 4)

Упаковка и распаковка могут быть объединены в одном операторе для создания составного присваивания:

>>> (s1, s2, s3, s4) = ('foo', 'bar', 'baz', 'qux')
>>> s1
'foo'
>>> s2
'bar'
>>> s3
'baz'
>>> s4
'qux'

Опять же, количество элементов в кортеже слева от присваивания должно быть равно количеству справа:

>>> (s1, s2, s3, s4, s5) = ('foo', 'bar', 'baz', 'qux')
Traceback (most recent call last):
  File "<pyshell#63>", line 1, in <module>
    (s1, s2, s3, s4, s5) = ('foo', 'bar', 'baz', 'qux')
ValueError: not enough values to unpack (expected 5, got 4)

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

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

>>> x1, x2, x3 = t
>>> x1, x2, x3
(1, 2, 3)

>>> x1, x2, x3 = 4, 5, 6
>>> x1, x2, x3
(4, 5, 6)

>>> t = 2,
>>> t
(2,)

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

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

>>> a = 'foo'
>>> b = 'bar'
>>> a, b
('foo', 'bar')

>>># We need to define a temp variable to accomplish the swap.
>>> temp = a
>>> a = b
>>> b = temp

>>> a, b
('bar', 'foo')

В Python подмена может быть выполнена с помощью одного присваивания кортежа:

>>> a = 'foo'
>>> b = 'bar'
>>> a, b
('foo', 'bar')

>>># Magic time!
>>> a, b = b, a

>>> a, b
('bar', 'foo')

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

Заключение

В этом уроке рассмотрены основные свойства списков и кортежей в Python, а также способы работы с ними. Вы будете широко использовать их в программировании на Python.

Одной из главных характеристик списка является то, что он упорядочен. Порядок элементов в списке является неотъемлемым свойством этого списка и не меняется, если только сам список не изменяется. (То же самое относится и к кортежам, только их, конечно, нельзя изменять.)

Следующий урок познакомит вас с Python dictionary: составным неупорядоченным типом данных. Читайте дальше!

Вернуться на верх