Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Управляющие конструкции
control_flow

Управляющие конструкции

if/elif/else, for, while, break, continue, else в циклах, match/case (Python 3.10+)

Открыть лабораториюv0.6.2Запускается локально из публичного репозитория

Управляющие конструкции в Python

  • Маршрут: Основы · тема 3 из 7
  • Навыки: J3
  • До урока: basic_types.
  • Результат: управление состояниями и ветвлениями без скрытых путей выполнения.
  • Основа: if, циклы, включения и match. Работа в эксплуатации: защитные условия, таблицы решений и ограничение сложности. Углубление: сопоставление с образцом, исчерпывающие случаи и связь потока управления с моделью данных.
  • Подтверждение: конечный автомат или синтаксический анализатор с таблицей граничных случаев.

Отступ определяет границы блока, поэтому в Python это часть синтаксиса. PEP 8 рекомендует четыре пробела; смешение табуляции и пробелов может привести к TabError.

#1. Условные операторы

x = 10 if x > 0: print("positive") elif x < 0: print("negative") else: print("zero")

#Истинность значений в Python

Python приводит любой объект к bool через __bool__() или __len__().

# Ложные значения (falsy): False, None, 0, 0.0, 0j # числа-нули "", [], {}, set(), () # пустые коллекции b"" # пустые байты # Истинные значения (truthy): всё остальное bool([]) # False bool([0]) # True — список непустой, хотя содержит 0! bool("") # False bool("0") # True — строка непустая! # Проверка на None — именно `is None`, не `== None` if value is None: handle_none() if value is not None: process(value) # Проверка на пустоту — не `== []`, а просто `if` if not my_list: print("list is empty") if my_list: # то же что len(my_list) > 0, но более Pythonic process(my_list)

#Тернарный оператор

# value_if_true if condition else value_if_false status = "admin" if user.is_admin else "user" message = f"{name} found" if name else "not found" # Вложенный тернарный — избегайте, плохо читается label = "high" if score > 80 else ("medium" if score > 50 else "low") # Лучше if/elif/else явно: if score > 80: label = "high" elif score > 50: label = "medium" else: label = "low"

#Оператор «моржик» := (Python 3.8+)

Присваивание выражения (walrus operator) — присваивает и возвращает значение.

# Без моржика — дублирование: data = compute() if data: process(data) # С моржиком — компактно: if data := compute(): process(data) # Цикл чтения файла with open("file.txt") as f: while line := f.readline(): process(line.strip()) # Регулярные выражения — одновременно матч и проверка import re if m := re.search(r"(\d+)", text): print(f"Found number: {m.group(1)}") # List comprehension — вычислить один раз, использовать дважды results = [y for x in data if (y := expensive(x)) > threshold] # Когда НЕ использовать # x := 5 # SyntaxError — нужны скобки или контекст (x := 5) # OK y = (x := 5) # OK — x=5, y=5

#2. Цикл for

# Итерация по любому iterable for item in [1, 2, 3]: print(item) for char in "Python": print(char) for key in {"a": 1, "b": 2}: print(key) for key, value in {"a": 1, "b": 2}.items(): print(f"{key}={value}")

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

range(5) # 0, 1, 2, 3, 4 range(1, 6) # 1, 2, 3, 4, 5 range(0, 10, 2) # 0, 2, 4, 6, 8 range(10, 0, -1) # 10, 9, 8, ... 1 # range — ленивый: не хранит все числа в памяти r = range(10**9) print(r[500_000_000]) # мгновенно! — вычисляет по индексу print(len(r)) # 1_000_000_000 print(7 in r) # O(1) — не перебирает все числа

#enumerate() — индекс + элемент

fruits = ["apple", "banana", "cherry"] # Проблема: Антипаттерн: ручной счётчик i = 0 for fruit in fruits: print(f"{i}: {fruit}") i += 1 # Вариант: enumerate for i, fruit in enumerate(fruits): print(f"{i}: {fruit}") # С нестандартным стартом for i, fruit in enumerate(fruits, start=1): print(f"{i}: {fruit}") # 1: apple, 2: banana, ...

#zip() — параллельная итерация

names = ["Alice", "Bob", "Charlie"] scores = [95, 87, 73] grades = ["A", "B", "C"] # Остановится на самом коротком итераторе for name, score in zip(names, scores): print(f"{name}: {score}") # zip возвращает кортежи — можно распаковать pairs = list(zip(names, scores)) # [("Alice", 95), ("Bob", 87), ...] # Создать словарь mapping = dict(zip(names, scores)) # {"Alice": 95, "Bob": 87, ...} # zip с *iterables — "распаковка" (unzip) pairs = [(1, "a"), (2, "b"), (3, "c")] nums, chars = zip(*pairs) # (1, 2, 3), ("a", "b", "c") # zip_longest — заполняет None (или fillvalue) для коротких from itertools import zip_longest for a, b in zip_longest([1, 2, 3], ["a", "b"], fillvalue=None): print(a, b) # 1 a # 2 b # 3 None

#3. Цикл while

