Цикл for в Python: обход итерируемых объектов

Обновлено:

Цикл:

for

используется для последовательного обхода элементов итерируемого объекта.

Общий синтаксис:

for element in iterable:
    ...

Где:

element  → переменная, в которую по очереди попадают элементы
iterable → объект, который мы обходим

Имя переменной мы придумываем самостоятельно.


1. Простой цикл for

Возьмём список:

technologies = [
    'Python',
    'SQL',
    'Django',
]

Обойдём его:

for technology in technologies:
    print(technology)

Результат:

Python
SQL
Django

На каждой итерации в:

technology

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

Упрощённо:

1 итерация → technology = 'Python'
2 итерация → technology = 'SQL'
3 итерация → technology = 'Django'

Когда элементы заканчиваются, заканчивается и цикл.


Итерируемые объекты

2. Список

numbers = [
    10,
    20,
    30,
]

for number in numbers:
    print(number)

Результат:

10
20
30

3. Кортеж

coordinates = (
    10,
    25,
    40,
)

for coordinate in coordinates:
    print(coordinate)

Результат:

10
25
40

4. Строка

Строка тоже является итерируемым объектом.

language = 'Python'

for symbol in language:
    print(symbol)

Результат:

P
y
t
h
o
n

При обходе строки цикл получает её символы по одному.


Переменная цикла

5. Переменная получает очередной объект

Рассмотрим:

words = [
    'python',
    'sql',
    'django',
]

for word in words:
    print(word)

На каждой новой итерации имя:

word

переназначается на очередной элемент списка.


6. Переназначение переменной цикла не изменяет список

Например:

words = [
    'python',
    'sql',
    'django',
]

for word in words:
    word = word.upper()

    print(word)

print(words)

Во время цикла получим:

PYTHON
SQL
DJANGO

Но исходный список:

['python', 'sql', 'django']

не изменился.

Почему?

Сначала:

word

ссылается на:

'python'

Затем:

word = word.upper()

метод upper() создаёт строковый результат:

'PYTHON'

и имя:

word

переназначается на него.

Но сам элемент внутри:

words

не переназначается.

На следующей итерации Python снова запишет в word очередной элемент исходного списка.


7. for не создаёт отдельную область видимости

Переменная цикла — обычное имя текущей области видимости.

Например:

numbers = [
    10,
    20,
    30,
]

for number in numbers:
    print(number)

print(number)

После завершения цикла:

number

будет ссылаться на последний полученный элемент:

30

Поэтому цикл for сам по себе не создаёт новую область видимости.


Условия внутри for

8. if

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

Например, выведем только короткие слова:

words = [
    'cat',
    'python',
    'code',
    'backend',
    'sql',
]

for word in words:
    if len(word) <= 4:
        print(word)

Результат:

cat
code
sql

Все элементы списка были обойдены, но print() выполнился только для тех элементов, которые прошли проверку.


9. if, elif, else

numbers = [
    -10,
    0,
    15,
    -3,
    20,
]

for number in numbers:
    if number > 0:
        print(
            number,
            'положительное',
        )
    elif number < 0:
        print(
            number,
            'отрицательное',
        )
    else:
        print(
            number,
            'ноль',
        )

Создание нового списка

10. Список создаём до цикла

Предположим, нужно получить отдельный список коротких слов.

words = [
    'cat',
    'python',
    'code',
    'backend',
    'sql',
]

short_words = []

for word in words:
    if len(word) <= 4:
        short_words.append(word)

print(short_words)

Результат:

['cat', 'code', 'sql']

Важно, что новый список:

short_words = []

создаётся до цикла.

Если создавать его внутри:

for word in words:
    short_words = []

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


break

11. Полное завершение цикла

Ключевое слово:

break

немедленно завершает ближайший цикл.

Например, найдём первое отрицательное число:

numbers = [
    10,
    25,
    40,
    -7,
    50,
    -3,
]

for number in numbers:
    if number < 0:
        print(
            f'Найдено: {number}'
        )

        break

Результат:

Найдено: -7

