Списки

Что это?

Список в Python это:

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

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

Создание списка

Список можно создать при помощи литералов. Литерал - это код, который используется для создания объекта "вручную". Например, некоторые литералы уже изученных ранее объектов:

  • int: 5, -23
  • float: 5., 5.0, -10.81, -1.081e1
  • str: 'ABCdef', "ABCdef"

В случае списка литералом являются квадратные скобки [], внутри которых через запятую , перечисляются элементы списка:

>>> []
[]
>>> [0, 1, 2, 3, 4]
[0, 1, 2, 3, 4]
>>> ['sapere', 'aude']
['sapere', 'aude']
>>> ['Gravitational acceleration', 9.80665, 'm s^-2']
['Gravitational acceleration', 9.80665, 'm s^-2']
>>> type([])
<class 'list'>

Генератор списков

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

>>> arithm = [ x for x in range(10) ]
>>> squares = [ x**2 for x in range(10) ]
>>> arithm
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

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

Метод list.append

Метод списков list.append (англ. добавить в конец), как следует из перевода, добавляет элемент в конец списка.

В примере ниже инициализируется пустой список fibs, а затем заполняется элементами:

>>> fibs = []
>>> fibs.append(1)
>>> fibs
[1]
>>> fibs.append(1)
>>> fibs
[1, 1]
>>> fibs.append(2)
>>> fibs
[1, 1, 2]
>>> fibs.append(3)
>>> fibs
[1, 1, 2, 3]

Функция list

Аналогично функциям преобразования типов int(), float(), str() существует функция list(), создающая список из итерируемого объекта. Её можно использовать, например, для создания списка символов из строки:

>>> list("sapere aude")
['s', 'a', 'p', 'e', 'r', 'e', ' ', 'a', 'u', 'd', 'e']
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Элементы списка: доступ и изменение

Для доступа к элементам списка используется операция взятия элемента по индексу. Для этого рядом с литералом или переменной списка необходимо подписать индекс элемента в квадратных скобках:

>>> ['Gravitational acceleration', 9.80665, 'm s^-2'][0]
'Gravitational acceleration'
>>> ['Gravitational acceleration', 9.80665, 'm s^-2'][1]
9.80665
>>> ['Gravitational acceleration', 9.80665, 'm s^-2'][2]
'm s^-2'
>>> l = [10, 20, 30]
>>> l[0]
10
>>> l[1]
20
>>> l[2]
30

Нумерация элементов списка начинается с нуля.

При запросе элемента по несуществующему индексу, происходит ошибка IndexError:

