Циклы Python "for" (определенная итерация)

Оглавление

В этом уроке вы узнаете, как выполнить бесконечную итерацию с помощью цикла for на Python.

В предыдущем уроке из этой вводной серии вы узнали следующее:

  • Бесконечная итерация, при которой количество повторений явно задается заранее
  • Неопределенная итерация, при которой блок кода выполняется до тех пор, пока не будет выполнено некоторое условие. В Python неограниченная итерация выполняется с помощью while цикла.

Вот что вы узнаете из этого урока:

  • Вы начнете со сравнения некоторых различных парадигм, используемых языками программирования для реализации определенной итерации.

  • Затем вы узнаете о литерах и литераторах, двух концепциях, которые составляют основу определенной итерации в Python.

  • Наконец, вы свяжете все воедино и узнаете о циклах for в Python.

Обзор определённой итерации в программировании

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

Исторически языки программирования предлагали несколько различных вариантов циклов for. Их краткое описание приведено в следующих разделах.

Цикл числового диапазона

Самый простой цикл for представляет собой простой оператор числового диапазона с начальным и конечным значениями. Точный формат зависит от языка, но обычно выглядит примерно так:

BASIC

for i = 1 to 10
    <loop body>

Здесь тело цикла выполняется десять раз. Переменная i принимает значение 1 на первой итерации, 2 на второй, и так далее. Такой цикл for используется в языках BASIC, Algol и Pascal.

Трехвыразительный цикл

Другая форма цикла for, популярная в языке программирования C, состоит из трех частей:

  • Инициализация
  • Выражение, определяющее условие завершения
  • Действие, которое будет выполняться в конце каждой итерации.

Этот тип цикла имеет следующий вид:

for (i = 1; i <= 10; i++)
    <loop body>

Техническое примечание: В языке программирования C, i++ увеличивает переменную i. Это примерно эквивалентно i += 1 в Python.

Этот цикл интерпретируется следующим образом:

  • Инициализируйте i до 1.
  • Продолжайте цикл до тех пор, пока i <= 10.
  • Увеличивайте i на 1 после каждой итерации цикла.

Циклы с тремя выражениями for популярны, потому что выражения, заданные для трех частей, могут быть практически любыми, поэтому они обладают гораздо большей гибкостью, чем более простая форма числового диапазона, показанная выше. Такие циклы for также встречаются в языках C++, Java, PHP и Perl.

Цикл на основе коллекции или итератора

Этот тип цикла выполняет итерацию по коллекции объектов, а не задает числовые значения или условия:

for i in <collection>
    <loop body>

При каждом прохождении цикла переменная i принимает значение следующего объекта в <collection>. Этот тип цикла for является, пожалуй, самым обобщенным и абстрактным. Perl и PHP также поддерживают этот тип цикла, но он вводится ключевым словом foreach вместо for.

Дальнейшее чтение: Смотрите страницу For loop в Википедии, где подробно рассматривается реализация однозначной итерации в языках программирования.

Цикл for

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

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

Цикл for в Python выглядит следующим образом:

for <var> in <iterable>:
    <statement(s)>

<iterable> - это коллекция объектов, например, список или кортеж. Элементы <statement(s)> в теле цикла обозначаются отступами, как и во всех управляющих структурах Python, и выполняются один раз для каждого элемента в <iterable>. Переменная цикла <var> принимает значение следующего элемента в <iterable> при каждом прохождении цикла.

Вот показательный пример:

>>> a = ['foo', 'bar', 'baz']
>>> for i in a:
...     print(i)
...
foo
bar
baz

В данном примере <iterable> - это список a, а <var> - переменная i. При каждом прохождении цикла i принимает очередной элемент в a, поэтому print() отображает значения 'foo', 'bar' и 'baz' соответственно. Подобный цикл for - это питонический способ обработки элементов в итерируемой таблице.

Но что именно является итерируемым? Перед дальнейшим рассмотрением циклов for будет полезно подробнее разобраться с тем, что такое итерируемые в Python.

Iterables

В Python iterable означает, что объект может быть использован в итерации. Термин используется как:

  • Прилагательное: Объект может быть охарактеризован как итерируемый.
  • Существительное: Объект может быть охарактеризован как итерируемый.

Если объект является итерируемым, его можно передать встроенной функции Python iter(), которая возвращает нечто, называемое iterator. Да, терминология становится немного повторяющейся. Держитесь. В конце концов все получится.

Каждый из объектов в следующем примере является итератором и возвращает какой-то тип итератора при передаче в iter():

