Перейти к содержимому

Условный оператор. Циклы. Исключения [1.8 ]

Условный оператор. Циклы. Исключения [1.8 ]

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

В предыдущем разделе мы рассмотрели базовые конструкции условных операторов. Теперь углубимся в более сложные сценарии и паттерны использования.

Множественные условия и логические операторы

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

# Без скобок - приоритет and выше чем or
age = 25
has_license = True
is_sober = False

# Это условие будет истинным, потому что выполняется (has_license and is_sober) ИЛИ age
# Но логически это неверно!
if age >= 18 or has_license and is_sober:
    print("Можно водить")  # Неверный вывод!

# Правильный вариант с явными скобками
if age >= 18 and (has_license and is_sober):
    print("Можно водить")  # Не выведется, что правильно

Цепочки сравнений

Python поддерживает математические цепочки сравнений, что делает код более читаемым:

x = 15

# Вместо:
if x >= 10 and x <= 20:
    print("x в диапазоне 10-20")

# Можно написать:
if 10 <= x <= 20:
    print("x в диапазоне 10-20")

# Сложные цепочки
y = 25
if 0 < x < y < 100:
    print("x и y в допустимом диапазоне")

Сопоставление с образцом (Pattern Matching)

Начиная с Python 3.10 появилась мощная конструкция match-case, которая заменяет множественные ветвления if-elif:

def process_command(command):
    match command.split():
        case ["quit"]:
            return "Выход из программы"
        case ["load", filename]:
            return f"Загрузка файла: {filename}"
        case ["save", filename]:
            return f"Сохранение файла: {filename}"
        case ["help"] | ["?"]:
            return "Справка по командам"
        case _:
            return f"Неизвестная команда: {command}"

print(process_command("load data.txt"))  # Загрузка файла: data.txt
print(process_command("help"))           # Справка по командам
print(process_command("delete temp"))    # Неизвестная команда: delete temp

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

Циклы с индексами

Иногда нужно одновременно получить элемент и его индекс. Для этого используется функция enumerate():

fruits = ["яблоко", "банан", "вишня", "груша"]

# Без enumerate
for i in range(len(fruits)):
    print(f"{i + 1}. {fruits[i]}")

# С enumerate - более читаемо
for index, fruit in enumerate(fruits, start=1):
    print(f"{index}. {fruit}")

# 1. яблоко
# 2. банан
# 3. вишня
# 4. груша

Циклы по нескольким последовательностям

Функция zip() позволяет итерироваться по нескольким последовательностям одновременно:

names = ["Анна", "Борис", "Виктор"]
ages = [25, 32, 19]
cities = ["Москва", "Санкт-Петербург", "Новосибирск"]

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

# Анна, 25 лет, живет в Москва
# Борис, 32 лет, живет в Санкт-Петербург  
# Виктор, 19 лет, живет в Новосибирск

Генераторы и выражения-генераторы

Генераторы позволяют создавать эффективные циклы для больших объемов данных:

# Обычное списковое включение
squares = [x**2 for x in range(1000)]  # Создает весь список в памяти

# Генераторное выражение
squares_gen = (x**2 for x in range(1000))  # Создает генератор

# Использование генератора
for square in squares_gen:
    if square > 100:
        break
    print(square)

# Преимущество: генератор вычисляет значения по мере необходимости
# и не хранит весь список в памяти

Вложенные циклы и оптимизация

При работе с вложенными циклами важно учитывать производительность:

# Неэффективный вариант - O(n²)
def find_duplicates_slow(numbers):
    duplicates = []
    for i in range(len(numbers)):
        for j in range(i + 1, len(numbers)):
            if numbers[i] == numbers[j] and numbers[i] not in duplicates:
                duplicates.append(numbers[i])
    return duplicates

# Эффективный вариант с использованием множества - O(n)
def find_duplicates_fast(numbers):
    seen = set()
    duplicates = set()
    for num in numbers:
        if num in seen:
            duplicates.add(num)
        else:
            seen.add(num)
    return list(duplicates)

# Тест производительности
import time
large_list = list(range(10000)) + [42, 99, 150]

start = time.time()
result1 = find_duplicates_slow(large_list)
print(f"Медленный метод: {time.time() - start:.4f} сек")

start = time.time()  
result2 = find_duplicates_fast(large_list)
print(f"Быстрый метод: {time.time() - start:.4f} сек")
# Быстрый метод значительно эффективнее для больших данных

Обработка исключений

Исключения — это механизм обработки ошибок и неожиданных ситуаций в программе. В Python исключения генерируются при возникновении ошибок и могут быть перехвачены для обработки.

Базовая структура try-except

