Циклы в Python — for и while для аналитика

Коротко

for итерирует по последовательности, while крутится, пока условие истинно. Аналитики используют циклы для обработки файлов, батчевых запросов к API, парсинга данных. На собеседованиях спрашивают range, break/continue, else у циклов и почему в pandas циклы обычно не нужны.

for — итерация по последовательности

for обходит любой итерируемый объект: список, строку, словарь, range, файл.

# Список
topics = ['SQL', 'Python', 'A/B-тесты']
for topic in topics:
    print(topic)

# Строка — посимвольно
for char in 'pandas':
    print(char, end=' ')
# p a n d a s

# Словарь — по ключам
config = {'host': 'localhost', 'port': 5432, 'db': 'analytics'}
for key in config:
    print(f"{key}: {config[key]}")

# По парам ключ-значение
for key, value in config.items():
    print(f"{key} = {value}")

range — генерация числовых последовательностей

range(start, stop, step) создаёт ленивую последовательность целых чисел. stop не включается.

# 0, 1, 2, 3, 4
for i in range(5):
    print(i)

# 2, 4, 6, 8
for i in range(2, 10, 2):
    print(i)

# Обратный отсчёт: 5, 4, 3, 2, 1
for i in range(5, 0, -1):
    print(i)

range не создаёт список в памяти — это ленивый объект. range(1_000_000) не занимает памяти на миллион чисел.

while — цикл по условию

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

# Ждать ответа API
import time

retries = 0
success = False
while not success and retries < 5:
    response = call_api()
    if response.status_code == 200:
        success = True
    else:
        retries += 1
        time.sleep(2 ** retries)  # экспоненциальный бэкофф

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

break и continue

break — выход из цикла досрочно. continue — перескочить к следующей итерации.

# break — найти первый отрицательный
numbers = [3, 7, -2, 5, -8]
for n in numbers:
    if n < 0:
        print(f"Первый отрицательный: {n}")
        break

# continue — пропустить None
data = [1, None, 3, None, 5]
total = 0
for x in data:
    if x is None:
        continue
    total += x
# total = 9

else у циклов

Блок else выполняется, если цикл завершился без break. Непривычная конструкция, но на собеседованиях спрашивают.

# Проверить, есть ли простое число в списке
numbers = [4, 6, 8, 10]

for n in numbers:
    if is_prime(n):
        print(f"Найдено простое: {n}")
        break
else:
    print("Простых чисел нет")
# "Простых чисел нет" — break не сработал

else можно читать как «если break не случился». Работает и с for, и с while.

Вложенные циклы

Цикл внутри цикла. Классика — обход матрицы или генерация комбинаций.

# Таблица умножения
for i in range(1, 4):
    for j in range(1, 4):
        print(f"{i}x{j}={i*j}", end='  ')
    print()
# 1x1=1  1x2=2  1x3=3
# 2x1=2  2x2=4  2x3=6
# 3x1=3  3x2=6  3x3=9

break во вложенном цикле выходит только из внутреннего. Чтобы выйти из обоих — используйте флаг или вынесите логику в функцию с return.

Примеры для аналитика

import os
import pandas as pd

# Прочитать все CSV из папки
data_frames = []
for filename in os.listdir('data/'):
    if filename.endswith('.csv'):
        df = pd.read_csv(f'data/{filename}')
        data_frames.append(df)
all_data = pd.concat(data_frames)

# Батчевые запросы к API
user_ids = list(range(1, 10001))
batch_size = 100
for i in range(0, len(user_ids), batch_size):
    batch = user_ids[i:i + batch_size]
    send_to_api(batch)

# Пагинация — while подходит идеально
page = 1
all_results = []
while True:
    response = fetch_page(page)
    if not response['data']:
        break
    all_results.extend(response['data'])
    page += 1

Циклы и pandas — когда НЕ нужен цикл

iterrows() существует, но почти всегда это антипаттерн. Векторные операции быстрее в 100–1000 раз.

import pandas as pd
df = pd.DataFrame({'salary': [90000, 150000, 110000]})

# Плохо — цикл по строкам
for idx, row in df.iterrows():
    df.at[idx, 'tax'] = row['salary'] * 0.13