>>> iter('foobar')                             # String
<str_iterator object at 0x036E2750>

>>> iter(['foo', 'bar', 'baz'])                # List
<list_iterator object at 0x036E27D0>

>>> iter(('foo', 'bar', 'baz'))                # Tuple
<tuple_iterator object at 0x036E27F0>

>>> iter({'foo', 'bar', 'baz'})                # Set
<set_iterator object at 0x036DEA08>

>>> iter({'foo': 1, 'bar': 2, 'baz': 3})       # Dict
<dict_keyiterator object at 0x036DD990>

Эти типы объектов, с другой стороны, не являются итерируемыми:

>>> iter(42)                                   # Integer
Traceback (most recent call last):
  File "<pyshell#26>", line 1, in <module>
    iter(42)
TypeError: 'int' object is not iterable

>>> iter(3.1)                                  # Float
Traceback (most recent call last):
  File "<pyshell#27>", line 1, in <module>
    iter(3.1)
TypeError: 'float' object is not iterable

>>> iter(len)                                  # Built-in function
Traceback (most recent call last):
  File "<pyshell#28>", line 1, in <module>
    iter(len)
TypeError: 'builtin_function_or_method' object is not iterable

Все типы данных, с которыми вы до сих пор сталкивались и которые являются типами коллекций или контейнеров, являются итерируемыми. К ним относятся типы string, list, tuple, dict, set и frozenset.

Но это далеко не единственные типы, над которыми можно выполнять итерацию. Многие объекты, встроенные в Python или определенные в модулях, предназначены для итерации. Например, открытые файлы в Python являются итерируемыми. Как вы скоро увидите в учебнике по файловому вводу-выводу, итерация над объектом открытого файла считывает данные из файла.

На самом деле, почти любой объект в Python можно сделать итерируемым. Даже пользовательские объекты могут быть спроектированы таким образом, чтобы их можно было итерировать. (О том, как это делается, вы узнаете в следующей статье об объектно-ориентированном программировании.)

Итераторы

Хорошо, теперь вы знаете, что значит для объекта быть итерируемым, и знаете, как использовать iter() для получения из него итератора. Как только вы получили итератор, что вы можете с ним сделать?

По сути, итератор - это производитель значений, который выдает последовательные значения из связанного с ним объекта iterable. Встроенная функция next() используется для получения следующего значения из итератора.

Вот пример, использующий тот же список, что и выше:

>>> a = ['foo', 'bar', 'baz']

>>> itr = iter(a)
>>> itr
<list_iterator object at 0x031EFD10>

>>> next(itr)
'foo'
>>> next(itr)
'bar'
>>> next(itr)
'baz'

В данном примере a - это итерируемый список, а itr - связанный с ним итератор, полученный с помощью iter(). Каждый вызов next(itr) получает следующее значение из itr.

Обратите внимание на то, как итератор сохраняет свое внутреннее состояние. Он знает, какие значения уже были получены, поэтому, когда вы вызываете next(), он знает, какое значение возвращать следующим.

Что произойдет, если в итераторе закончатся значения? Давайте сделаем еще один вызов next() на итераторе выше:

>>> next(itr)
Traceback (most recent call last):
  File "<pyshell#10>", line 1, in <module>
    next(itr)
StopIteration

Если все значения из итератора уже были возвращены, последующий вызов next() вызывает исключение StopIteration. Любые дальнейшие попытки получить значения из итератора будут безуспешными.

Вы можете получать значения из итератора только в одном направлении. Вы не можете двигаться назад. Функции prev() не существует. Но вы можете определить два независимых итератора на одном объекте iterable:

>>> a
['foo', 'bar', 'baz']

>>> itr1 = iter(a)
>>> itr2 = iter(a)

>>> next(itr1)
'foo'
>>> next(itr1)
'bar'
>>> next(itr1)
'baz'

>>> next(itr2)
'foo'

Даже когда итератор itr1 уже находится в конце списка, itr2 все еще находится в начале. Каждый итератор поддерживает свое собственное внутреннее состояние, не зависящее от другого.

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

>>> a = ['foo', 'bar', 'baz']
>>> itr = iter(a)
>>> list(itr)
['foo', 'bar', 'baz']

Аналогично, встроенные функции tuple() и set() возвращают кортеж и множество, соответственно, из всех значений, которые выдает итератор:

>>> a = ['foo', 'bar', 'baz']

>>> itr = iter(a)
>>> tuple(itr)
('foo', 'bar', 'baz')

>>> itr = iter(a)
>>> set(itr)
{'baz', 'foo', 'bar'}