# Классический цикл с условием counter = 0 while counter < 5: print(counter) counter += 1 # Бесконечный цикл с выходом по условию while True: data = fetch_next() if data is None: break process(data) # Читаем пока не получим валидный ввод while not (name := input("Enter name: ").strip()): print("Name cannot be empty!")

#4. break, continue, pass

# break — выход из цикла for n in range(10): if n == 5: break # выйдет из for print(n) # 0, 1, 2, 3, 4 # continue — пропустить итерацию for n in range(10): if n % 2 == 0: continue # пропустить чётные print(n) # 1, 3, 5, 7, 9 # pass — заглушка (ничего не делает) for n in range(10): pass # пустой цикл, корректный синтаксис class AbstractBase: def method(self): pass # будет реализован в подклассах # ... (Ellipsis) — ещё одна заглушка class Protocol: def method(self) -> None: ... # типичный паттерн для Protocol/ABC

#5. else в циклах

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

# Поиск простого числа def is_prime(n: int) -> bool: if n < 2: return False for i in range(2, int(n**0.5) + 1): if n % i == 0: return False # нашли делитель — не простое else: return True # цикл завершился без break — простое # Поиск элемента def find_first(lst, predicate): for item in lst: if predicate(item): break else: raise ValueError("No matching element found") return item # while-else: редко используется attempts = 0 while attempts < 3: if try_operation(): break attempts += 1 else: raise RuntimeError("All attempts failed")

#6. Comprehensions — выразительные включения

#List comprehension

# [expression for item in iterable if condition] squares = [x**2 for x in range(10)] evens = [x for x in range(20) if x % 2 == 0] words_upper = [w.upper() for w in ["hello", "world"]] # Вложенные comprehensions matrix = [[i * j for j in range(1, 4)] for i in range(1, 4)] # [[1, 2, 3], [2, 4, 6], [3, 6, 9]] # Flatten матрицы flat = [x for row in matrix for x in row] # [1, 2, 3, 2, 4, 6, 3, 6, 9] # Несколько условий filtered = [x for x in range(100) if x % 2 == 0 if x % 3 == 0] # Числа делящиеся на 2 И на 3

#Dict и Set comprehension

# Dict comprehension names = ["alice", "bob", "charlie"] name_lengths = {name: len(name) for name in names} # {"alice": 5, "bob": 3, "charlie": 7} # Инвертировать словарь original = {"a": 1, "b": 2, "c": 3} inverted = {v: k for k, v in original.items()} # Фильтрация admins = {id: user for id, user in users.items() if user["role"] == "admin"} # Set comprehension unique_lengths = {len(word) for word in words}

#Generator expression

# Ленивый — не создаёт список в памяти gen = (x**2 for x in range(1_000_000)) # мгновенно total = sum(gen) # вычисляется по одному элементу # Передача в функцию — можно без лишних скобок total = sum(x**2 for x in range(1000)) any_large = any(x > 999 for x in data) all_positive = all(x > 0 for x in numbers) result = max(len(line) for line in lines) # Разница в памяти import sys lst = [x**2 for x in range(1000)] gen = (x**2 for x in range(1000)) print(sys.getsizeof(lst)) # ~8856 bytes print(sys.getsizeof(gen)) # ~208 bytes

#7. match / case (Python 3.10+)

Structural Pattern Matching — мощнее switch/case из других языков.

#Литералы и значения

def describe(x): match x: case 0: return "zero" case 1 | 2 | 3: # OR — несколько значений return "small positive" case int(n) if n > 100: # guard condition return f"large: {n}" case int(): return "some integer" case str(s): return f"string: {s}" case _: # wildcard — always matches return "other"

#Последовательности

def parse_command(cmd): match cmd.split(): case ["quit"]: return "exit" case ["go", direction]: return f"move {direction}" case ["go", direction, speed]: return f"move {direction} at {speed}" case ["get", obj, *rest]: # *rest захватывает остаток return f"get {obj}, extras: {rest}" case _: return "unknown command" parse_command("go north") # "move north" parse_command("get sword shield") # "get sword, extras: ['shield']"

#Словари и классы

def process_event(event): match event: case {"type": "click", "x": x, "y": y}: return f"Click at ({x}, {y})" case {"type": "keypress", "key": key}: return f"Key: {key}" case {"type": str(t)}: return f"Unknown event type: {t}" # Паттерн класса from dataclasses import dataclass @dataclass class Point: x: float y: float def classify_point(point): match point: case Point(x=0, y=0): return "origin" case Point(x=0, y=y): return f"on Y-axis at {y}" case Point(x=x, y=0): return f"on X-axis at {x}" case Point(x=x, y=y) if x == y: return f"on diagonal at {x}" case Point(x=x, y=y): return f"({x}, {y})" # Вложенные паттерны def process(data): match data: case {"users": [{"name": str(name), "role": "admin"}, *rest]}: return f"First admin: {name}, others: {len(rest)}" case {"users": []}: return "no users"

#__match_args__ для позиционного сопоставления