try:
    # Код, который может вызвать исключение
    number = int(input("Введите число: "))
    result = 100 / number
    print(f"Результат: {result}")
except ValueError:
    # Обработка ошибки преобразования в число
    print("Ошибка: Введено не числовое значение!")
except ZeroDivisionError:
    # Обработка деления на ноль
    print("Ошибка: Деление на ноль невозможно!")
except Exception as e:
    # Обработка всех остальных исключений
    print(f"Произошла неожиданная ошибка: {e}")
else:
    # Выполняется, если исключений не было
    print("Операция выполнена успешно")
finally:
    # Выполняется всегда, независимо от исключений
    print("Программа завершена")

Типы исключений и их иерархия

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

# Иерархия некоторых исключений:
# Exception
# ├── ArithmeticError
# │   ├── ZeroDivisionError
# │   └── OverflowError
# ├── LookupError
# │   ├── IndexError
# │   └── KeyError
# ├── ValueError
# ├── TypeError
# └── IOError
#     └── FileNotFoundError

# Пример правильного порядка обработки
def process_data(data):
    try:
        # Попытка получить элемент по индексу
        item = data[0]
        # Попытка разделить на этот элемент
        result = 10 / item
        return result
    except IndexError:
        print("Ошибка: Список пуст")
    except ZeroDivisionError:
        print("Ошибка: Деление на ноль")
    except TypeError:
        print("Ошибка: Неподдерживаемый тип данных")
    except Exception as e:
        print(f"Неожиданная ошибка: {e}")

Создание пользовательских исключений

Можно создавать свои типы исключений для специфических ситуаций:

# Создание пользовательского исключения
class InvalidAgeError(Exception):
    def __init__(self, age, message="Некорректный возраст"):
        self.age = age
        self.message = message
        super().__init__(self.message)
    
    def __str__(self):
        return f"{self.message}: {self.age}"

# Использование пользовательского исключения
def check_age(age):
    if not isinstance(age, int):
        raise TypeError("Возраст должен быть целым числом")
    if age < 0:
        raise InvalidAgeError(age, "Возраст не может быть отрицательным")
    if age > 150:
        raise InvalidAgeError(age, "Возраст слишком большой")
    return True

# Тестирование
try:
    check_age(25)    # OK
    check_age(-5)    # Вызовет InvalidAgeError
except InvalidAgeError as e:
    print(e)         # Возраст не может быть отрицательным: -5
except TypeError as e:
    print(e)

Контекстные менеджеры и исключения

Контекстные менеджеры (оператор with) автоматически обрабатывают исключения при работе с ресурсами:

# Без контекстного менеджера - ручное управление исключениями
file = None
try:
    file = open("data.txt", "r", encoding="utf-8")
    content = file.read()
    print(content)
except FileNotFoundError:
    print("Файл не найден!")
except UnicodeDecodeError:
    print("Ошибка декодирования файла!")
finally:
    if file:
        file.close()  # Обязательное закрытие файла

# С контекстным менеджером - автоматическое управление
try:
    with open("data.txt", "r", encoding="utf-8") as file:
        content = file.read()
        print(content)
except FileNotFoundError:
    print("Файл не найден!")
except UnicodeDecodeError:
    print("Ошибка декодирования файла!")
# Файл закроется автоматически даже при исключении

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

Пример 1: Валидация пользовательского ввода с повторными попытками

def get_valid_number(prompt, min_value=None, max_value=None, retries=3):
    """
    Получает валидное число от пользователя с ограничением попыток
    
    Args:
        prompt (str): Сообщение для пользователя
        min_value (int/float, optional): Минимальное значение
        max_value (int/float, optional): Максимальное значение  
        retries (int): Количество попыток
    
    Returns:
        int/float: Валидное число или None при превышении попыток
    """
    for attempt in range(retries):
        try:
            user_input = input(prompt)
            number = float(user_input)
            
            # Проверка диапазона
            if min_value is not None and number < min_value:
                raise ValueError(f"Число должно быть не меньше {min_value}")
            if max_value is not None and number > max_value:
                raise ValueError(f"Число должно быть не больше {max_value}")
            
            # Если введено целое число, преобразуем в int
            return int(number) if number.is_integer() else number
            
        except ValueError as e:
            print(f"Ошибка: {e}")
            print(f"Осталось попыток: {retries - attempt - 1}")
        except Exception as e:
            print(f"Неожиданная ошибка: {e}")
            print(f"Осталось попыток: {retries - attempt - 1}")
    
    print("Превышено количество попыток!")
    return None

# Использование
age = get_valid_number("Введите ваш возраст (18-100): ", 18, 100)
if age is not None:
    print(f"Ваш возраст: {age}")

Пример 2: Обработка сетевых запросов с повторными попытками

