Управляющие конструкции - if, for, while
Управляющие конструкции, такие как if, for и while, представляют собой фундаментальные строительные блоки языка программирования, которые определяют порядок выполнения инструкций в программе, причём конструкция if позволяет выполнять блок кода только при выполнении определённого условия, конструкция for обеспечивает перебор элементов в итерируемой последовательности, а конструкция while организует многократное повторение блока до тех пор, пока истинно заданное логическое выражение, и все вместе они формируют основу алгоритмической логики.
Перед чтением: Операторы — общие понятия оператора, операнда, приоритетов и типов операций без привязки к языку.
Сначала: Циклы в коде — общая идея повторений, виды циклов и типичные ошибки без привязки к синтаксису языка.
Python — if, for, while
Ветвление представляет собой механизм принятия решений в программе, который позволяет выбирать один из нескольких возможных путей выполнения на основе оценки истинности одного или нескольких логических выражений, реализуемый через конструкцию if-elif-else, где каждый последующий блок проверяется только в том случае, если все предыдущие условия оказались ложными, что даёт возможность обрабатывать множество сценариев и направлять поток управления в нужное русло.
Ветвление — if / elif / else с отступами вместо {}. Цикл for в Python почти всегда означает «для каждого элемента в …» (for x in items), а не счётчик с тремя частями в заголовке; счётчик — через range(). Списковое включение — компактная форма того же цикла, когда результат сразу собирается в список. while — когда число итераций заранее неизвестно.
Общая схема циклов и типичные ошибки — в циклах в коде (блок "Сначала" выше). Оценка сложности вложенных циклов (O(n²)) с разбором кода — Lab / Big-O — 1128. В задачах на Python те же конструкции встречаются в переборах, FizzBuzz и двойных циклах — Lab / 1122.
Операторы
Play ITЗагрузка интерактивного демо…
Условные операторы
Условные операторы предназначены для ветвления выполнения программы на основе логических выражений.
Логическое выражение представляет собой конструкцию, результатом вычисления которой является истина или ложь, построенную с использованием операторов сравнения, логических операторов, таких как и, или и не, а также переменных и констант, и которая служит основой для принятия решений в условных конструкциях и управления выполнением циклов, определяя, когда программа должна выполнить тот или иной блок кода.
В Python используется ключевое слово if для начала проверки условия, elif (сокращение от else if) — для дополнительных условий, и else — для блока, выполняемого в случае, если ни одно из предыдущих условий не было истинным.
Синтаксис условной конструкции:
if условие1:
# блок кода 1
elif условие2:
# блок кода 2
else:
# блок кода 3
Каждый блок кода должен быть выделен отступом (PEP 8 рекомендует использовать 4 пробела). Интерпретатор оценивает условия по порядку; выполнение передаётся первому блоку, чьё условие вернуло True. Остальные ветви игнорируются.
Пример:
x = 10
if x > 15:
print("x больше 15")
elif x > 5:
print("x больше 5, но не больше 15")
else:
print("x меньше или равно 5")
Разбор примера:
x = 10создаёт переменную и сохраняет в ней целое число.if x > 15проверяется первым. Условие ложно, блок пропускается.elif x > 5проверяется вторым. Условие истинно, выполняетсяprint(...).elseне выполняется, потому что подходящая ветка уже найдена.- В консоли будет строка
x больше 5, но не больше 15.
Важно отметить, что ветви elif и else являются опциональными. Конструкция может состоять только из одного if.
Присваивание в условии (:=)
Присваивание в условии представляет собой практику, при которой операция присваивания значения переменной выполняется непосредственно внутри проверяемого логического выражения, часто с использованием оператора морж, который одновременно и присваивает значение, и возвращает его для оценки, что позволяет совместить получение данных и их проверку в одной строке, сокращая объём кода и улучшая его читаемость в определённых сценариях.
С Python 3.8 в условии if или заголовке while можно присвоить значение переменной и сразу проверить его — оператор := (walrus, "моржовый"). Определение и синтаксис — в Работа с типами — операторы.
В if — сохранить длину списка и сразу сравнить:
data = list(range(12))
if (n := len(data)) > 10:
print(f"{n} items")
Разбор:
len(data)вычисляется один раз, результат попадает вn.- Условие
(n := len(data)) > 10истинно, печатается12 items. - Без walrus пришлось бы писать две строки:
n = len(data)иif n > 10:.
В while — читать ввод и проверять его в одном выражении:
while (line := input("Enter: ")) != "quit":
print("You typed:", line)
Разбор:
- Каждая итерация вызывает
input(...), строка сохраняется вline. - Цикл продолжается, пока пользователь не введёт
quit. - Переменная
lineдоступна в теле цикла на каждой итерации.
Скобки вокруг (n := len(data)) и (line := input(...)) нужны из-за приоритета операторов.
Сопоставление с образцом (match / case, Python 3.10+)
Сопоставление с образцом представляет собой мощную конструкцию, введённую в последних версиях Python, которая позволяет сравнивать структуру данных с набором шаблонов и выполнять соответствующий блок кода в зависимости от того, какому образцу соответствует значение, причём эта конструкция, реализованная через ключевое слово match, выходит далеко за рамки простого сравнения и позволяет извлекать вложенные данные, обрабатывать различные типы и даже сопоставлять с последовательностями и словарями.
Конструкция match — альтернатива длинным цепочкам if/elif, когда нужно разобрать структуру значения (число, строка, кортеж, объект):
def http_status_line(code: int) -> str:
match code:
case 200:
return "OK"
case 404:
return "Not Found"
case 500 | 502 | 503:
return "Server error"
case n if 400 <= n < 500:
return f"Client error {n}"
case _:
return "Unknown"
case _ — ветка по умолчанию (как else). Для простых двух вариантов по-прежнему достаточно if/else; match удобен при многих ветках и распаковке структур. Ключевые слова перечислены в справочнике ключевых слов.
if <условие>:
<действие>
if <условие>:
<действие_1>
else:
<действие_2>
if <условие_1>:
<действие_1>
elif <условие_2>:
<действие_2>
else:
<действие_3>
Логические операторы
Логические операторы используются для комбинирования или инвертирования булевых выражений. В Python они представлены словами and, or, not, в отличие от многих других языков программирования, где применяются символьные аналоги (&&, ||, !). Это делает код более читаемым, но требует внимания при сравнении с другими C-подобными языками.
and— возвращает True, если оба операнда истинны.or— возвращает True, если хотя бы один операнд истинен.not— возвращает логическое отрицание операнда.
<выражение_1> and <выражение_2>
<выражение_1> or <выражение_2>
not <выражение>
Операторы работают по принципу короткого замыкания (short-circuit evaluation):
xandy: если x ложно, y не вычисляется.xory: если x истинно, y не вычисляется.
Пример:
a = True
b = False
print(a and b) # False
print(a or b) # True
print(not a) # False
Разбор примера:
andвозвращаетTrueтолько если оба операнда истинны.True and FalseдаётFalse.orвозвращаетTrue, если истинен хотя бы один операнд.True or FalseдаётTrue.notинвертирует булево значение.not TrueдаётFalse.print(...)выводит результат каждой операции в отдельной строке.
Такой подход позволяет эффективно управлять порядком вычислений и избегать ошибок, например:
if obj is not None and obj.has_method():
obj.method()
Здесь has_method() не будет вызван, если obj — None, поскольку первая часть условия ложна, и вторая не вычисляется.
Важно не путать логические операторы с операторами сравнения:
==равно;!=не равно;>больше;<меньше;>=больше или равно;<=меньше или равно.
Сравнительные операторы
Сравнительные операторы представляют собой специальные символы или их комбинации, такие как равенство, неравенство, больше, меньше, больше или равно и меньше или равно, которые используются для сравнения двух значений и возвращают логическое значение истины или лжи, причём в Python эти операторы могут быть объединены в цепочки для выполнения множественных сравнений, например для проверки нахождения числа в определённом диапазоне.
Есть классические операторы сравнения:
Код ITЗагрузка примера кода…
Операторы is и == выполняют разные типы сравнения, что важно понимать для корректной работы с объектами.
== — проверяет равенство значений двух объектов. Вызывает метод __eq__() соответствующего класса.
is — проверяет идентичность объектов, то есть указывают ли две переменные на один и тот же объект в памяти (сравнивает их идентификаторы через id()).
<значение_1> == <значение_2>
<значение_1> != <значение_2>
<значение_1> > <значение_2>
<значение_1> < <значение_2>
<значение_1> >= <значение_2>
<значение_1> <= <значение_2>
<объект_1> is <объект_2>
<объект> is None
Пример:
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True — значения одинаковы
print(a is b) # False — это разные объекты в памяти
Разбор примера:
aиbсодержат одинаковые элементы, поэтомуa == bвозвращаетTrue.a is bсравнивает не значения, а идентичность объектов.- В памяти созданы два разных списка, поэтому
a is bвозвращаетFalse. - Практическое правило: для сравнения данных используйте
==, аisоставляйте дляNoneи синглтонов.
Однако:
c = a
print(a is c) # True — c ссылается на тот же объект, что и a
Разбор примера:
c = aне копирует список, а создаёт второе имя для того же объекта.- Поэтому
a is cвозвращаетTrue. - Если изменить
a, изменения будут видны и черезc.
Исключение составляют малые целые числа и строки, которые интернируются (кэшируются) интерпретатором. Например:
x = 256
y = 256
print(x is y) # True (в большинстве реализаций)
x = 257
y = 257
print(x is y) # Может быть False — зависит от контекста выполнения
По этой причине не рекомендуется использовать is для сравнения значений, особенно числовых или строковых. Оператор is следует применять в основном для сравнения с None, True, False и другими синглтонами:
if obj is None:
print("Объект не определён")
Это считается лучшей практикой, поскольку None — уникальный объект.
Интерактивное демо — пошаговый цикл на примере JavaScript (
for,while). В Python синтаксис другой (for…in,while), но порядок шагов тот же. Обобщённо: циклы в коде.
Play ITЗагрузка интерактивного демо…
Циклы
Циклы представляют собой управляющие конструкции, предназначенные для многократного выполнения блока кода, и включают в себя цикл for, который используется для перебора известной последовательности элементов или для выполнения итераций определённое количество раз, и цикл while, который продолжает выполняться до тех пор, пока заданное условие остаётся истинным, при этом оба типа циклов могут управляться с помощью инструкций прерывания, таких как break для немедленного выхода и continue для перехода к следующей итерации.
В Python два основных оператора повторения — for … in (обход итерируемого) и while (пока условие истинно). Оператора do…while в языке нет; "сделать хотя бы раз" имитируют через while True с break или отдельной проверкой после первого прохода.
| Конструкция | Порядок | Когда удобна |
|---|---|---|
for … in | следующий элемент → тело | список, строка, range(), ключи словаря, генератор |
| списковое включение | выражение → элемент → … | новый список из итерируемого за одну строку |
while | проверка условия → тело | число итераций заранее неизвестно |
Счётный цикл "N раз" в Python обычно пишут как for i in range(N) или for _ in range(N), а не через while со счётчиком. Обобщённая теория — циклы в коде.
for
Цикл for в Python является итерационным, а не счётным, как в некоторых других языках. Он предназначен для обхода элементов итерируемого объекта — коллекций, строк, диапазонов и других объектов, поддерживающих протокол итерации.
Итерационность, или итерируемость, представляет собой свойство объекта, которое означает, что по его элементам можно последовательно проходить с использованием цикла for, причём итерируемый объект должен реализовывать специальный метод, возвращающий итератор, который в свою очередь предоставляет механизм для извлечения следующего элемента до тех пор, пока все элементы не будут исчерпаны, и это свойство лежит в основе работы многих встроенных функций и конструкций языка.
Синтаксис:
for переменная in итерируемый_объект:
# тело цикла
Часто в качестве итерируемого объекта используется функция range(), которая генерирует арифметическую последовательность чисел.
Функция range(stop) — создаёт объект диапазона, содержащий целые числа от 0 до stop - 1.
range(start, stop[, step]) позволяет задать начальное значение, конечное значение (не включая его) и шаг.
Примеры:
Код ITЗагрузка примера кода…
Разбор примера:
for i in range(10)выполняет тело 10 раз, аiпринимает значения от0до9.print(i, end=" ")печатает числа в одну строку, разделяя их пробелом.range(0, 50, 5)задаёт начало, конец (не включая 50) и шаг 5.range(10, 0, -1)использует отрицательный шаг и даёт обратный отсчёт.for letter in 'word'итерируется по строке посимвольно —w,o,r,d.
Обратите внимание — range() возвращает объект типа range, который является ленивым (lazy) — числа генерируются по мере необходимости, что экономит память.
Я бы выделил три основных вида цикла for():
for(A)- повторять определённое количество раз;for(A, B)- выполнять количество раз в диапазоне отAдоB;for(A, B, C)- выполнять количество раз в диапазоне отAдоB, с размером шагаC.
Цикл for можно использовать с любыми итерируемыми типами:
- списками;
- кортежами;
- множествами;
- словарями (ключи);
- генераторами;
- т.д.
for <переменная> in <итерируемый_объект>:
<действие>
for <переменная> in range(<конец>):
<действие>
for <переменная> in range(<начало>, <конец>):
<действие>
for <переменная> in range(<начало>, <конец>, <шаг>):
<действие>
К примеру, повторить 100 раз какую-то фразу:
for i in range(100):
print(f'{i}. Повтор!')
Комбинированные паттерны
# Поиск с прерыванием
for <элемент> in <коллекция>:
if <условие_нахождения>:
<обработка>
break
# Пропуск элементов
for <элемент> in <коллекция>:
if <условие_пропуска>:
continue
<обработка>
# Безопасная проверка объекта
if <объект> is not None and <объект>.<метод>():
<действие>
# Итерация по словарю
for <ключ> in <словарь>:
<действие_с_ключом>
for <ключ>, <значение> in <словарь>.items():
<действие_с_парой>
Списковые включения — «for в одну строку»
Списковое включение (list comprehension) — компактная запись цикла for, который сразу строит новый список. В разговорной речи его иногда называют «генератором списка», но не путайте с функцией-генератором (yield) и выражением-генератором в круглых скобках — об этом ниже и в статье Итераторы, генераторы и контекстные менеджеры.
Списковые включения представляют собой компактный синтаксический конструктор для создания новых списков на основе существующих итерируемых объектов с возможностью применения фильтрации и преобразования элементов, при этом они записываются в квадратных скобках и включают выражение для вычисления каждого элемента и один или несколько циклов for, а также опциональное условие if, что позволяет создавать новые списки более кратко и выразительно по сравнению с традиционным циклом.
Выражение-генератор представляет собой конструкцию, очень похожую на списковое включение, но записываемую в круглых скобках, и отличается от него тем, что возвращает объект-генератор, который вычисляет элементы лениво, то есть только по мере необходимости, а не создаёт сразу весь список в памяти, что делает его незаменимым при работе с большими объёмами данных, когда хранение всей коллекции одновременно было бы неэффективным.
Обход, или итерация, представляет собой процесс последовательного доступа к каждому элементу структуры данных, такой как список, кортеж, множество или словарь, с целью выполнения некоторой операции для каждого из этих элементов, и может быть реализован явно с использованием цикла for, неявно с помощью встроенных функций, таких как map или filter, или автоматически внутри списковых включений и выражений-генераторов, причём порядок обхода зависит от типа структуры данных.
Вместо «создать пустой список → пройти циклом → append» вы описываете результат одним выражением. Это один из самых характерных приёмов Python — от простых квадратов чисел до генерации игровых карт и матриц.
Полный синтаксис
[<выражение> for <элемент> in <итерируемый_объект> if <условие>]
Читаем слева направо: сначала что попадёт в список, затем откуда берутся элементы, при необходимости — фильтр в конце.
[x ** 2 for x in range(5)]
# ^^^^^ ^^^^^^^^^^^^^^^
# что откуда берём
# делаем элементы
Часть if <условие> необязательна. Без неё перебираются все элементы итератора.
Базовые формы
# 1. Простое включение — без преобразования
[x for x in range(10)]
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 2. С преобразованием
[x ** 2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# 3. С фильтрацией
[x for x in range(10) if x % 2 == 0]
# [0, 2, 4, 6, 8]
# 4. Фильтрация и преобразование вместе
[x ** 2 for x in range(10) if x % 2 == 0]
# [0, 4, 16, 36, 64]
Эквивалент четвёртой формы через обычный цикл:
result = []
for x in range(10):
if x % 2 == 0:
result.append(x ** 2)
Тот же шаблон работает со строками, списками и любыми итерируемыми объектами:
upper = [letter.upper() for letter in "hello"]
lengths = [len(word) for word in ["Python", "Java", "Go"]]
Условное выражение внутри включения
Если в список должны попадать разные значения в зависимости от условия, в левую часть (в <выражение>) вставляют тернарный оператор:
<значение_если_True> if <условие> else <значение_если_False>
result = [x if x % 2 == 0 else -x for x in range(10)]
# [0, -1, 2, -3, 4, -5, 6, -7, 8, -9]
# чётные оставляем, нечётные делаем отрицательными
Разбор:
for x in range(10)— источник элементов.x if x % 2 == 0 else -x— выражение результата для каждогоx.- Фильтрующий
ifв конце здесь не нужен — в список попадают все десять чисел, просто в разном виде.
Типичный приём в roguelike и тайловых играх — карта с границей-стеной:
ROWS, COLS = 10, 15
map_with_border = [
['#' if x == 0 or x == COLS - 1 or y == 0 or y == ROWS - 1 else '.'
for x in range(COLS)]
for y in range(ROWS)
]
# края — стены ('#'), внутри — пол ('.')
Внешний цикл for y in range(ROWS) строит строки карты, внутренний — столбцы в каждой строке. Похожие приёмы — в практикуме по roguelike.
Вложенные включения
Вложенное включение — это включение, результат которого снова используется как элемент другого включения. Внешний цикл for идёт последним в записи (снизу вверх при чтении).
Матрица одинаковых строк:
matrix = [[j for j in range(3)] for i in range(3)]
# [[0, 1, 2], [0, 1, 2], [0, 1, 2]]
Матрица с уникальными значениями:
matrix = [[i * 3 + j for j in range(3)] for i in range(3)]
# [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
Транспонирование (строки становятся столбцами):
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
transposed = [[row[i] for row in matrix] for i in range(len(matrix[0]))]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Шахматная доска — чередование символов по сумме координат:
chess_board = [
['♖' if (x + y) % 2 == 0 else '♘' for x in range(8)]
for y in range(8)
]
Разворот (flatten) вложенного списка в один — один уровень вложенности, два цикла в одной строке:
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [x for row in matrix for x in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
Несколько циклов for в одном включении
Несколько for в одном включении эквивалентны вложенным циклам. Порядок сохраняется — как если бы вы писали for снаружи внутрь:
# Обычный вложенный цикл
pairs = []
for x in range(3):
for y in range(3):
pairs.append((x, y))
# То же через включение
pairs = [(x, y) for x in range(3) for y in range(3)]
# [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
Фильтр if в конце относится ко всей комбинации циклов:
# только пары, где x != y
pairs = [(x, y) for x in range(3) for y in range(3) if x != y]
# [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
# пары с суммой координат 3
pairs = [(x, y) for x in range(4) for y in range(4) if x + y == 3]
# [(0, 3), (1, 2), (2, 1), (3, 0)]
Несколько if подряд работают как цепочка and:
result = [x for x in range(20) if x % 2 == 0 if x % 3 == 0]
# [0, 6, 12, 18] — делятся и на 2, и на 3
Включения для set и dict
Тот же синтаксис «цикл + выражение» работает для других коллекций:
| Конструкция | Скобки | Результат |
|---|---|---|
| списковое включение | [...] | list |
| включение множества | {expr ...} (без :) | set |
| словарное включение | {ключ: значение ...} | dict |
Множество — уникальные значения:
squares = {x ** 2 for x in range(10)}
letters = {char for char in "hello world" if char != ' '}
# {'h', 'e', 'l', 'o', 'w', 'r', 'd'}
Словарь — пары ключ–значение:
squares_dict = {x: x ** 2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
original = {'a': 1, 'b': 2, 'c': 3}
inverted = {v: k for k, v in original.items()}
# {1: 'a', 2: 'b', 3: 'c'}
filtered = {k: v for k, v in original.items() if v > 1}
# {'b': 2, 'c': 3}
Подробнее про включения в контексте коллекций — в главе Типы данных.
enumerate, zip и другие итераторы
Включение не обязано идти только по range() — источником может быть любой итерируемый объект:
# индекс + значение
indexed = [(i, x) for i, x in enumerate(['a', 'b', 'c'])]
# [(0, 'a'), (1, 'b'), (2, 'c')]
# параллельный обход двух списков
names = ['Alice', 'Bob', 'Charlie']
scores = [85, 92, 78]
students = [(name, score) for name, score in zip(names, scores)]
# [('Alice', 85), ('Bob', 92), ('Charlie', 78)]
Функции enumerate и zip — в статье Функции.
Примеры из игровой логики
Клетки комнаты — все координаты прямоугольника одним выражением:
def create_room(x, y, w, h):
room_cells = [(cx, cy) for cx in range(x, x + w) for cy in range(y, y + h)]
for cx, cy in room_cells:
game_map[cy][cx] = '.' # пол комнаты
Видимые клетки в радиусе (круг на дискретной сетке):
visible_cells = [
(x, y)
for x in range(max(0, px - radius), min(COLS, px + radius + 1))
for y in range(max(0, py - radius), min(ROWS, py + radius + 1))
if (x - px) ** 2 + (y - py) ** 2 <= radius ** 2
]
Пакетное создание объектов — несколько монстров за один проход:
import random
monsters = [
Monster(
random.randint(room[0] + 1, room[0] + room[2] - 2),
random.randint(room[1] + 1, room[1] + room[3] - 2),
random.choice(['Goblin', 'Orc', 'Bat']),
random.randint(3, 8),
random.randint(1, 4),
)
for _ in range(random.randint(1, 3))
]
Фильтрация «живых» объектов после обновления:
particles = [p for p in particles if p.update()]
# update() двигает частицу и возвращает False, когда life <= 0
Система частиц — создание и отбор за два включения:
import random
class Particle:
def __init__(self, x, y, dx, dy):
self.x, self.y, self.dx, self.dy = x, y, dx, dy
self.life = 100
def update(self):
self.x += self.dx
self.y += self.dy
self.life -= 1
return self.life > 0
particles = [
Particle(
random.randint(0, 800),
random.randint(0, 600),
random.uniform(-1, 1),
random.uniform(-1, 1),
)
for _ in range(100)
]
particles = [p for p in particles if p.update()]
Выражение-генератор
Выражение-генератор выглядит как списковое включение, но в круглых скобках и не создаёт список сразу — возвращает ленивый итератор:
list_comp = [x ** 2 for x in range(1_000_000)] # list — всё в памяти
gen_exp = (x ** 2 for x in range(1_000_000)) # generator — по одному элементу
total = sum(x ** 2 for x in range(10)) # скобки часто опускают
Разница принципиальна:
[...]— все элементы материализуются сразу.(...)— элементы считаются по запросу (next, циклfor,sum,max).
В roguelike, если список координат нужен только для одного прохода, выгоднее не создавать список:
visible_cells = (
(x, y)
for x in range(max(0, px - radius), min(COLS, px + radius + 1))
for y in range(max(0, py - radius), min(ROWS, py + radius + 1))
if (x - px) ** 2 + (y - py) ** 2 <= radius ** 2
)
for x, y in visible_cells:
game_map[y][x].visible = True
Когда нужен именно список (индексация, повторный обход, len) — квадратные скобки. Когда достаточно одного прохода — круглые скобки или выражение без обёртки.
Когда включение, когда обычный for
Удобно, когда:
- простое преобразование:
squares = [x ** 2 for x in numbers]; - фильтрация:
evens = [x for x in numbers if x % 2 == 0]; - создание матрицы:
matrix = [[0 for _ in range(cols)] for _ in range(rows)]; - код читается как «создать список из…».
Лучше обычный for, когда:
# 1. Сложная логика с несколькими ветками
# Плохо:
result = [x if x > 0 else y if y > 0 else z for x, y, z in zip(a, b, c)]
# Хорошо:
result = []
for x, y, z in zip(a, b, c):
if x > 0:
result.append(x)
elif y > 0:
result.append(y)
else:
result.append(z)
# 2. Побочные эффекты (print, запись в файл, API)
# Плохо:
[print(x) for x in range(5)]
# Хорошо:
for x in range(5):
print(x)
# 3. Очень большой объём и один проход — генераторное выражение
# list: [x for x in range(1_000_000)]
# gen: (x for x in range(1_000_000))
Списковое включение — выражение, возвращающее список; цикл for — оператор с блоком инструкций.
Производительность
Списковые включения обычно быстрее эквивалентного цикла с append: интерпретатор выполняет построение списка на более низком уровне, без повторных вызовов метода.
import timeit
def test_loop():
result = []
for i in range(1000):
result.append(i ** 2)
return result
def test_comprehension():
return [i ** 2 for i in range(1000)]
# На типичной машине comprehension быстрее примерно на 30–50 %
timeit.timeit(test_loop, number=10_000)
timeit.timeit(test_comprehension, number=10_000)
Разница заметна на больших объёмах и в горячих участках кода. На читаемости экономить не стоит — если включение превращается в «однострочник на полэкрана», вернитесь к обычному for.
Полезные приёмы
Матрица без ловушки общей ссылки — [[0] * cols] * rows даёт один и тот же внутренний список во всех строках. Правильно:
# НЕЛЬЗЯ: [[0, 0]] * 3 — все строки ссылаются на один список
matrix = [[0 for _ in range(cols)] for _ in range(rows)]
Подсчёт вхождений через включение (для небольших данных; на больших — collections.Counter):
words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
counts = {word: words.count(word) for word in set(words)}
# {'apple': 3, 'banana': 2, 'orange': 1}
Безопасное удаление из списка — не менять коллекцию во время обхода, а собрать новую:
items = [1, 2, 3, 4, 5]
items = [x for x in items if x % 2 == 1]
# [1, 3, 5]
Тот же приём — в разделе Частые ловушки ниже.
Частые ошибки
Забытый if — условие фильтрации пишется только с ключевым словом if:
# Ошибка:
# [x for x in range(10) x % 2 == 0]
# Правильно:
[x for x in range(10) if x % 2 == 0]
Порядок циклов — переменная в <выражение> должна быть объявлена раньше (левее) того цикла, где используется:
# Ошибка — x ещё не определён в момент чтения выражения:
# [x for y in range(3) for x in range(2)]
# Правильно:
[x for x in range(2) for y in range(3)]
Путаница «изменить» и «создать новый» — включение не меняет исходный список, а возвращает новый. Результат нужно присвоить переменной:
numbers = [1, 2, 3, 4, 5]
doubled = [x * 2 for x in numbers] # numbers не изменился
numbers = doubled # явная замена — ок
Вложенное включение для побочных эффектов — [game_map[y].__setitem__(x, '.') for ...] и подобное лучше заменить обычным for: включение должно возвращать данные, а не выполнять действия ради действий.
while
Цикл while выполняет блок кода до тех пор, пока указанное условие остаётся истинным. Он используется, когда количество итераций заранее неизвестно.
Синтаксис:
while условие:
# тело цикла
Пример:
count = 0
while count < 5:
print(count)
count += 1
Разбор примера:
count = 0задаёт начальное значение счётчика.- Перед каждой итерацией проверяется условие
count < 5. - Внутри цикла текущее значение выводится в консоль.
count += 1изменяет состояние, поэтому цикл в итоге завершается.- Результат вывода: числа от
0до4.
Типичный приём с walrus — чтение ввода прямо в условии цикла (см. присваивание в условии):
while (line := input("Enter: ")) != "quit":
print("You typed:", line)
while <условие>:
<действие>
Важно обеспечить выход из цикла, иначе он станет бесконечным. Бесконечный цикл возникает, если условие никогда не становится ложным:
while True:
print("Это бесконечный цикл")
Такие конструкции допустимы, если внутри цикла предусмотрен механизм прерывания через break.
Для детального контроля над выполнением циклов и условных блоков в Python предусмотрены специальные управляющие инструкции.
break — немедленно прекращает выполнение текущего цикла и передаёт управление следующему за ним блоку кода. Часто используется при поиске элемента или обработке ошибок.
for i in range(10):
if i == 5:
break
print(i) # Вывод: 0, 1, 2, 3, 4
Разбор примера:
- Цикл идёт по числам от
0до9. - Когда
iстановится5, срабатываетbreak. breakмгновенно завершает весь цикл, а не только текущую итерацию.- Поэтому печатаются только
0, 1, 2, 3, 4. continue — прерывает текущую итерацию и переходит к следующей. Управление возвращается к началу цикла, где проверяется условие.
for i in range(5):
if i == 2:
continue
print(i) # Вывод: 0, 1, 3, 4
Разбор примера:
continueпропускает оставшийся код текущей итерации.- При
i == 2вызовprint(i)не выполняется. - Цикл продолжается со следующего значения.
- Поэтому в выводе отсутствует только
2.
pass — пустая операция. Используется как заглушка, когда синтаксически требуется наличие инструкции, но логика ещё не реализована.
if condition:
pass # Заглушка для будущей реализации
else:
print("Условие ложно")
pass также применяется в определениях классов, функций и блоков, где отсутствие кода привело бы к синтаксической ошибке.
Связанные материалы
- Сопоставление с образцом (match / case) — ветвление по структуре данных (Python 3.10+)
- Типы данных — списковые и словарные включения
- Итераторы, генераторы и контекстные менеджеры — выражения-генераторы,
yield, ленивые вычисления - Функции
- Обработка исключений
Практическая логика написания условий и циклов
Чтобы код оставался читаемым при росте проекта, полезно придерживаться трёх правил:
- Условия пишутся от "самого узкого и критичного" к "общему".
- Длинные проверки выносятся в отдельные функции-предикаты.
- Цикл отвечает за обход, а бизнес-логика выносится в функции.
Пример аккуратного предиката:
def can_publish(post, user):
return user.is_staff and post.is_ready and not post.is_archived
if can_publish(post, user):
publish(post)
Так код проще тестировать и поддерживать.
Частые ловушки новичков
- Ошибочный
isвместо==при сравнении строк и чисел. - Изменение коллекции во время обхода той же коллекции в
for— безопаснее собрать новый список через списковое включение. - Бесконечный
whileиз-за условия, которое не меняется. - Глубокие вложенные
if, где лучше ранний выход черезreturn. - Включение ради побочного эффекта —
[print(x) for x in items]вместо обычногоfor(см. когда не использовать включение). [[0] * cols] * rowsвместо[[0 for _ in range(cols)] for _ in range(rows)]— общая ссылка на внутренние списки.
Безопасный шаблон изменения списка:
items = [1, 2, 3, 4, 5]
items = [x for x in items if x % 2 == 1]
Это надёжнее, чем удалять элементы из items внутри цикла.