EasyТеория6 min

Управление потоком

if/elif/else, match/case (structural pattern matching), for, while, break/continue/else, range(), enumerate()

Управляющие конструкции определяют порядок выполнения инструкций. Python предлагает классические условия и циклы, а также мощный structural pattern matching (match/case), появившийся в Python 3.10.

Условные конструкции: if / elif / else

age = 25

if age < 18:
    print("Несовершеннолетний")
elif age < 65:
    print("Взрослый")
else:
    print("Пенсионер")

# Single-line (only for simple cases)
status = "активен" if age >= 18 else "неактивен"

# Multiple conditions
score = 85
if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
elif score >= 70:
    grade = "C"
elif score >= 60:
    grade = "D"
else:
    grade = "F"
print(f"Оценка: {grade}")  # Оценка: B

Условные выражения (тернарный оператор)

# Conditional expression (ternary)
x = 10
result = "положительное" if x > 0 else "отрицательное или ноль"

# Chained (avoid - hard to read)
y = 0
sign = "+" if y > 0 else "-" if y < 0 else "0"

# With walrus operator
data = [1, 2, 3, 4, 5]
if (n := len(data)) > 3:
    print(f"Много данных: {n} элементов")

Structural Pattern Matching (match/case)

Мощная конструкция, появившаяся в Python 3.10. Это не просто switch/case -- это полноценное сопоставление с образцом:

# Basic value matching
def http_status(code: int) -> str:
    match code:
        case 200:
            return "OK"
        case 301:
            return "Moved Permanently"
        case 404:
            return "Not Found"
        case 500:
            return "Internal Server Error"
        case _:
            return f"Unknown status: {code}"

print(http_status(404))  # Not Found

Сопоставление с захватом переменных

# Pattern matching with variable capture
def process_command(command: str) -> str:
    match command.split():
        case ["quit"]:
            return "Выход"
        case ["hello", name]:
            return f"Привет, {name}!"
        case ["move", x, y]:
            return f"Перемещение в ({x}, {y})"
        case ["move", x, y, z]:
            return f"Перемещение в ({x}, {y}, {z})"
        case _:
            return "Неизвестная команда"

print(process_command("hello Иван"))     # Привет, Иван!
print(process_command("move 10 20"))     # Перемещение в (10, 20)
print(process_command("move 1 2 3"))     # Перемещение в (1, 2, 3)

Сопоставление со структурами данных

# Matching dictionaries and nested structures
def process_event(event: dict) -> str:
    match event:
        case {"type": "click", "x": x, "y": y}:
            return f"Клик в ({x}, {y})"
        case {"type": "keypress", "key": key}:
            return f"Нажата клавиша: {key}"
        case {"type": "scroll", "direction": "up" | "down" as d}:
            return f"Прокрутка: {d}"
        case _:
            return "Неизвестное событие"

print(process_event({"type": "click", "x": 100, "y": 200}))
# Клик в (100, 200)
print(process_event({"type": "scroll", "direction": "up"}))
# Прокрутка: up

Guards (условия в паттернах)

# Pattern matching with guards
def classify_point(point: tuple[int, int]) -> str:
    match point:
        case (0, 0):
            return "Начало координат"
        case (x, 0):
            return f"На оси X: x={x}"
        case (0, y):
            return f"На оси Y: y={y}"
        case (x, y) if x == y:
            return f"На диагонали: ({x}, {y})"
        case (x, y) if x > 0 and y > 0:
            return f"Первый квадрант: ({x}, {y})"
        case (x, y):
            return f"Точка ({x}, {y})"

print(classify_point((5, 5)))   # На диагонали: (5, 5)
print(classify_point((3, 0)))   # На оси X: x=3

Сопоставление с классами

from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

@dataclass
class Circle:
    center: Point
    radius: float

@dataclass
class Rectangle:
    top_left: Point
    bottom_right: Point

