Основы программирования в Python

Алла Тамбовцева, НИУ ВШЭ

Списки в Python и функция range()

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

In [1]:
nums = [5, 6, 8, 1]
nums
Out[1]:
[5, 6, 8, 1]
In [2]:
names = ["Anna", "Fred", "Tina"]
names
Out[2]:
['Anna', 'Fred', 'Tina']
In [3]:
mixed = ["Anna", 10, "Fred", 7]
mixed
Out[3]:
['Anna', 10, 'Fred', 7]

Изменяемость и неизменяемость + метод .append()

Важная особенность списков заключается в том, что они являются изменяемой (mutable) структурой данных. Это означает, что список можно изменять, не переопределяя переменную, в которой этот список сохранен. Чтобы понять, о чём речь, давайте посмотрим сначала на примеры неизменяемых (immutable) объектов в Python. Начнём с целых чисел. Если у нас есть переменная a, и в ней сохранено целое число, мы сможем изменить её значение только создав новую переменную с таким же названием:

In [4]:
a = 3
a = a + 1  # новое значение
a
Out[4]:
4

Другими словами, у нас нет никакого иного способа, который можно было бы применить, чтобы, например, увеличить значение a, не переопределяя её через оператор =. То же будет и со строками.

In [5]:
s = "питон греется на солнышке"

Теперь попробуем сделать первую букву заглавной:

In [6]:
s.capitalize() 
Out[6]:
'Питон греется на солнышке'

Но сама строка пока не изменилась – всё потому, что строка является объектом неизменяемого типа:

In [7]:
s
Out[7]:
'питон греется на солнышке'

Чтобы сохранить изменения, нужно переопределить переменную s:

In [8]:
s = s.capitalize()
s
Out[8]:
'Питон греется на солнышке'

К неизменяемым объектам в Python относятся:

  • целые числа (int);
  • числа с плавающей точкой (float);
  • строки (str);
  • логические значения (bool);
  • кортежи (tuples).

Теперь посмотрим на изменяемые объекты. К изменяемым объектам относятся:

  • списки (list);
  • словари (dictionary);
  • множества (set).

Есть список M:

In [9]:
M = [1, 9, 4] 

Заменим первый элемент на 100:

In [10]:
M[0] = 100
M
Out[10]:
[100, 9, 4]

Изменения сохранились, но при этом значение переменной M не пришлось переопределять заново. Теперь припишем в конец списка M ещё один элемент – для это применим метод .append() :

In [11]:
M.append(200)
M
Out[11]:
[100, 9, 4, 200]

Список M изменился, но присваивание с = мы нигде не использовали, мы изменили список «как есть». Более того, если мы исполним что-то такое M = M.append(200), мы получим неожиданный результат:

In [12]:
M = M.append(200)
print(M)
None

Результат None. Почему? Потому что метод .append() сам по себе не возвращает никакого результата (результат пуст, имеет тип None), он «молча» записывает элемент в конец списка.

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

In [13]:
a = 3
b = a
b = b + 1
print(a, b)
3 4

В примере выше мы скопировали значение a в переменную b, значение b изменили, а значение a изменений не претерпело.

С изменяемыми объектами это бы не сработало. Вернемся к спискам.

In [14]:
L = [1, 6, 7]
L2 = L
L2.append(10)
print(L, L2) 
[1, 6, 7, 10] [1, 6, 7, 10]

Несмотря на то, что список L мы не трогали, он изменился точно так же, как и список L2! Что произошло? На самом деле, когда мы записали L2 = L, мы скопировали не сам список, а ссылку на него. Другими словами, проводя аналогию с папкой и ярлыком, вместо того, чтобы создать новую папку L2 с элементами, такими же, как в L, мы создали ярлык L2, который сам по себе ничего не содержит, а просто ссылается на папку L.

Так как же тогда копировать списки? Во-первых, у списков есть метод .copy().

In [15]:
L = [1, 6, 7]
L2 = L.copy() 
L2.append(10)
print(L, L2) 
[1, 6, 7] [1, 6, 7, 10]

Во-вторых, можно сделать полный срез срез и «срезать» весь список:

In [16]:
L = [1, 6, 7]
L2 = L[:]
L2.append(10)
print(L, L2) 
[1, 6, 7] [1, 6, 7, 10]

Метод .extend() vs метод .append()

Метод .append() используется, если в конец списка нужно приписать один элемент. А что, если нужно добавить сразу несколько элементов? Метод .append() уже не подойдёт:

In [17]:
a = [7, 2, 1, 8]
a.append(3, 4) 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-17-20f19072f5fc> in <module>
      1 a = [7, 2, 1, 8]
----> 2 a.append(3, 4)

TypeError: append() takes exactly one argument (2 given)

А если объединить элементы в список, метод .append() сработает, но результат будет отличаться от ожидаемого:

In [18]:
a.append([3, 4])  # список [3, 4] как отдельный элемент
a
Out[18]:
[7, 2, 1, 8, [3, 4]]

Поэтому в таком случае нужно использовать особый метод, метод .extend():

In [19]:
a = [7, 2, 1, 8]
a.extend([3, 4]) 
a
Out[19]:
[7, 2, 1, 8, 3, 4]

Результат выше совпадает с тем, что мы бы получили, «склеивая» два списка через +:

In [20]:
a = [7, 2, 1, 8]
a + [3, 4] 
Out[20]:
[7, 2, 1, 8, 3, 4]

Функция range()

В Python есть функция range(), которая позволяет перебирать целые числа на заданном промежутке, не создавая при этом сам список чисел.

In [21]:
# пример

for j in range(0, 6):
    print(j)
0
1
2
3
4
5

Правый конец заданного в range() промежутка не включается, будьте бдительны. В примере выше на экран были выведены числа от 0 до 5, число 6 включено не было.

Если мы хотим посмотреть на то, какие значения будут в range(), придется превратить его в список:

In [22]:
range(0, 3) # не поспоришь, но бесполезно
Out[22]:
range(0, 3)
In [23]:
list(range(0, 3)) # значения внутри range
Out[23]:
[0, 1, 2]

Полезный факт: если нас интересуют числа на промежутке, начиная с нуля, в range() левый конец можно не указывать, 0 будет выбран по умолчанию.

In [24]:
list(range(5))
Out[24]:
[0, 1, 2, 3, 4]

Еще один полезный факт: шаг (разность между последующими элементами) для последовательности целых чисел можно выставить самостоятельно. Для этого достаточно дописать третье число – значение шага – внутри функции range():

In [25]:
list(range(0, 18, 2))  # шаг 2
Out[25]:
[0, 2, 4, 6, 8, 10, 12, 14, 16]
In [26]:
list(range(0, 18, 3))  # шаг 3
Out[26]:
[0, 3, 6, 9, 12, 15]