import requests
import time

def fetch_data_with_retry(url, max_retries=3, delay=1):
    """
    Получает данные по URL с повторными попытками при ошибке
    
    Args:
        url (str): URL для запроса
        max_retries (int): Максимальное количество попыток
        delay (int): Задержка между попытками в секундах
    
    Returns:
        dict: Данные в формате JSON или None при неудаче
    """
    for attempt in range(max_retries):
        try:
            print(f"Попытка {attempt + 1}/{max_retries}...")
            response = requests.get(url, timeout=5)
            response.raise_for_status()  # Вызовет исключение при HTTP ошибке
            return response.json()
            
        except requests.exceptions.ConnectionError:
            print("Ошибка соединения. Проверьте интернет-соединение.")
        except requests.exceptions.Timeout:
            print("Таймаут запроса. Сервер не отвечает.")
        except requests.exceptions.HTTPError as e:
            print(f"HTTP ошибка: {e.response.status_code}")
        except requests.exceptions.RequestException as e:
            print(f"Ошибка запроса: {e}")
        except ValueError:
            print("Ошибка декодирования JSON")
        
        if attempt < max_retries - 1:
            print(f"Повторная попытка через {delay} секунд...")
            time.sleep(delay)
        delay *= 2  # Экспоненциальная задержка
    
    print("Все попытки исчерпаны!")
    return None

# Использование (требует установки requests)
# data = fetch_data_with_retry("https://api.github.com/users/octocat")
# if data:
#     print(f"Имя пользователя: {data['name']}")

Пример 3: Безопасная работа с базой данных

import sqlite3
from contextlib import contextmanager

# Создание контекстного менеджера для базы данных
@contextmanager
def database_connection(db_name):
    conn = None
    try:
        conn = sqlite3.connect(db_name)
        conn.row_factory = sqlite3.Row  # Для доступа по именам колонок
        yield conn
    except sqlite3.Error as e:
        print(f"Ошибка базы данных: {e}")
        if conn:
            conn.rollback()
        raise
    finally:
        if conn:
            conn.close()

# Функция с обработкой исключений
def get_user_by_id(user_id):
    try:
        with database_connection("users.db") as conn:
            cursor = conn.cursor()
            cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
            user = cursor.fetchone()
            
            if user is None:
                raise ValueError(f"Пользователь с ID {user_id} не найден")
            
            return dict(user)  # Преобразуем в словарь для удобства
            
    except ValueError as e:
        print(f"Ошибка валидации: {e}")
        return None
    except sqlite3.Error as e:
        print(f"Ошибка базы данных: {e}")
        return None
    except Exception as e:
        print(f"Неожиданная ошибка: {e}")
        return None

# Использование
user = get_user_by_id(123)
if user:
    print(f"Имя пользователя: {user['name']}")
else:
    print("Пользователь не найден или произошла ошибка")

ЧЕК-ЛИСТ ПО СТАТЬЕ 1.8: УСЛОВНЫЙ ОПЕРАТОР, ЦИКЛЫ, ИСКЛЮЧЕНИЯ

Навык / ЗнаниеПонимаю и умею применятьНужно повторить
1 Условные операторы
2 Функции для работы с циклами
3 Базовые исключения
4 Пользовательские исключения
  • Создание своих классов исключений
  • Наследование от Exception
  • Пользовательские сообщения об ошибках
5 Контекстные менеджеры
  • Оператор with для ресурсов
  • Автоматическое закрытие файлов
  • Создание своих менеджеров
6 Валидация ввода
  • Проверка числовых значений
  • Ограничение количества попыток
  • Информативные сообщения об ошибках
7 Обработка сетевых ошибок
  • Повторные попытки запросов
  • Экспоненциальная задержка
  • Типы сетевых исключений
8 Работа с БД и файлами
  • Безопасное закрытие соединений
  • Откат транзакций при ошибках
  • Обработка специфичных ошибок БД
9 Лучшие практики
  • Не подавлять исключения без причины
  • Закрывать ресурсы в finally или with
  • Информативные сообщения об ошибках
10 Отладка и тестирование
  • Тестирование кода с исключениями
  • Логирование ошибок
  • Предоставление понятных сообщений пользователю

Заключение

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

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

В следующей статье мы рассмотрим функциональное программирование в Python и более продвинутые техники работы с данными.

Приложение 1: Исключения в Python

Ключевое слово raise используется в Python для явной генерации (выброса) исключений. Это позволяет создавать исключения при нарушении условий, повышать уровень перехваченных исключений или вызывать пользовательские исключения для специфичных ситуаций.

Таблицы исключений Python

Таблица 1: Основные встроенные исключения