Не рекомендуется брать это в привычку. Часть элегантности итераторов заключается в том, что они "ленивы". Это означает, что когда вы создаете итератор, он не генерирует все элементы, которые он может выдать, прямо сейчас. Он ждет, пока вы не запросите их с помощью next(). Элементы не создаются до тех пор, пока их не запросят.

Когда вы используете list(), tuple() и т. п., вы заставляете итератор генерировать все свои значения одновременно, чтобы все они могли быть возвращены. Если общее количество объектов, возвращаемых итератором, очень велико, это может занять много времени.

На самом деле, в Python можно создать итератор, возвращающий бесконечную серию объектов, используя функции генератора и itertools. Если вы попытаетесь получить все значения сразу из бесконечного итератора, программа зависнет.

Внутри цикло for

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

Term Meaning
Iteration Процесс циклического перебора объектов или элементов коллекции
Iterable Объект (или прилагательное, используемое для описания объекта), над которым можно выполнять итерации
Iterator Объект, который производит последовательные элементы или значения из связанной с ним итерируемой таблицы
iter() Встроенная функция, используемая для получения итератора из итерируемого объекта

Теперь снова рассмотрим простой цикл for, представленный в начале этого урока:

>>> a = ['foo', 'bar', 'baz']
>>> for i in a:
...     print(i)
...
foo
bar
baz

Этот цикл можно полностью описать в терминах понятий, о которых вы только что узнали. Чтобы выполнить итерацию, которую описывает этот цикл for, Python делает следующее:

  • Вызывает iter(), чтобы получить итератор для a
  • Многократно вызывает next() для получения каждого элемента из итератора по очереди
  • Завершает цикл, когда next() вызывает StopIteration исключение

Тело цикла выполняется один раз для каждого возвращаемого элемента next(), при этом переменная цикла i устанавливается на данный элемент для каждой итерации.

Эта последовательность событий вкратце представлена на следующей схеме:

Python for loop diagram

Схема цикла Python for Loop

Возможно, это кажется лишней возней, но польза от этого существенная. В Python цикл по всем итерациям обрабатывается именно таким образом, а в Python итераций и итераторов великое множество:

  • Многие встроенные и библиотечные объекты являются итерируемыми.

  • Существует модуль стандартной библиотеки под названием itertools, содержащий множество функций, возвращающих итерабельные объекты.

  • Пользовательские объекты, созданные с помощью объектно-ориентированных возможностей Python, можно сделать итерируемыми.

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

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

Итерация по словарю

Ранее вы видели, что итератор можно получить из словаря с помощью iter(), поэтому вы знаете, что словари должны быть итерируемыми. Что происходит, когда вы выполняете цикл по словарю? Давайте посмотрим:

>>> d = {'foo': 1, 'bar': 2, 'baz': 3}
>>> for k in d:
...     print(k)
...
foo
bar
baz

Как видите, когда цикл for проходит по словарю , переменная цикла присваивается ключам словаря.

Для доступа к значениям словаря внутри цикла можно сделать ссылку на словарь, используя ключ, как обычно:

>>> for k in d:
...     print(d[k])
...
1
2
3

Вы также можете перебирать значения словаря напрямую, используя .values():

>>> for v in d.values():
...     print(v)
...
1
2
3

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

>>> i, j = (1, 2)
>>> print(i, j)
1 2

>>> for i, j in [(1, 2), (3, 4), (5, 6)]:
...     print(i, j)
...
1 2
3 4
5 6

Как отмечалось в учебнике по Python словарям, метод словаря .items() эффективно возвращает список пар ключ/значение в виде кортежей:

>>> d = {'foo': 1, 'bar': 2, 'baz': 3}

>>> d.items()
dict_items([('foo', 1), ('bar', 2), ('baz', 3)])

Таким образом, питоновский способ итерации по словарю с доступом к ключам и значениям выглядит так:

>>> d = {'foo': 1, 'bar': 2, 'baz': 3}
>>> for k, v in d.items():
...     print('k =', k, ', v =', v)
...
k = foo , v = 1
k = bar , v = 2
k = baz , v = 3

Функция range()

В первом разделе этого учебника вы познакомились с типом цикла for, который называется цикл числового диапазона, в котором задаются начальное и конечное числовые значения. Хотя эта форма цикла for не встроена непосредственно в Python, к ней легко прийти.

Например, если вы хотите перебрать значения от 0 до 4, вы можете сделать следующее:

>>> for n in (0, 1, 2, 3, 4):
...     print(n)
...
0
1
2
3
4

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