# Хорошо — векторная операция
df['tax'] = df['salary'] * 0.13

# Плохо — цикл с условием
for idx, row in df.iterrows():
    if row['salary'] > 100000:
        df.at[idx, 'level'] = 'senior'
    else:
        df.at[idx, 'level'] = 'junior'

# Хорошо — np.where или apply
import numpy as np
df['level'] = np.where(df['salary'] > 100000, 'senior', 'junior')

Правило: если операция применяется к каждой строке одинаково — ищите векторный способ. Циклы в pandas оправданы только при сложной зависимости между строками.

for vs while — когда что

for — когда известно, по чему итерируем: список, range, файл, словарь. 90% циклов в аналитике.

while — когда количество итераций заранее неизвестно: ретраи, пагинация, ожидание условия, пользовательский ввод.

Частые ошибки

Модификация списка во время итерации. Удаление элементов из списка в цикле for ломает индексацию. Итерируйте по копии или используйте list comprehension.

# Баг — пропускает элементы
items = [1, 2, 3, 4, 5]
for x in items:
    if x % 2 == 0:
        items.remove(x)  # Не делайте так

# Правильно
items = [x for x in items if x % 2 != 0]

Бесконечный while. Забыли обновить условие или нет break — цикл никогда не завершится. Всегда добавляйте ограничение: счётчик или таймаут.

Цикл вместо векторной операции. for idx, row in df.iterrows() в 99% случаев заменяется на df['col'] = .... Если пишете цикл по DataFrame — сначала убедитесь, что нет встроенного способа.

Вопросы с собеседований

-- Чем for отличается от while? -- for итерирует по готовой последовательности (список, range, генератор). while проверяет условие перед каждой итерацией и работает, пока оно истинно. for — когда знаем «по чему идём», while — когда знаем «пока что выполняется».

-- Что такое range и почему это не список? -- range — ленивый объект, который генерирует числа по запросу. range(1_000_000) занимает фиксированный объём памяти независимо от размера, в отличие от list(range(1_000_000)), который создаёт миллион элементов в памяти.

-- Когда выполняется else у цикла? -- Когда цикл завершился естественно, без break. Если break сработал — else пропускается. Работает и с for, и с while. На практике встречается редко, но на собесах спрашивают.

-- Почему iterrows в pandas — плохая идея? -- Потому что iterrows создаёт Series для каждой строки, что убивает производительность. Векторные операции в pandas (и numpy) работают в 100–1000 раз быстрее, потому что выполняются на уровне C без Python-оверхеда на каждую строку.

-- Как выйти из вложенного цикла? -- break выходит только из внутреннего цикла. Чтобы выйти из обоих: вынести во функцию и использовать return, либо завести флаг-переменную. В Python нет labeled break, как в Java.

Потренировать Python-вопросы на практике можно в тренажёре Карьерника. Подробнее про enumerate и zip, list comprehension и lambda-функции. А больше примеров вопросов — на отдельной странице.

Открыть тренажёр в Telegram — вопросы по Python, pandas, SQL и аналитике. Бесплатно.

FAQ

range(10) создаёт список из 10 элементов?

Нет. range возвращает ленивый объект range, а не список. Числа генерируются по одному при итерации. Чтобы получить список — list(range(10)). Но обычно range используют прямо в цикле, и оборачивать не нужно.

Можно ли использовать break в while True?

Да, это стандартный паттерн — бесконечный цикл с выходом по условию внутри. Типичный пример: пагинация API, чтение из очереди, ожидание события. Главное — не забыть break, иначе цикл действительно станет бесконечным.

Когда оправдан iterrows в pandas?

Когда каждая строка зависит от результата обработки предыдущей (например, кумулятивная логика со сложными условиями). Или когда нужно вызвать внешний API для каждой строки. Во всех остальных случаях — векторные операции, apply или np.where.

Чем continue отличается от pass?

continue перескакивает к следующей итерации цикла — код ниже не выполняется. pass ничего не делает — это заглушка, выполнение продолжается дальше по телу цикла. pass используют, чтобы оставить пустой блок (например, except: pass), а continue — для пропуска итераций.