@dataclass class Color: r: int g: int b: int # dataclass автоматически устанавливает __match_args__ = ("r", "g", "b") def classify_color(c): match c: case Color(0, 0, 0): # позиционно: r=0, g=0, b=0 return "black" case Color(255, 255, 255): return "white" case Color(r, 0, 0): # красный с любым r return f"red shade: {r}"

#8. Продвинутые паттерны управления потоком

#Ранний возврат (Early Return)

# Проблема: Глубокая вложенность ("pyramid of doom") def process_user(user): if user is not None: if user.is_active: if user.has_permission("admin"): if user.quota > 0: do_work(user) return True return False # Вариант: Early return — плоская структура def process_user(user): if user is None: return False if not user.is_active: return False if not user.has_permission("admin"): return False if user.quota <= 0: return False do_work(user) return True

#Guard clauses

def calculate_discount(user, purchase_amount): # Guard clauses проверяют условия в начале if not user: raise ValueError("User required") if purchase_amount <= 0: raise ValueError("Amount must be positive") if not user.is_premium: return 0 # no discount for regular users # Основная логика только для валидных случаев if purchase_amount > 10000: return 0.15 elif purchase_amount > 5000: return 0.10 else: return 0.05

#for/while как State Machine

from enum import Enum, auto class State(Enum): IDLE = auto() PROCESSING = auto() DONE = auto() ERROR = auto() def run_state_machine(items): state = State.IDLE results = [] for item in items: match state: case State.IDLE: if item: state = State.PROCESSING case State.PROCESSING: try: results.append(process(item)) except Exception: state = State.ERROR break if not item: state = State.DONE case _: break return results, state

#9. Итераторы и итерируемые объекты

# Любой объект с __iter__ — итерируемый (iterable) # Объект с __iter__ И __next__ — итератор # for реализует протокол: for x in iterable: process(x) # Эквивалентно: iterator = iter(iterable) # вызывает iterable.__iter__() while True: try: x = next(iterator) # вызывает iterator.__next__() process(x) except StopIteration: break # Ручное управление итератором it = iter([1, 2, 3]) print(next(it)) # 1 print(next(it)) # 2 print(next(it, "default")) # 3 print(next(it, "default")) # "default" — вместо StopIteration

#Практика: от пробы к рабочему решению

#Шаг 1. Соберите минимальный пример

Представляйте ветвление таблицей допустимых переходов, когда число состояний растёт. Результат основы — функция, у которой все пути дают определённый результат или явную ошибку.

#Шаг 2. Проверьте поведение

В примере ниже raise и try нужны только для наблюдаемой проверки ошибки. Их полный контракт разбирается в теме exceptions.

def transition(state: str, event: str) -> str: match state, event: case "new", "approve": return "approved" case "approved", "ship": return "shipped" case _: raise ValueError(f"недопустимый переход: {state}/{event}") assert transition("new", "approve") == "approved" try: transition("new", "ship") except ValueError as error: assert "new/ship" in str(error) else: raise AssertionError("ошибка перехода должна быть явной")

#Шаг 3. Доведите решение до рабочего сценария

Практика с подсказками: составьте таблицу состояний заказа, а затем реализуйте её двумя способами — через match и через отображение обработчиков. Проверьте не только допустимые переходы, но и неизвестное состояние, событие и повтор.

#Шаг 4. Объясните внутренний механизм

Оцените, где сопоставление с образцом делает контракт яснее, а где скрывает избыточную связанность с формой объекта. Для старшего уровня добавьте новый тип события и сравните объём изменений в двух реализациях.

#Упражнения

  1. Напишите is_prime(n) используя for/else без флаговой переменной.
  2. Реализуйте функцию chunk(iterable, size) делящую итерируемое на части размером size.
  3. Используя match, напишите парсер простых математических выражений вида ["add", 1, 2], ["mul", 3, 4], ["div", 10, ["add", 1, 1]].
  4. Объясните разницу: while True: ... break vs while condition: .... Когда лучше использовать каждый подход?
  5. Через list comprehension получите квадраты чётных чисел от 0 до 20.
  6. Разверните список ровно одного уровня, например [[1, 2], [3], [], [4, 5]], с помощью двух for, а затем comprehension.

#Самопроверка

  • is_prime должен вернуть False для чисел меньше 2 и составных чисел, а ветка else цикла должна отвечать за случай, когда делитель не найден.
  • chunk обязан явно отклонять size <= 0; проверьте пустой вход и последний неполный блок.
  • Для парсера запишите ожидаемые результаты и ошибки в таблицу до реализации; проверьте неизвестную операцию и деление на ноль.
  • Упражнения 5 и 6 должны выполняться без yield и рекурсии: эти механизмы будут введены после функций.

#Практическая лаборатория

Закрепите тему в лаборатории «Табличный конечный автомат событий». Вы сделаете все переходы явными, воспроизведёте повторную доставку события и отделите ошибку входа от системного сбоя источника.

git clone --branch v0.6.2 --depth 1 https://gitlab.potapov.me/courses/python-labs.git cd python-labs uv sync --group test uv run --group test pytest control_flow/tests

Далее: Функции и аргументы