def describe_shape(shape) -> str:
    match shape:
        case Circle(center=Point(x=0, y=0), radius=r):
            return f"Круг в центре координат, радиус {r}"
        case Circle(radius=r) if r > 100:
            return f"Большой круг радиусом {r}"
        case Circle(center=c, radius=r):
            return f"Круг в ({c.x}, {c.y}), радиус {r}"
        case Rectangle(top_left=tl, bottom_right=br):
            w = br.x - tl.x
            h = br.y - tl.y
            return f"Прямоугольник {w}x{h}"
        case _:
            return "Неизвестная фигура"

print(describe_shape(Circle(Point(0, 0), 5)))
# Круг в центре координат, радиус 5

Цикл for

Python for итерирует по любому итерируемому объекту:

# Iterate over a list
fruits = ["яблоко", "банан", "вишня"]
for fruit in fruits:
    print(fruit)

# Iterate over a string
for char in "Python":
    print(char, end=" ")  # P y t h o n

# Iterate over a dictionary
config = {"host": "localhost", "port": 8080, "debug": True}

for key in config:
    print(f"{key} = {config[key]}")

# Better: iterate over items
for key, value in config.items():
    print(f"{key} = {value}")

# Iterate over keys or values
for key in config.keys():
    print(key)
for value in config.values():
    print(value)

range()

# range(stop) - from 0 to stop-1
for i in range(5):
    print(i, end=" ")  # 0 1 2 3 4

# range(start, stop)
for i in range(2, 7):
    print(i, end=" ")  # 2 3 4 5 6

# range(start, stop, step)
for i in range(0, 20, 3):
    print(i, end=" ")  # 0 3 6 9 12 15 18

# Counting down
for i in range(10, 0, -1):
    print(i, end=" ")  # 10 9 8 7 6 5 4 3 2 1

# range is lazy - doesn't create a list in memory
r = range(1_000_000_000)  # uses almost no memory
print(999_999 in r)        # True (O(1) check)
print(len(r))              # 1000000000

enumerate()

# enumerate() adds an index counter
languages = ["Python", "JavaScript", "Go", "Rust"]

for index, lang in enumerate(languages):
    print(f"{index}: {lang}")
# 0: Python
# 1: JavaScript
# 2: Go
# 3: Rust

# Custom start index
for num, lang in enumerate(languages, start=1):
    print(f"{num}. {lang}")
# 1. Python
# 2. JavaScript
# 3. Go
# 4. Rust

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

# Iterate over multiple sequences in parallel
names = ["Иван", "Мария", "Алексей"]
ages = [25, 30, 28]
cities = ["Москва", "Петербург", "Казань"]

for name, age, city in zip(names, ages, cities):
    print(f"{name}, {age} лет, {city}")

# zip stops at the shortest sequence
short = [1, 2]
long = [10, 20, 30, 40]
print(list(zip(short, long)))  # [(1, 10), (2, 20)]

# Use zip_longest to pad with None
from itertools import zip_longest
print(list(zip_longest(short, long, fillvalue=0)))
# [(1, 10), (2, 20), (0, 30), (0, 40)]

# Strict mode (Python 3.10+) - error if lengths differ
# list(zip(short, long, strict=True))  # ValueError!

Цикл while

# Basic while loop
count = 5
while count > 0:
    print(count, end=" ")
    count -= 1
# 5 4 3 2 1

# While with condition
password = ""
while password != "секрет":
    password = input("Введите пароль: ")
print("Доступ разрешён!")

# Infinite loop with break
while True:
    line = input(">>> ")
    if line == "quit":
        break
    print(f"Вы ввели: {line}")

break, continue, else

# break - exit the loop immediately
for n in range(2, 100):
    for i in range(2, int(n**0.5) + 1):
        if n % i == 0:
            break
    else:
        # else runs if loop completed WITHOUT break
        print(n, end=" ")
# Prints all prime numbers up to 99

# continue - skip to the next iteration
for i in range(10):
    if i % 3 == 0:
        continue  # skip multiples of 3
    print(i, end=" ")
# 1 2 4 5 7 8

