Одна из стандартных идиом при работе с последовательностями — перебор содержимого последовательности. Например, вы можете отфильтровать один из элементов, применить к элементам функцию, вывести элементы и т.д. Для решения этой задачи можно воспользоваться циклом for. В следующем примере выводятся все строки из списка:
>>> for letter in ['c', 'a', 't']:
... print(letter)
c
a
t
>>> print(letter)
t
ПРИМЕЧАНИЕ
Обратите внимание: конструкция цикла for содержит двоеточие (:), за которым следует код с отступом (блок цикла for).
В цикле for Python создает новую переменную letter для хранения текущего элемента. Обратите внимание: в letter хранится не индекс, а строка. Python не очищает переменную после завершения цикла.
В таких языках, как C, перебор в последовательностях ведется не по элементам последовательности, а по индексам. Используя индексы, вы можете извлекать элементы с указанными индексами. Ниже показан один из вариантов решения этой задачи с использованием встроенных функций Python range и len:
>>> animals = ["cat", "dog", "bird"]
>>> for index in range(len(animals)):
... print(index, animals[index])
0 cat
1 dog
2 bird
Приведенный выше фрагмент содержит код с душком: этот термин обычно подразумевает, что вы используете Python не так, как следовало бы. Обычно перебор в последовательностях выполняется для получения доступа к элементам последовательности, а не к индексам. Тем не менее в отдельных случаях индекс тоже бывает нужен. Python предоставляет встроенную функцию enumerate, с которой комбинация range и len становится излишней. Функция enumerate возвращает кортеж (индекс, элемент) для каждого элемента в последовательности:
>>> animals = ["cat", "dog", "bird"]
>>> for index, value in enumerate(animals):
... print(index, value)
0 cat
1 dog
2 bird
Так как кортеж содержит пару из индекса и значения, вы можете воспользоваться распаковкой кортежа для создания двух переменных, index и value, прямо в цикле for. Имена переменных должны разделяться запятой. Если длина кортежа совпадает с количеством переменных, включаемых в цикл for, Python создаст их за вас.
Рис. 15.1. Создание переменной в цикле for. Создается новая переменная letter; сначала она указывает на символ 'c', который выводится вызовом print. Затем цикл переходит на следующую итерацию, и переменная указывает на 'a'
Рис. 15.2. Цикл for продолжается, а после вывода 't' он завершается. В этот момент литерал списка уничтожается в ходе уборки мусора. Так как на 'c' и 'a' указывает только список, они тоже уничтожаются. Однако переменная letter продолжает указывать на 't'. Python не уничтожает эту переменную, и она продолжит существовать после завершения цикла for
Иногда бывает нужно прервать выполнение цикла преждевременно, до перебора всех элементов в цикле. Ключевое слово break прерывает ближайший внутренний цикл, в котором вы находитесь. Следующая программа суммирует числа до тех пор, пока не обнаружит отрицательное число. В этом случае выполнение цикла прерывается командой break:
>>> numbers = [3, 5, 9, -1, 3, 1]
>>> result = 0
>>> for item in numbers:
... if item < 0:
... break
... result += item
>>> print(result)
17
ПРИМЕЧАНИЕ
В строке
result += item
используется так называемое расширенное присваивание. Эта команда эквивалентна:
result = result + item
Расширенное присваивание выполняется чуть быстрее, так как выборка для переменной result выполняется всего один раз. Кроме того, команда получается более компактной и быстрее вводится.
ПРИМЕЧАНИЕ
Блок if внутри блока for снабжен отступом из восьми пробелов. Блоки могут быть вложенными, и каждый уровень должен иметь свой отступ.
Другая стандартная идиома циклов — пропуск отдельных элементов при переборе. Если выполнение тела цикла for занимает некоторое время, а выполняться оно должно только для некоторых элементов последовательности, вам пригодится ключевое слово continue. Команда continue приказывает Python прервать обработку текущего элемента цикла и продолжить цикл от начала блока for со следующим значением в цикле.
В следующем примере суммируются все положительные числа из списка:
>>> numbers = [3, 5, 9, -1, 3, 1]
>>> result = 0
>>> for item in numbers:
... if item < 0:
... continue
... result = result + item
>>> print(result)
21
Мы использовали команду in в цикле for. В языке Python эта команда также может использоваться для проверки принадлежности. Если вы хотите узнать, содержит ли список заданный элемент, используйте команду in для проверки:
>>> animals = ["cat", "dog", "bird"]
>>> 'bird' in animals
True
Если вам потребуется узнать индекс, используйте метод .index:
>>> animals.index('bird')
2
Как уже упоминалось ранее, списки являются изменяемыми. Изменяемость означает, что в них можно добавлять или удалять элементы. Кроме того, списки являются последовательностями, а значит, их содержимое можно перебирать.
Например, если вы захотите отфильтровать список имен так, чтобы в нем остался только элемент 'John' или 'Paul', делать это так было бы неправильно:
>>> names = ['John', 'Paul', 'George',
... 'Ringo']
>>> for name in names:
... if name not in ['John', 'Paul']:
... names.remove(name)
>>> print(names)
['John', 'Paul', 'Ringo']
Что произошло? Python предполагает, что списки не изменяются в процессе перебора. Добравшись до 'George', цикл удаляет имя из списка. Во внутренней реализации Python отслеживает текущий индекс цикла for. На этот момент в списке остаются только три элемента: 'John', 'Paul' и 'Ringo'. Однако цикл for думает, что текущей является позиция с индексом 3 (четвертый элемент), а четвертого элемента не существует, поэтому цикл останавливается, и элемент 'Ringo' остается на месте.
Существует два альтернативных решения для удаления элементов из списка в процессе перебора. В первом варианте удаляемые элементы отбираются при первом проходе по списку. Следующий цикл перебирает только те элементы, которые подлежат удалению (names_to_remove), и удаляет их из исходного списка (names):
>>> names = ['John', 'Paul', 'George',
... 'Ringo']
>>> names_to_remove = []
>>> for name in names:
... if name not in ['John', 'Paul']:
... names_to_remove.append(name)
>>> for name in names_to_remove:
... names.remove(name)
>>> print(names)
['John', 'Paul']
Другое решение — перебор по копии списка. Оно довольно легко реализуется конструкцией копирования среза [:], которая будет рассмотрена в главе, посвященной срезам:
>>> names = ['John', 'Paul', 'George',
... 'Ringo']
>>> for name in names[:]: # copy of names
... if name not in ['John', 'Paul']:
... names.remove(name)
>>> print(names)
['John', 'Paul']
Цикл for также может содержать блок else. Любой код в блоке else будет выполнен в том случае, если цикл for не достиг команды break. Следующий пример проверяет, являются ли числа из цикла положительными:
>>> positive = False
>>> for num in items:
... if num < 0:
... break
... else:
... positive = True
Команды continue не влияют на выполнение блока else.
Имя команды else выглядит несколько странно. Для цикла for она показывает, что была обработана вся последовательность. Блок else в цикле for часто применяется для обработки отсутствия элементов.
Python позволяет многократно выполнять блок кода, пока некоторое условие остается истинным. Такая конструкция называется циклом while, а для ее создания используется команда while. За циклом while следует выражение, результат которого равен True или False, а за выражением идет двоеточие. Помните, что следует за двоеточием (:) в Python? Да, блок кода с отступом. Этот блок продолжит выполняться, пока результат выражения остается равным True. В программе может легко возникнуть бесконечный цикл.
Бесконечные циклы обычно нежелательны, потому что ваша программа «зависает» в цикле без возможности выхода. Впрочем, у правила есть исключения: например, сервер в бесконечном цикле принимает и обрабатывает запросы. Другое исключение, встречающееся в коде Python более высокого уровня, — бесконечный генератор. Генератор ведет себя как отложенный список, который создает значения только тогда, когда они будут задействованы в переборе. Если вы знакомы с обработкой потоков, генератор можно рассматривать как поток. (Генераторы в этой книге не рассматриваются, но я опишу их в книге более высокого уровня.)
Как правило, если у вас имеется объект, поддерживающий перебор, для перебора элементов используется цикл for. Циклы while используются при отсутствии простого доступа к объекту, поддерживающему перебор.
Типичный пример использования цикла while — обратный отсчет:
>>> n = 3
>>> while n > 0:
... print(n)
... n = n - 1
3
2
1
Для выхода из цикла while также может использоваться команда break:
>>> n = 3
>>> while True:
... print(n)
... n = n - 1
... if n == 0:
... break
В этой главе рассматривается использование циклов for для перебора элементов последовательности. Вы видели, что в цикле можно вести перебор по спискам; также возможен перебор по строкам, кортежам, словарям и другим структурам данных. Более того, вы можете определять собственные классы, поддерживающие перебор в циклах for, реализуя метод .__iter__.
Цикл for создает переменную при переборе. Эта переменная не уничтожается после цикла, а продолжает существовать. Если цикл for выполняется внутри функции, переменная будет уничтожена при выходе из функции.
Также в этой главе была представлена функция enumerate. Функция возвращает последовательность кортежей (пар «индекс, значение») для переданной последовательности. Если вам понадобится получить при переборе как индекс, так и значение, используйте enumerate.
Наконец, вы узнали, как прервать выполнение цикла, перейти к следующему элементу или использовать команду else. Все эти конструкции позволяют адаптировать логику циклов для конкретных задач.
1. Создайте список с именами друзей и коллег. Вычислите среднюю длину имен в списке.
2. Создайте список с именами друзей и коллег. Проведите поиск имени John в списке в цикле for. Если имя не найдено, выведите соответствующее сообщение (подсказка: используйте else).
3. Создайте список кортежей из имени, фамилии и возраста ваших друзей и коллег. Если возраст неизвестен, занесите значение None. Вычислите средний возраст, пропустив все значения None. Выведите каждое имя, за которым следует строка Old (возраст выше среднего) или Young (возраст ниже среднего).