К содержанию

Модуль 4 · Урок 4.2

Списки

Изменяемая последовательность: методы списка, проход по значениям и индексам, ссылки и копии, split и join.

7 задач

В сквозной задаче числа пока были отдельными именами или вводились по одному. Для тысячи измерений нужна одна переменная на все. Список хранит много значений под одним именем и, в отличие от строки, меняется на месте. Отсюда и удобство, и главная ловушка: присваивание списка не копирует его. В уроке о значениях модель «имя ссылается на объект» была уточнением в «Тонкостях» — со списками без неё не обойтись.

Под рукой всё время будет список [4, 7, 2]: будем его менять, обходить, копировать и собирать из текста.

Список

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

temperatures = [18, 21, 19, 23]print(temperatures[1])print(len(temperatures))
21 4

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

numbers = [4, 7, 2]numbers[1] = 10numbers.append(8)print(numbers)
[4, 10, 2, 8]

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

Остальные действия со списком — каждый пример начинается с того же a = [4, 7, 2]:

append
a.append(5): a → [4, 7, 2, 5]один элемент в конец
extend
a.extend([5, 6]): a → [4, 7, 2, 5, 6]элементы источника по одному
insert
a.insert(1, 9): a → [4, 9, 7, 2]перед позицией 1
pop
a.pop(): вернул 2, a → [4, 7]удаляет и возвращает последний; a.pop(i) — элемент на позиции i
remove
a.remove(7): a → [4, 2]первое равное значение; ошибка, если его нет
del
del a[0]: a → [7, 2]удаляет по индексу
clear
a.clear(): a → []удаляет все элементы
reverse
a.reverse(): a → [2, 7, 4]разворачивает на месте
index
a.index(7) → 1, список не изменилсяиндекс первого равного; ошибка, если нет
count
a.count(7) → 1, список не изменилсясколько раз встречается

Методы вроде append, extend и reverse меняют список и возвращают None. Поэтому a = a.append(5) теряет связь имени a со списком: результат метода — None.

a = [4, 7, 2]a = a.append(5)print(a)
None

Обход списка

Список обходят по значениям, когда нужны элементы, и по индексам, когда нужны позиции.

a = [4, 7, 2]for value in a:    print(value) for i in range(len(a)):    a[i] *= 2print(a)
4 7 2 [8, 14, 4]

Первый вариант удобен, когда нужны сами элементы. Второй — когда нужны позиции, соседние значения или замена элементов. Инструкция value *= 2 в первом цикле с целыми числами изменила бы только привязку имени value, а не список.

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

Два имени — один список

Присваивание не копирует список: два имени смотрят на один объект. Нажми «Запуск» и пройди пример по шагам: после b = a обе стрелки ведут к одному списку.

a = [1, 2]b = ab.append(3)print(a)
[1, 2, 3]
После b = a оба имени ведут к одному списку. b.append(3) меняет этот список, поэтому изменение видно и через a.

a и b — два имени одного объекта, поэтому append через b меняет то, что видно через a. Чтобы создать отдельный внешний список, используют a.copy(), a[:] или list(a).

a = [1, 2]b = a.copy()b.append(3)print(a)print(b)print(a == b)print(a is b)
[1, 2] [1, 2, 3] False False
После b = a.copy() у каждого имени свой список: append через b не трогает список a.

== сравнивает содержимое, is — один ли это список. Два разных списка могут быть равны по содержимому: сразу после копии a == b было бы True, а a is b — False. Сравнивай значения через ==; is нужен для value is None и для вопроса «это тот же список?».

Тонкости:is для чисел и строк

Python иногда хранит одинаковые небольшие числа и короткие строки одним объектом, а иногда — разными. Поэтому is для чисел и строк может давать разные ответы в разных программах; полагаться на это нельзя. Значения сравнивают только через ==.

Копия и вложенные списки

Копия внешнего списка не копирует вложенные списки. Список может содержать другие списки: так удобно представлять таблицу. Но копирование внешнего списка не копирует автоматически его внутренние элементы.

a = [[1, 2], [3, 4]]b = a.copy()b[0][0] = 99print(a)
[[99, 2], [3, 4]]

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

Особенно опасна запись:

table = [[0] * 3] * 2table[0][0] = 9print(table)
[[9, 0, 0], [9, 0, 0]]
[[0] * 3] * 2: обе ячейки внешнего списка ссылаются на одну внутреннюю строку, поэтому изменение table[0][0] видно в обеих.

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

table = []for row in range(2):    table.append([0] * 3)table[0][0] = 9print(table)
[[9, 0, 0], [0, 0, 0]]
Строки, созданные в цикле отдельно: у каждой ячейки внешнего списка своя внутренняя строка, и изменение первой не затрагивает вторую.

[0] * 3 безопасно для последующей замены отдельных чисел: целые неизменяемы, а присваивание элементу заменяет ссылку. Проблема возникает при изменении повторённых вложенных объектов.

split и join