# for...else example
def find_item(items: list, target: str) -> int:
    """Find target in items, return index or -1."""
    for i, item in enumerate(items):
        if item == target:
            print(f"Найдено: {target} на позиции {i}")
            break
    else:
        # Executed only if loop finished without break
        print(f"{target} не найдено")
        return -1
    return i

find_item(["a", "b", "c"], "b")  # Найдено: b на позиции 1
find_item(["a", "b", "c"], "z")  # z не найдено

while...else

# while...else - else runs when condition becomes False
n = 10
while n > 0:
    if n == 5:
        print("Прервано на 5")
        break
    n -= 1
else:
    # This would run if while condition became False naturally
    print("Цикл завершён нормально")

# Here: "Прервано на 5" (else block skipped because of break)

Вложенные циклы и итерация

# Nested loops
matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
]

for row in matrix:
    for cell in row:
        print(f"{cell:3}", end="")
    print()
#   1  2  3
#   4  5  6
#   7  8  9

# Flat iteration with itertools
from itertools import chain
flat = list(chain.from_iterable(matrix))
print(flat)  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Product of iterables (replaces nested loops)
from itertools import product
for x, y in product(range(3), range(3)):
    print(f"({x},{y})", end=" ")
# (0,0) (0,1) (0,2) (1,0) (1,1) (1,2) (2,0) (2,1) (2,2)

Практические паттерны

Обработка данных

# Filter and transform
scores = [85, 92, 45, 78, 95, 60, 88, 55]

# Find passing scores
passing = [s for s in scores if s >= 70]
print(f"Сдали: {passing}")  # [85, 92, 78, 95, 88]

# Calculate average
average = sum(scores) / len(scores)
print(f"Средний балл: {average:.1f}")  # 74.8

# Find max with index
max_score = max(scores)
max_idx = scores.index(max_score)
print(f"Лучший результат: {max_score} (позиция {max_idx})")

Retry-паттерн

import time

def fetch_with_retry(url: str, max_retries: int = 3) -> str:
    """Fetch URL with exponential backoff retry."""
    for attempt in range(max_retries):
        try:
            # Simulated fetch
            if attempt < 2:
                raise ConnectionError("Timeout")
            return f"Data from {url}"
        except ConnectionError as e:
            wait = 2 ** attempt  # exponential backoff
            print(f"Попытка {attempt + 1} неудачна: {e}. Ждём {wait}с...")
            time.sleep(wait)
    else:
        raise ConnectionError(f"Не удалось подключиться после {max_retries} попыток")

# result = fetch_with_retry("https://api.example.com/data")

Меню с match/case

def calculator():
    """Simple calculator using match/case."""
    while True:
        expr = input("Введите выражение (или 'q' для выхода): ")
        match expr.split():
            case ["q"]:
                print("До свидания!")
                break
            case [a, "+", b]:
                print(f"= {float(a) + float(b)}")
            case [a, "-", b]:
                print(f"= {float(a) - float(b)}")
            case [a, "*", b]:
                print(f"= {float(a) * float(b)}")
            case [a, "/", b] if float(b) != 0:
                print(f"= {float(a) / float(b)}")
            case [_, "/", "0"]:
                print("Ошибка: деление на ноль!")
            case _:
                print("Неверный формат. Используйте: число оператор число")

Итоги

  • if/elif/else -- базовые условия, поддерживают тернарный оператор
  • match/case -- мощное сопоставление с образцом (паттерны, guards, захват переменных)
  • for -- итерация по любому итерируемому объекту
  • while -- цикл с условием, while True + break для бесконечных циклов
  • range() -- ленивая последовательность чисел
  • enumerate() -- добавляет счётчик к итерации
  • zip() -- параллельная итерация
  • break/continue -- управление потоком внутри цикла
  • for...else -- блок else выполняется только если цикл не был прерван

Проверь себя

Что будет значением переменной после: x = 'да' if 0 else 'нет'?

Что означает паттерн case (x, y) if x == y: в match/case?

Что произойдёт при zip([1,2], [10,20,30])?

Когда выполняется блок else в конструкции for...else?