После выполнения:

break

элементы:

50
-3

уже не обходятся.


continue

12. Пропуск текущей итерации

continue не завершает весь цикл.

Оно прекращает только текущую итерацию и переходит к следующему элементу.

numbers = [
    10,
    -5,
    20,
    -3,
    30,
]

for number in numbers:
    if number < 0:
        continue

    print(number)

Результат:

10
20
30

Когда встречается отрицательное число:

continue

пропускает оставшийся код текущей итерации.


else у цикла for

13. for ... else

У цикла for, как и у while, может быть свой else:

for element in iterable:
    ...
else:
    ...

else выполняется, если цикл завершился без break.


14. Поиск элемента

technologies = [
    'Python',
    'SQL',
    'Django',
]

for technology in technologies:
    if technology == 'Java':
        print('Java найдена')

        break
else:
    print('Java не найдена')

Результат:

Java не найдена

Цикл дошёл до конца и ни разу не выполнил:

break

поэтому сработал else.


Если список:

technologies = [
    'Python',
    'Java',
    'SQL',
]

то при:

Java

выполнится:

break

и else уже не запустится.


Несколько переменных в for

15. Распаковка элементов

Предположим, список содержит кортежи:

employees = [
    (
        'Alex',
        'Brown',
        'Developer',
    ),
    (
        'Emma',
        'Wilson',
        'Designer',
    ),
    (
        'Daniel',
        'Smith',
        'Manager',
    ),
]

Можно писать:

for employee in employees:
    print(employee)

Тогда на каждой итерации мы получаем целый кортеж.


Но можно сразу распаковать его:

for first_name, last_name, position in employees:
    print(
        f'{first_name} {last_name}'
        f' — {position}'
    )

Получим:

Alex Brown — Developer
Emma Wilson — Designer
Daniel Smith — Manager

На каждой итерации происходит распаковка:

('Alex', 'Brown', 'Developer')
        ↓
first_name = 'Alex'
last_name = 'Brown'
position = 'Developer'

16. Количество значений должно совпадать

Если элемент содержит три значения:

(
    'Alex',
    'Brown',
    'Developer',
)

а мы попытаемся распаковать его только в две переменные:

for first_name, last_name in employees:
    ...

возникнет:

ValueError

Потому что количество переменных не соответствует количеству распаковываемых элементов.


Что можно писать после in

17. Необязательно передавать готовую переменную

Самый простой вариант:

for number in numbers:
    ...

Но после:

in

можно использовать выражение, которое вернёт итерируемый объект.


18. sorted()

numbers = [
    50,
    10,
    30,
    20,
]

for number in sorted(numbers):
    print(number)

Результат:

10
20
30
50

Функция:

sorted(numbers)

сначала возвращает новый отсортированный список.

И уже его обходит цикл.

Исходный:

numbers

не изменяется.


19. Срез

technologies = [
    'Python',
    'SQL',
    'Django',
    'FastAPI',
    'Docker',
]

for technology in technologies[1:-1]:
    print(technology)

Будут обойдены только:

SQL
Django
FastAPI

Сначала вычисляется:

technologies[1:-1]

и цикл работает с полученным результатом.


20. Обратный срез

for technology in technologies[::-1]:
    print(technology)

Элементы обходятся в обратном порядке.

Новый объект можно не сохранять отдельно, если он нужен только для этого цикла.


21. Строковый метод

message = 'PyThOn'

for symbol in message.lower():
    print(symbol)

Сначала выполняется:

message.lower()

Получаем:

python

и затем цикл обходит символы этой строки.


22. Несколько операций

technologies = [
    'django',
    'python',
    'sql',
]

for technology in sorted(technologies):
    print(
        technology.upper()
    )

Здесь:

  1. sorted() создаёт отсортированный список;
  2. for обходит его;
  3. внутри цикла каждый элемент переводится в верхний регистр.

Левая часть for

23. Здесь происходит присваивание

В конструкции:

for technology in technologies:

часть:

technology

— место, куда Python записывает очередное значение.