split превращает строку в список частей, join — список строк обратно в строку.

print("  10   20  30 ".split())print("a,,b".split(","))
['10', '20', '30'] ['a', '', 'b']

Без аргумента разделителями служат серии пробельных символов, лишние края исчезают. С явным разделителем правило другое: два разделителя подряд дают пустую часть. Поэтому .split() и .split(" ") не взаимозаменяемы.

Разберём несколько чисел из одной строки без сокращений:

line = "10 20 30"parts = line.split()numbers = []for part in parts:    numbers.append(int(part))print(numbers)
[10, 20, 30]

Обратное соединение строк выполняет join:

words = ["Учимся", "писать", "программы"]print(" ".join(words))
Учимся писать программы

Объект перед точкой — разделитель. Элементы должны быть строками: целые сначала преобразуют. Для последовательного вывода чисел позже пригодится также print(*numbers).

Новый список

Новый список подходящих элементов оставляет исходный нетронутым. Из [5, -2, 0, 8, -1] создадим отдельный список неотрицательных значений:

source = [5, -2, 0, 8, -1]result = []for value in source:    if value >= 0:        result.append(value)print(result)print(source)
[5, 0, 8] [5, -2, 0, 8, -1]

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

Сквозная задача

Сквозная задача курса: числа лежат в списке. Раньше числа 5, −2, 10, 7 были четырьмя именами, потом вводились по одному. Список хранит их все под одним именем, а for проходит по ним без ручного счётчика:

numbers = [5, -2, 10, 7]total = 0best = numbers[0]for x in numbers:    total += x    if x > best:        best = xprint(total, best)
20 10

Внутри цикла — те же две строки, что в уроке о while. Изменилось только то, откуда берутся числа.

Итог

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

  • Список[4, 7, 2]: индексы и срезы — как у строк, но элементы можно менять: a[i] = x.
  • Методыappend, insert, pop, remove, reverse меняют список и возвращают None: не пиши a = a.append(x).
  • Обходfor value in a — по значениям; for i in range(len(a)) — когда нужны позиции или замена.
  • b = aНе копия: два имени одного списка. Копия — a.copy(), a[:], list(a).
  • == и is== — равное содержимое, is — тот же список.
  • ТаблицаСтроки создают отдельно (циклом), а не [[0] * 3] * 2.
  • split и joinsplit() режет строку на части, " ".join(words) склеивает строки.

Проверьте себя

Ответь сначала сам, потом открой ответ.

Вопрос 1

Что напечатает программа?

a = [1, 2]b = ab.append(3)print(a)
ОтветСкрыть ответ
Ответ

[1, 2, 3]: b = a не копирует список, оба имени ведут к одному.

Вопрос 2

Почему цикл for value in a: value *= 2 не меняет список a? Как удвоить элементы?

ОтветСкрыть ответ
Ответ

value *= 2 связывает с новым числом только имя value. Нужен перебор индексов: for i in range(len(a)): a[i] *= 2.

Вопрос 3

Что напечатает print([4, 7, 2].append(5))?

ОтветСкрыть ответ
Ответ

None: append меняет список и возвращает None.

Вопрос 4

Что дадут "3;8;;1".split(";") и " 3 8 1 ".split()?

ОтветСкрыть ответ
Ответ

['3', '8', '', '1'] и ['3', '8', '1'].

Вопрос 5

Что даст "-".join(["2026", "10", "10"])? Что случится с "-".join([2026, 10, 10])?

ОтветСкрыть ответ
Ответ

2026-10-10; с числами — TypeError: элементы должны быть строками.

Практика

Для проверки ответов нужен JavaScript. Условия, подсказки и решения доступны ниже.

Напиши программу: неотрицательные значения

Напиши программу: из списка [3, -4, 0, 6, -2] она создаёт отдельный список неотрицательных значений и печатает его; исходный список не меняется. Запусти её и запиши, что она напечатала.

Запишите список так, как его печатает Python.

Что напечатает программа?

a = [3, 1, 4]a.append(1)a.insert(0, 9)x = a.pop()a.remove(1)print(a)

Запишите список так, как его печатает Python.

Что напечатает программа?

a = [1, 2, 3]b = ab[0] = 10print(a)

Запишите список так, как его печатает Python.

Что напечатает программа?

a = [1, 2]b = [1, 2]print(a == b)print(a is b)

Запишите напечатанные строки через пробел в порядке вывода.

Что напечатает программа?

t = [[0] * 2] * 3t[1][0] = 5print(t)

Запишите список так, как его печатает Python.

Что напечатает программа?

a = [5, 2, 8]a = a.reverse()print(a)

Запишите напечатанное значение.

Напиши программу: отрицательные и сумма

Напиши программу: строка "4 -1 7 0 -3" превращается в список целых, затем программа печатает количество отрицательных чисел и сумму всех. Запусти её и запиши, что она напечатала.

Запишите два числа через пробел: количество отрицательных, затем сумму.