>>> l
[10, 20, 30]
>>> l[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

Поэтому всегда нужно быть уверенным, что индексация не выходит за пределы длины списка. Получить её можно, как и для строк, с помощью функции len():

>>> l
[10, 20, 30]
>>> len(l)
3
>>> l[len(l) - 1]
30

Последняя конструкция встречается нередко, поэтому в Python существует возможность взять элемент по отрицательному индексу:

>>> l
[10, 20, 30]
>>> l[-1]
30
>>> l[-2]
20
>>> l[-3]
10
>>> l[-4]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

Таким образом для индекса n ≥ 0, l[-n] эвивалентно l[len(l) - n].

Изменение элементов

Изменение элементов осуществляется с помощью присваивания:

>>> l = [10, 20, 30]
>>> l
[10, 20, 30]
>>> l[0] = 0
>>> l
[0, 20, 30]
>>> l[2] = 55
>>> l
[0, 20, 55]

Доступ в цикле while

>>> l
[0, 20, 55]
>>> i = 0
>>> while i < len(l):
...     print(i, l[i])
...     i += 1
...
0 0
1 20
2 55
>>>

Доступ в цикле for

Наиболее универсальный способ это использование генератора range:

>>> l
[0, 20, 55]
>>> for i in range(len(l)):
...     print(i, l[i])
...
0 0
1 20
2 55

Питонизмы

Конструкции с использованием while и for, изложенные выше, имеют аналоги практически во всех языках программирования. Они универсальны, стандартны, переносимы из языка в язык.

Этот раздел относится только к особенностям языка Python.

Не злоупотребляйте питонизмами, наша цель - освоить алгоритмы и структуры данных, а не Python.

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

for item in any_iterable:
    #  тело цикла

Здесь item это выбранное программистом имя переменной итерирования, которая доступна в теле цикла. В начале каждой итерации в эту переменную помещается значение из any_iterable. Под any_iterable может стоять любой итерируемый объект.

Знакомые нам примеры итерируемых объектов:

  • range - генератор арифметической последовательности, for "просит" новые значения у генератора, пока те не закончатся
  • str - строковый тип, итерирование происходит по символам
  • list - список, итерирование происходит по элементам

Таким образом, "pythonic way" пробега по списку может выглядеть так:

>>> l
[0, 20, 55]
>>> for elem in l:
...     print(elem)
...
0
20
55

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

Под подобные мелкие задачи существует множество "питонизмов" - специфических для языка Python инструментов.

Один из примеров - enumerate - позволяет программисту получить в цикле индекс итерации (!) и сам элемент.

При таком использовании номер итерации совпадает с индексом элемента:

>>> l
[0, 20, 55]
>>> for i, elem in enumerate(l):
...     print(i, elem)
...
0 0
1 20
2 55

Код приведённый для enumerate выше, аналогичен универсальным:

>>> l
[0, 20, 55]
>>> for i in range(len(l)):
...     elem = l[i]
...     print(i, elem)
...
0 0
1 20
2 55
>>> l
[0, 20, 55]
>>> i = 0
>>> while i < len(l):
...     elem = l[i]
...     print(i, elem)
...     i += 1
...
0 0
1 20
2 55

Соединение и копирование списков

Списки можно соединять in place, т.е. перезаписывая, с помощью метода list.extend:

>>> a
[0, 1, 2]
>>> b
[3, 4, 5]
>>> a.extend(b)
>>> a
[0, 1, 2, 3, 4, 5]
>>> b
[3, 4, 5]

Или соединять, создавая новый список из исходных:

>>> a
[0, 1, 2]
>>> b
[3, 4, 5]
>>> c = a + b
>>> c
[0, 1, 2, 3, 4, 5]

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

>>> a
[0, 1, 2]
>>> b = a
>>> b
[0, 1, 2]
>>> b[0] = 123
>>> a
[123, 1, 2]
>>> b
[123, 1, 2]

В строчке b = a лишь создаётся ещё одна ссылка на объект [0, 1, 2], которая присваивается переменной b. В итоге a и b будут указывать на один и тот же объект.

Чтобы создать копию, необходимо поэлементно создать новый список из исходного. Например, с помощью функции list():

>>> a = [0, 1, 2]
>>> b = list(a)
>>> a
[0, 1, 2]
>>> b
[0, 1, 2]
>>> b[0] = 123
>>> a
[0, 1, 2]
>>> b
[123, 1, 2]

Ремарка о строках

На самом деле, мы уже ранее сталкивались со списками в предудыщих работах, когда использовали str.split:

>>> s = "ab cd ef1 2 301"
>>> s.split()
['ab', 'cd', 'ef1', '2', '301']

Т.е. str.split, по умолчанию, разбивает строку по символам пустого пространства (пробел, табуляция) и создаёт список из получившихся "слов".

Загляните в help(str.split), чтобы узнать, как изменить такое поведение, и разбивать строку, например, по запятым, что является стандартом для представления таблиц в файлах csv (comma separated values).

Методом, являющимся обратным к операции str.split является str.join. Он "собирает" строку из списка строк:

>>> s
'ab cd ef1 2 301'
>>> l = s.split()
>>> l
['ab', 'cd', 'ef1', '2', '301']
>>> l[-1] = '430'
>>> l
['ab', 'cd', 'ef1', '2', '430']
>>> ','.join(l)
'ab,cd,ef1,2,430'
>>> ' -- '.join(l)
'ab -- cd -- ef1 -- 2 -- 430'