Поэтому обычно здесь находится имя:

for element in ...

или несколько имён для распаковки:

for first_name, last_name in ...

Метод нельзя вызвать вместо переменной:

for name.upper() in names:
    ...

Такой код синтаксически некорректен.

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

  • справа после in;
  • либо уже внутри тела цикла.

Уникальные элементы

24. Сохраняем только первое появление

Возьмём:

numbers = [
    10,
    20,
    10,
    30,
    20,
    40,
    10,
]

Создадим новый список:

unique_numbers = []

for number in numbers:
    if number not in unique_numbers:
        unique_numbers.append(
            number
        )

print(unique_numbers)

Результат:

[10, 20, 30, 40]

Каждый раз спрашиваем:

number not in unique_numbers

Если такого значения ещё нет, добавляем его.

В результате сохраняется порядок первого появления элементов.


Изменение списка во время обхода

25. Опасная ситуация

Рассмотрим:

numbers = [
    1,
    2,
    3,
]

for number in numbers:
    numbers.append(
        number * 10
    )

Цикл начинает обходить:

[1, 2, 3]

Но во время обхода список постоянно расширяется.

Например:

[1, 2, 3]
↓
[1, 2, 3, 10]
↓
[1, 2, 3, 10, 20]
↓
[1, 2, 3, 10, 20, 30]
↓
...

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

Так можно получить бесконечное увеличение списка и огромное потребление памяти.


26. Удаление тоже может дать логическую ошибку

numbers = [
    10,
    10,
    10,
    20,
]

for number in numbers:
    if number == 10:
        numbers.remove(number)

print(numbers)

Можно ожидать:

[20]

но часть элементов может быть пропущена.

Почему?

После удаления элементы сдвигаются влево, в то время как цикл продолжает движение дальше.

Упрощённо:

[10, 10, 10, 20]
 ^
 удалить

[10, 10, 20]
     ^
цикл уже переходит дальше

Один из элементов оказался пропущен.


Главное правило

27. Не меняем длину обходящегося списка без специальной причины

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

append()
extend()
insert()

pop()
remove()
clear()

и аналогичные операции.

Такой код может:

  • пропускать элементы;
  • обрабатывать некоторые элементы несколько раз;
  • приводить к бесконечному росту;
  • давать неожиданный результат.

Это не означает, что Python всегда выдаст исключение. Часто программа продолжит работу, но её логика окажется неправильной.


28. Лучше создавать новый список

Например, нужно оставить только положительные числа:

numbers = [
    10,
    -5,
    20,
    -3,
    30,
]

positive_numbers = []

for number in numbers:
    if number > 0:
        positive_numbers.append(
            number
        )

print(positive_numbers)

Результат:

[10, 20, 30]

Исходный список во время обхода не меняется.


for внутри функций

29. Сумма положительных чисел

def sum_positive(numbers):
    total = 0

    for number in numbers:
        if number > 0:
            total += number

    return total

Проверим:

numbers = [
    10,
    -5,
    20,
    -3,
    30,
]

print(
    sum_positive(numbers)
)

Результат:

60

return вместо break

30. Поиск внутри функции

Предположим, функция должна проверить, есть ли указанное значение.

def contains(
    values,
    target,
):
    for value in values:
        if value == target:
            return True

    return False

Пример:

technologies = [
    'Python',
    'SQL',
    'Django',
]

print(
    contains(
        technologies,
        'SQL',
    )
)

Результат:

True

Как только выполняется:

return True

завершается сразу вся функция.

Поэтому отдельный:

break

здесь не нужен.


31. break и return — разные вещи

break:

завершает цикл

return:

завершает всю функцию

Например:

def contains(
    values,
    target,
):
    for value in values:
        if value == target:
            break

    return ...

После break выполнение продолжится уже после цикла.

А:

return True

немедленно завершит функцию.


break и return вместе

32. Сначала прекращаем поиск, затем возвращаем результат

Например, функция собирает элементы до специального значения:

def collect_until(
    values,
    stop_value,
):
    result = []

    for value in values:
        if value == stop_value:
            break

        result.append(value)

    return result

Проверим:

numbers = [
    10,
    20,
    30,
    0,
    40,
    50,
]

result = collect_until(
    numbers,
    0,
)

print(result)

Результат:

[10, 20, 30]

Когда найден:

0

выполняется:

break

Мы выходим из цикла.

После него:

return result

возвращает накопленный список.


Ещё одна функция

33. Получение коротких слов

def get_short_words(
    words,
    max_length=4,
):
    short_words = []

    for word in words:
        if len(word) <= max_length:
            short_words.append(
                word
            )

    return short_words

Используем:

words = [
    'cat',
    'python',
    'code',
    'backend',
    'sql',
]

result = get_short_words(
    words
)

print(result)

Получим:

['cat', 'code', 'sql']

Функция:

  1. создаёт список для результата;
  2. обходит входные данные;
  3. фильтрует элементы;
  4. добавляет подходящие элементы;
  5. возвращает готовый список.

Главное по теме

Цикл for предназначен для последовательного обхода итерируемого объекта:

for element in iterable:
    ...

На данном этапе мы можем обходить, например:

list
tuple
str

Строка обходится по символам:

for symbol in 'Python':
    ...

Переменная цикла получает очередной элемент:

for element in values:
    ...

Но её переназначение:

element = ...

само по себе не заменяет элемент внутри исходного списка.


Внутри for можно использовать:

if
elif
else

break
continue

А у самого цикла может быть:

for ...:
    ...
else:
    ...

где else выполняется, если цикл завершился без break.


Можно распаковывать элементы сразу в несколько переменных:

for (
    first_name,
    last_name,
    position,
) in employees:
    ...

После in можно использовать выражение, возвращающее итерируемый объект:

for number in sorted(numbers):
    ...
for value in values[::-1]:
    ...
for symbol in text.lower():
    ...

Во время обычного обхода списка нежелательно менять длину того же самого списка:

for value in values:
    values.append(...)

или:

for value in values:
    values.remove(...)

Это может нарушить алгоритм.

Обычно безопаснее создавать отдельный список результата:

result = []

for value in values:
    if ...:
        result.append(value)

Внутри функций вместо break иногда удобнее использовать:

return

если при нахождении результата функцию можно закончить целиком.

Если после цикла ещё нужно обработать накопленные данные, можно использовать оба:

for value in values:
    if ...:
        break

return result

Нижнее подчёркивание _ для ненужного значения

Игнорирование значения при распаковке

Иногда при распаковке нам нужны не все значения.

Например:

employees = [
    (
        'Alex',
        'Brown',
        'Developer',
    ),
    (
        'Emma',
        'Wilson',
        'Designer',
    ),
]

Если нам нужны только имя и должность, фамилию можно записать в переменную:

for first_name, last_name, position in employees:
    print(
        first_name,
        position,
    )

Но переменная:

last_name

дальше нигде не используется.

В таких ситуациях в Python принято использовать:

_

Например:

for first_name, _, position in employees:
    print(first_name, position)

Результат:

Alex Developer
Emma Designer

Нижнее подчёркивание здесь означает для другого программиста:

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


_ — это соглашение

В данном случае:

_

не является специальным синтаксисом Python.

Это обычное допустимое имя переменной.

То есть Python действительно присваивает ему значение:

first_name, _, position = (
    'Alex',
    'Brown',
    'Developer',
)

print(_)

Результат:

Brown

Но по соглашению между Python-разработчиками одиночное:

_

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

Поэтому такую запись:

for first_name, _, position in employees:
    ...

можно читать как:

Получить три значения, но второе значение нам не нужно.


Ещё один пример

products = [
    ('Laptop', 1500, 'available'),
    ('Mouse', 50, 'available'),
    ('Keyboard', 120, 'sold out'),
]

Допустим, стоимость сейчас не нужна:

for name, _, status in products:
    print(
        f'{name}: {status}'
    )

Результат:

Laptop: available
Mouse: available
Keyboard: sold out