К счастью, Python предоставляет лучший вариант - встроенную функцию range(), которая возвращает итерируемую последовательность целых чисел.

range(<end>) возвращает итерабель, который дает целые числа, начиная с 0, до , но не включая <end>:

>>> x = range(5)
>>> x
range(0, 5)
>>> type(x)
<class 'range'>

Обратите внимание, что range() возвращает объект класса range, а не список или кортеж значений. Поскольку объект range является итерируемым, вы можете получить значения, перебирая их с помощью цикла for:

>>> for n in x:
...     print(n)
...
0
1
2
3
4

Вы также можете получить все значения сразу с помощью list() или tuple(). В сеансе REPL это может быть удобным способом быстрого отображения значений:

>>> list(x)
[0, 1, 2, 3, 4]

>>> tuple(x)
(0, 1, 2, 3, 4)

Однако, когда range() используется в коде, который является частью более крупного приложения, обычно считается плохой практикой использовать list() или tuple() таким образом. Как и итераторы, объекты range являются ленивыми - значения в указанном диапазоне не генерируются до тех пор, пока не будут запрошены. Использование list() или tuple() на объекте range заставляет возвращать все значения сразу. Это редко бывает необходимо, а если список длинный, то это может привести к потере времени и памяти.

range(<begin>, <end>, <stride>) возвращает итерабель, который дает целые числа, начиная с <begin>, до , но не включая <end>. Если указано, <stride> указывает величину, которую следует пропустить между значениями (аналогично значению stride, используемому для нарезки строк и списков):

>>> list(range(5, 20, 3))
[5, 8, 11, 14, 17]

Если <stride> опущен, то по умолчанию используется 1:

>>> list(range(5, 10, 1))
[5, 6, 7, 8, 9]
>>> list(range(5, 10))
[5, 6, 7, 8, 9]

Все параметры, указанные в range(), должны быть целыми числами, но любой из них может быть отрицательным. Естественно, если <begin> больше <end>, то <stride> должен быть отрицательным (если вы хотите получить какой-либо результат):

>>> list(range(-5, 5))
[-5, -4, -3, -2, -1, 0, 1, 2, 3, 4]

>>> list(range(5, -5))
[]
>>> list(range(5, -5, -1))
[5, 4, 3, 2, 1, 0, -1, -2, -3, -4]

Техническое примечание: Строго говоря, range() не совсем является встроенной функцией. Она реализована как вызываемый класс, создающий неизменяемый тип последовательности. Но для практических целей она ведет себя как встроенная функция.

Для получения дополнительной информации о range() смотрите статью Real Python Python's range() Function (Guide).

Изменение поведения циклов for

В предыдущем уроке из этой вводной серии вы видели, как выполнение цикла while можно прервать с помощью операторов break и continue и изменить с помощью предложения else. Эти возможности доступны и для цикла for.

Заявления break и continue

Циклы break и continue работают с циклами for так же, как и с циклами while. break полностью завершает цикл и переходит к первому оператору, следующему за циклом:

>>> for i in ['foo', 'bar', 'baz', 'qux']:
...     if 'b' in i:
...         break
...     print(i)
...
foo

continue завершает текущую итерацию и переходит к следующей итерации:

>>> for i in ['foo', 'bar', 'baz', 'qux']:
...     if 'b' in i:
...         continue
...     print(i)
...
foo
qux

Предложение else

Цикл for может содержать также предложение else. Интерпретация аналогична интерпретации цикла while. Предложение else будет выполнено, если цикл завершится из-за исчерпания итерируемого пространства:

>>> for i in ['foo', 'bar', 'baz', 'qux']:
...     print(i)
... else:
...     print('Done.')  # Will execute
...
foo
bar
baz
qux
Done.

Предложение else не будет выполнено, если список разбит на части с помощью оператора break:

>>> for i in ['foo', 'bar', 'baz', 'qux']:
...     if i == 'bar':
...         break
...     print(i)
... else:
...     print('Done.')  # Will not execute
...
foo

Заключение

В этом уроке представлен цикл for, рабочая лошадка бесконечной итерации в Python.

Вы также узнали о внутреннем устройстве iterables и iterators, двух важных типов объектов, которые лежат в основе определенной итерации, а также занимают видное место во множестве другого кода Python.

В следующих двух уроках из этой вводной серии вы немного сместите акценты и изучите, как программы на Python могут взаимодействовать с пользователем посредством ввода с клавиатуры и вывода на консоль.

Вернуться на верх