for
¶Создадим список age
из значений возраста респондентов. Элементы списка перечисляются в квадратных скобках через запятую:
age = [25, 35, 48, 20]
print(age)
[25, 35, 48, 20]
Список может содержать элементы любого типа, необязательно числового. Например, мы можем создать список имён name
, полностью состоящий из строк:
name = ["Ann", "Nick", "Ben", "George", "James"]
print(name)
['Ann', 'Nick', 'Ben', 'George', 'James']
А можем создать список, состоящий из элементов разных типов. Представим, что не очень сознательный исследователь закодировал пропущенные значения в списке текстом, написав «нет ответа»:
mixed = [23, 25, "no answer", 32]
print(mixed)
[23, 25, 'no answer', 32]
Элементы разных типов спокойно уживаются в списке: Python не меняет тип элементов. Все элементы, которые являются строками, останутся строками, а числа – числами. Список может иметь более сложную структуру, например, представлять собой список списков:
L = [[1, 2, 3], [4, 5]]
print(L)
[[1, 2, 3], [4, 5]]
У списка всегда есть длина – количество элементов в нём. Длина определяется с помощью функции len()
.
len(age) # четыре элемента
4
Если список пустой, то, как несложно догадаться, его длина равна нулю:
empty = []
len(empty)
0
Раз список состоит из элементов, к ним можно обращаться по отдельности. Главное, нужно помнить, что нумерация в Python начинается с нуля, а не с единицы. Существует несколько обоснований, почему это так, с одним из них мы познакомимся чуть позже, когда будем обсуждать срезы.
age[0] # первый элемент age
25
Порядковый номер элемента в списке называется индексом. Далее, чтобы не путаться, будем разделять термины: порядковые числительные останутся для обозначения номера элемента в нашем обычном понимании, а индексы – для обозначения номера элемента в Python. Например, если нас будет интересовать элемент 35 из списка age
, мы можем сказать, что нас интересует второй элемент или элемент с индексом 1:
print(age)
print(age[1])
[25, 35, 48, 20] 35
Если элемента с интересующим нас индексом в списке нет, Python выдаст ошибку, а точнее, исключение, под названием IndexError
.
age[5]
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-9-6dda8ec0be36> in <module> ----> 1 age[5] IndexError: list index out of range
А как обратиться к последнему элементу списка, да так, чтобы код был универсальным – работал и в случае, когда мы изменим длину списка? Давайте подумаем. Длина списка age
, как мы уже убедились, равна 4, но нумерация самих элементов начинается с нуля. Поэтому:
age[len(age)-1] # последний элемент - 20
20
Конечно, в том, что нумерация элементов в списке начинается с нуля, есть некоторое неудобство – индекс последнего элемента не совпадает с длиной списка. Но, на самом деле, обращаться к последнему элементу списка можно и по-другому: считать элементы с конца!
age[-1] # последний элемент - он же первый с конца
20
Отрицательные индексы элементов в Python – абсолютно нормальная вещь. Можем так же получить второй элемент с конца:
age[-2]
48
Список – изменяемый тип в Python (mutable). Это означает, что список можно изменять, не перезаписывая его, то есть не создавая новую переменную с тем же названием. Для сравнения: объекты, с которыми мы работали ранее, были неизменяемыми. Для того, чтобы перезаписать значение числовой переменной x
, нужно было явно задействовать оператор присваивания =
:
# было 2
x = 2
# стало 3
x = x + 1
print(x)
3
Со списками всё обстоит иначе: мы можем обращаться к уже существующему списку, выбирать в нём элемент и заменять его, не перезаписывая весь список «с нуля». Например, заменим последний элемент списка age
на число 30:
age[-1] = 30
print(age)
[25, 35, 48, 30]
А ещё можно дописывать элементы в конец списка. Для этого существует два метода: .append()
и .extend()
. Метод .append()
используется для присоединения одного элемента, .extend()
– для добавления целого списка.
age.append(27) # добавили 27
print(age)
[25, 35, 48, 30, 27]
age.extend([43, 33]) # добавили 43 и 33
print(age)
[25, 35, 48, 30, 27, 43, 33]
Важный момент: методы .append()
и extend()
, да и почти все методы, которые затрагивают исходный список, молча вносят изменения в сам список, а не возвращают его обновлённую копию. Возвращают они пустое значение None
, поэтому использовать одновременно, например, .append()
и =
для изменения списка – ошибочное решение:
# якобы добавляем 90 и сохраняем обновленный список в age2
age2 = age.append(90)
print(age2) # но нет
None
Методы .append()
и .extend()
приписывают значения только в конец списка. Для добавления элементов в любое другое место существует метод .insert()
, он «втискивает» элемент на место с указанным индексом:
print(age) # до
age.insert(3, 29) # добавили 29 четвертым элементом (индекс 3)
print(age) # после
[25, 35, 48, 30, 27, 43, 33, 90] [25, 35, 48, 29, 30, 27, 43, 33, 90]
Также списки можно склеивать, то есть использовать операцию, которая называется конкатенацией. В этом смысле списки очень похожи на строки, и для их конкатенации тоже используется знак +
(история про перегрузку операторов, когда одни и те же операторы выполняют разные операции на разных типах, мы уже такое обсуждали):
L = [4, 5, 6] + [7, 8, 9]
print(L)
[4, 5, 6, 7, 8, 9]
Запись через +
кажется очень интуитивной и заманчивой, но не стоит ей часто пользоваться, когда списки очень большие и их много. При такой конкатенации списков происходит создание нового списка, который «склеивается» из отдельных частей, чего не происходит при использовании .extend()
: там элементы просто дописываются в уже существующий список. Поэтому приписывание одного списка в конец другого быстрее и эффективнее делать именно через .extend()
.
На данный момент мы достаточно хорошо познакомились со списками. Но списки не так просты, как кажется. И это снова связано с тем, что список – изменяемый тип. Давайте попробуем сделать следующее: скопировать один список в другой путем присваивания.
age2 = age # сохранили список L1 в L2
print(age, age2)
[25, 35, 48, 29, 30, 27, 43, 33, 90] [25, 35, 48, 29, 30, 27, 43, 33, 90]
Пока все ожидаемо. А теперь допишем в age2
элемент 18:
age2.append(18)
И сравним оба списка:
print(age, age2)
[25, 35, 48, 29, 30, 27, 43, 33, 90, 18] [25, 35, 48, 29, 30, 27, 43, 33, 90, 18]
Несмотря на то, что список age
мы не трогали, он изменился точно так же, как и список age2
! Что произошло? На самом деле, когда мы записали age2 = age
, мы скопировали не сам список, а ссылку на него. Другими словами, проводя аналогию с папкой и ярлыком, вместо того, чтобы создать новую папку age2
с элементами, такими же, как в age
, мы создали ярлык age2
, который сам по себе ничего не представляет, а просто ссылается на папку age
.
Так как же тогда копировать списки? Можно воспользоваться методом .copy()
.
age2 = age.copy()
age2.append(18)
print(age)
print(age2)
[25, 35, 48, 29, 30, 27, 43, 33, 90, 18] [25, 35, 48, 29, 30, 27, 43, 33, 90, 18, 18]
Мы уже познакомились с тем, как выбирать отдельные элементы из списка, однако мы ещё не обсудили, как выбирать несколько элементов подряд. Такие части списков называются срезами (slices). Индексы элементов, которые должны войти в срез, указываются в квадратных скобках, через двоеточие (начало
: конец
).
age[2:5]
[48, 29, 30]
Важно: правый конец не включается в срез! В срез выше вошли элементы с индексами 2, 3, 4, элемент с индексом 5 включён не был.
Если мы хотим задать только начало или конец среза, один из индексов легко можно опустить:
print(age[2:])
print(age[:5])
[48, 29, 30, 27, 43, 33, 90, 18] [25, 35, 48, 29, 30]
Тут мы подходим к тому, почему нумерация элементов в Python начинается с нуля. В частности, для удобных срезов. Если нам нужны первые два элемента списка, нам не нужно долго думать и сдвигать номера элементов на единицу, достаточно просто написать, например, age[:2]
.
Можно ли сделать срез, который будет включать в себя весь список? Легко!
age[:] # опускаем все индексы
[25, 35, 48, 29, 30, 27, 43, 33, 90, 18]
Срезы можно задействовать и для изменения списка:
print(age)
age[1:3] = [28, 26] # заменим элементы с индексами 1 и 2
print(age)
[25, 35, 48, 29, 30, 27, 43, 33, 90, 18] [25, 28, 26, 29, 30, 27, 43, 33, 90, 18]
Длина списка, на который мы заменяем срез, не обязательно должна совпадать с длиной среза. Можно взять список с большим числом элементов, тогда исходный список расширится, а можно с меньшим – список сузится. Замены остальных элементов при этом не произойдет, новый срез просто «вклинится» в середину списка.
age[1:3] = [18, 32, 45]
print(age)
[25, 18, 32, 45, 29, 30, 27, 43, 33, 90, 18]
for
¶Раз есть списки, хочется научиться «пробегаться» по их элементам. Например, выводить на экран не весь список age
сразу, а постепенно, каждый элемент с новой строки. Для этого существуют циклы. Они позволяют выполнять одну и ту же операцию или набор операций несколько раз, не копируя один и тот же код и не запуская его заново.
Рассмотрим цикл for
. Создадим список nums
и последовательно выведем его элементы на экран:
nums = [1, 10, 23, -8, 6]
for i in nums:
print(i)
1 10 23 -8 6
Как устроен цикл выше? Кодом выше мы доносим до Python мысль: пробегайся по всем элементам списка age
и выводи каждый элемент на экран. Вообще любой цикл for
имеет такую структуру: сначала указывается, по каким значениям нужно пробегаться, а потом, что нужно делать. Действия, которые нужно выполнить в цикле, указываются после двоеточия в for
– эта часть назвается телом цикла.
Буквы в конструкции for
могут быть любые, совсем необязательно брать букву i
. Python сам поймёт, просто по синтаксису конструкции, что мы имеем в виду, запуская цикл.
# element вместо i
for element in nums:
print(element)
1 10 23 -8 6
Можем выводить не сами элементы, а, например, результаты возведения их в квадрат:
for i in nums:
print(i, i ** 2)
1 1 10 100 23 529 -8 64 6 36
Давайте, используя цикл, создадим новый список на основе старого.
squares = [] # пока пустой список
for n in nums:
squares.append(n ** 2) # постепенно записываем элементы
print(squares)
[1, 100, 529, 64, 36]
Конечно, циклы нужны не только для того, чтобы работать со списками. С помощью циклом можно решить любую задачу, которая требует повторения одинаковых действий. Рассмотрим такую задачу.
С приходом весны питон решил каждый день выползать погреться на солнышко. Однако он знал, что солнце весной довольно активное, и поэтому разработал такую схему: в первый день он греется одну минуту, а в каждый последующий день увеличивает время пребывания на солнце на 3 минуты. Нам нужно написать код, который позволит вычислять, сколько минут питон будет тратить на солнечные ванны в некоторый выбранный день.
# создадим список с номерами дней
# зафиксируем начальное значение времени t = 1 минута
days = [2, 3, 4, 5, 6, 7]
t = 1
print(1, t)
# теперь будем обновлять значение t в цикле
# и выводить на экран номер дня и время
for day in days:
t = t + 3
print(day, t)
1 1 2 4 3 7 4 10 5 13 6 16 7 19
range()
¶На самом деле, можно было поступить ещё проще. В Python есть функция range()
, которая позволяет перебирать целые числа на заданном промежутке, не создавая при этом сам список чисел.
range(0, 7)
range(0, 7)
Есть небольшая проблема: из-за того, что список с числами не создаётся и не занимает память, явно элементы внутри range()
мы не увидим. Можно преобразовать результат в список:
list(range(0, 7))
[0, 1, 2, 3, 4, 5, 6]
Правый конец заданного в range()
промежутка не включается, будьте бдительны. В примере выше на экран были выведены числа от 0 до 6, число 7 включено не было.
При использовании range()
в циклах преобразовывать результат в список уже не нужно, в примере выше мы просто хотели посмотреть на объект изнутри. Применим range()
к нашей задаче про питона:
t = 1
print(1, t)
for day in range(2, 8):
t = t + 3
print(day, t)
1 1 2 4 3 7 4 10 5 13 6 16 7 19
Полезный факт 1: если нас интересуют числа на промежутке, начиная с нуля, в range()
левый конец можно не указывать, 0 будет выбран по умолчанию.
list(range(5))
[0, 1, 2, 3, 4]
Полезный факт 2: внутри range()
можно указать любой целочисленный шаг для получения последовательности чисел.
# шаг 2, только чётные числа от 0 до 16, исключая 16
list(range(0, 16, 2))
[0, 2, 4, 6, 8, 10, 12, 14]
Полезный факт 3: шаг внутри range()
может быть отрицательным, тогда мы получим последовательность, отсортированную по убыванию. В таком случае сначала нужно указывать правый конец интервала, а потом – левый.
list(range(16, 0, -2))
[16, 14, 12, 10, 8, 6, 4, 2]
Если сначала указать меньшее значение, то мы получим пустой список. Это происходит потому, что мы даём Python противоречивые указания – range()
двигается всегда слева направо, а отрицательный шаг предполагает движение справа налево:
list(range(0, 16, -2))
[]