КатегорияИсключениеКогда возникаетПример
Базовые Exception Базовый класс всех исключений except Exception as e:
BaseException Родитель всех исключений Редко используется напрямую
Арифметические ArithmeticError Базовый класс для арифметических ошибок В иерархии исключений
ZeroDivisionError Деление на ноль 10 / 0
OverflowError Результат операции слишком велик 10 ** 1000 (в некоторых случаях)
Типы данных TypeError Неправильный тип данных "5" + 3
ValueError Неправильное значение int("abc")
Работа с коллекциями LookupError Базовый класс для ошибок поиска В иерархии исключений
IndexError Индекс вне диапазона lst[10] для списка из 5 элементов
KeyError Ключ не найден в словаре dct["несуществующий_ключ"]
Ввод-вывод IOError/OSError Ошибка ввода-вывода Проблемы с чтением/записью файлов
FileNotFoundError Файл не найден open("несуществующий_файл.txt")
Атрибуты AttributeError Атрибут объекта не найден obj.несуществующий_атрибут
Импорт ImportError Ошибка импорта модуля import несуществующий_модуль

Таблица 2: Специализированные исключения

БиблиотекаИсключениеКогда возникаетПример обработки
Requests
(сетевые запросы)
ConnectionError Проблемы с сетевым соединением except requests.exceptions.ConnectionError:
Timeout Таймаут запроса except requests.exceptions.Timeout:
HTTPError HTTP ошибка (4xx, 5xx) except requests.exceptions.HTTPError:
RequestException Базовый класс всех исключений requests except requests.exceptions.RequestException:
SQLite3
(работа с БД)
sqlite3.Error Базовый класс ошибок SQLite except sqlite3.Error as e:
sqlite3.IntegrityError Нарушение целостности данных except sqlite3.IntegrityError:
sqlite3.OperationalError Ошибка операций с БД except sqlite3.OperationalError:
JSON json.JSONDecodeError Ошибка декодирования JSON except json.JSONDecodeError:

Практические примеры с raise

Пример 1: Генерация встроенных исключений
Python
def calculate_average(numbers):
    if not numbers:
        raise ValueError("Список чисел не должен быть пустым")
    if not all(isinstance(x, (int, float)) for x in numbers):
        raise TypeError("Все элементы должны быть числами")
    return sum(numbers) / len(numbers)
Пример 2: Повторный выброс исключения
Python
def process_file(filename):
    try:
        with open(filename, 'r') as file:
            return file.read()
    except PermissionError:
        print(f"Нет доступа к файлу {filename}")
        raise  # Повторно выбрасываем то же исключение
Пример 3: Замена типа исключения
Python
def parse_user_input(input_str):
    try:
        return int(input_str)
    except ValueError as e:
        raise ValueError(f"Невозможно преобразовать '{input_str}' в число") from e

Иерархия исключений (сокращенная)

BaseException
├── KeyboardInterrupt
├── SystemExit
└── Exception
    ├── ArithmeticError
    │   ├── ZeroDivisionError
    │   └── OverflowError
    ├── LookupError
    │   ├── IndexError
    │   └── KeyError
    ├── ValueError
    ├── TypeError
    ├── IOError
    │   └── FileNotFoundError
    └── [Пользовательские исключения]

Лучшие практики работы с исключениями

1
Конкретные исключения в первую очередь — обрабатывайте конкретные типы исключений перед общими
2
Не подавляйте без причины — избегайте пустых блоков except:
3
Информативные сообщения — всегда добавляйте понятное описание ошибки
4
Документируйте исключения — указывайте в docstring, какие исключения могут быть выброшены
5
Освобождайте ресурсы — используйте finally или контекстные менеджеры (with)
6
Свои исключения для бизнес-логики — создавайте пользовательские исключения для специфичных ситуаций

Шпаргалка по обработке исключений

Python
try:
    # Код, который может вызвать исключение
    risky_operation()
except SpecificException as e:
    # Обработка конкретного исключения
    print(f"Конкретная ошибка: {e}")
except (AnotherException, DifferentException) as e:
    # Обработка нескольких типов исключений
    print(f"Одна из ошибок: {e}")
except Exception as e:
    # Обработка всех остальных исключений
    print(f"Неожиданная ошибка: {e}")
    # Логирование или повторный выброс
    raise
else:
    # Выполняется, если исключений не было
    print("Операция успешна")
finally:
    # Выполняется всегда
    print("Завершение операции")

Примечание: Это приложение является дополнением к основной статье об условных операторах, циклах и исключениях. Здесь собрана справочная информация для быстрого доступа и повторения материала.

Ссылки по теме

Конспект:
Пятница, 05 декабря 2025
Условный оператор. Циклы. Исключения [1.8 ]