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

k09. классы, dataclasses и Enum

k09. классы, dataclasses и Enum

UAV Python Pro · Конспект K09 · Модуль M1. Python intensive: классы, dataclasses и Enum · Редакция 1.0 · После CHECKPOINT-K08

До сих пор телеметрию и точки маршрута вы часто описывали словарями и кортежами. Такой способ работает, но имена полей легко перепутать, а допустимые режимы полёта остаются «просто строками». На этом уроке вы научитесь собирать данные и поведение в типы: обычный класс с методами, dataclass для снимков состояния, Enum для ограниченного набора режимов и композицию объектов. Практика выполняется в виртуальном окружении. Полные тексты файлов k09_task*.py вложены в аккордеоны раздела практики.

Как устроены примеры. В теории каждый важный фрагмент кода дан дважды: сначала блок с подписью «Чистый код», затем раскрывающийся построчный разбор. В практике для каждой задачи в аккордеоне приведён полный исходный текст файла.

Главная мысль урока состоит в следующем. Класс описывает, какие данные хранит объект и какие действия с ними допустимы. Dataclass сокращает шаблонный код для структур данных вроде отсчёта телеметрии или точки маршрута. Enum фиксирует конечный набор допустимых значений, например режимов полёта, и снижает число ошибок из-за опечаток в строках. Композиция («объект содержит другие объекты») позволяет собирать статус аппарата из телеметрии, монитора батареи и других частей, не смешивая всё в одном гигантском словаре.

Как следует читать код на сайте. Знаки сравнения и угловые скобки в примерах могут отображаться полноширинными символами «<» и «>» для безопасной публикации в Joomla ZOO. В среде PyCharm вы набираете обычные клавиши языка Python. Файлы практики на диске проекта содержат нормальный синтаксис Python.

Содержание

1. Цели урока

После изучения этого урока вы будете уметь выполнять следующие действия.

  • Вы будете объявлять класс с методом __init__ и обычными методами экземпляра.
  • Вы будете объяснять разницу между классом и экземпляром (объектом).
  • Вы будете создавать dataclass для снимка телеметрии и вызывать его методы.
  • Вы будете описывать ограниченный набор режимов полёта через Enum и сравнивать элементы перечисления.
  • Вы будете собирать составной объект по принципу композиции.
  • Вы будете моделировать учебную точку маршрута и миссию списком точек.
  • Вы будете понимать, когда словарь уже неудобен и когда тип с полями читается лучше.
Связь с разработкой программного обеспечения для БПЛА. Сообщения телеметрии, пункты маршрута, режимы автопилота и состояние канала связи — это сущности с устойчивыми полями. Если дать им имена типов, код наземной станции и код бортового компьютера-компаньона становится короче и безопаснее при изменениях. Позднее те же идеи лягут на разбор сообщений MAVLink и на построитель миссий.
↑ К оглавлению

2. Словарь урока

ТерминПростыми словами
Класс Описание типа объектов: какие поля они хранят и какие методы к ним применимы.
Экземпляр (объект) Конкретная «вещь», созданная по классу, со своими значениями полей.
__init__ Специальный метод, который вызывается при создании экземпляра.
self Имя текущего экземпляра внутри методов класса.
Метод Функция, объявленная внутри класса и работающая с экземпляром.
dataclass Декоратор, который автоматически создаёт шаблонный код для класса данных.
Enum Перечисление: фиксированный набор именованных допустимых значений.
Композиция Способ сборки: объект содержит внутри себя другие объекты и использует их.
frozen dataclass Dataclass, поля которого нельзя изменить после создания экземпляра.
↑ К оглавлению

3. Зачем типы объектов в программах для БПЛА

Словарь удобен на первых шагах. Однако запись sample["altitud_m"] с опечаткой в ключе проявляется поздно, а строка режима "GUDED" не будет поймана до сравнения с эталоном. Класс и dataclass делают набор полей явным. Enum ограничивает множество допустимых режимов. Композиция позволяет не сваливать батарею, координаты и флаги связи в один плоский dict на сотню ключей.

Это не «академическое ООП ради ООП». Это способ уменьшить число скрытых ошибок в коде, который потом будет рядом с автопилотом.

↑ К оглавлению

4. Класс, экземпляр, __init__ и метод

Класс — это описание типа объектов. Экземпляр, или объект, — конкретная сущность, созданная по этому описанию. Метод __init__ выполняется при создании экземпляра и обычно сохраняет начальные данные в полях. Имя self внутри методов обозначает текущий экземпляр.

Чистый код

# Пример: простой класс монитора напряжения
class BatteryMonitor:
    """Учебный монитор напряжения бортовой сети."""

    def __init__(self, low_v: float = 14.4, critical_v: float = 14.0) -> None:
        self.low_v = low_v
        self.critical_v = critical_v

    def classify(self, voltage_v: float) -> str:
        if voltage_v >= self.low_v:
            return "OK"
        if voltage_v >= self.critical_v:
            return "LOW"
        return "CRITICAL"


monitor = BatteryMonitor()
print(monitor.classify(15.1))
print(monitor.classify(13.7))
Python
  • Построчный разбор

    Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с последовательности символов «# →», содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.

    class BatteryMonitor:
    # → Объявляем класс — чертёж объектов одного типа.
    
        """Учебный монитор напряжения бортовой сети."""
    # → Docstring класса: кратко, для чего этот тип нужен.
    
        def __init__(self, low_v: float = 14.4, critical_v: float = 14.0) -> None:
    # → __init__ вызывается при создании объекта. self — сам создаваемый экземпляр.
    
            self.low_v = low_v
    # → Сохраняем порог «низкое напряжение» в поле экземпляра.
    
            self.critical_v = critical_v
    # → Сохраняем порог «критическое напряжение».
    
        def classify(self, voltage_v: float) -> str:
    # → Метод экземпляра: работает с self и входным напряжением.
    
            if voltage_v >= self.low_v:
    # → Сравниваем с порогом, который хранится в объекте.
    
                return "OK"
    # → Напряжение не ниже порога low.
    
            if voltage_v >= self.critical_v:
    # → Иначе проверяем критический порог.
    
                return "LOW"
    # → Между critical и low.
    
            return "CRITICAL"
    # → Ниже критического порога.
    
    monitor = BatteryMonitor()
    # → Создаём объект (экземпляр) с порогами по умолчанию.
    
    print(monitor.classify(15.1))
    # → Вызов метода у конкретного объекта.
    
    print(monitor.classify(13.7))
    # → Другое напряжение — другая метка.
    Python
Пороги в примере учебные. Числа 14.4 и 14.0 не следует переносить на реальный борт без методики и понимания единиц конкретной прошивки. Здесь важен механизм класса, а не настройка автопилота.
Связь с практикой. Полный текст файла k09_task1_simple_class.py приведён в задаче 9.1.
↑ К оглавлению

5. Dataclass для снимков данных

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

Чистый код

# Пример: dataclass снимка телеметрии
from dataclasses import dataclass

@dataclass
class TelemetrySample:
    """Один учебный отсчёт телеметрии."""

    t_s: float
    altitude_m: float
    mode: str
    voltage_v: float

    def is_airborne(self, min_alt_m: float = 1.0) -> bool:
        return self.altitude_m >= min_alt_m


s = TelemetrySample(1.0, 8.0, "GUIDED", 15.8)
print(s.mode, s.altitude_m, s.is_airborne())
Python
  • Построчный разбор

    Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с последовательности символов «# →», содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.

    from dataclasses import dataclass
    # → Подключаем декоратор dataclass из стандартной библиотеки.
    
    @dataclass
    # → Декоратор сам создаёт __init__ и удобное представление объекта по аннотированным полям.
    
    class TelemetrySample:
    # → Имя типа для одного отсчёта телеметрии.
    
        """Один учебный отсчёт телеметрии."""
    # → Краткое описание класса.
    
        t_s: float
    # → Поле: время в секундах.
    
        altitude_m: float
    # → Поле: высота в метрах.
    
        mode: str
    # → Поле: режим полёта строкой (ниже заменим на Enum).
    
        voltage_v: float
    # → Поле: напряжение в вольтах.
    
        def is_airborne(self, min_alt_m: float = 1.0) -> bool:
    # → Обычный метод можно добавлять в dataclass так же, как в обычный класс.
    
            return self.altitude_m >= min_alt_m
    # → Учебное правило «в воздухе» по высоте.
    
    s = TelemetrySample(1.0, 8.0, "GUIDED", 15.8)
    # → Создание экземпляра: аргументы соответствуют полям по порядку.
    
    print(s.mode, s.altitude_m, s.is_airborne())
    # → Обращение к полям и вызов метода.
    Python
Когда dataclass уместен. Когда объект в основном хранит данные и немного простых методов. Когда логика создания сложна, а состояние богатое, обычный класс с явным __init__ может быть яснее. На курсе для TelemetrySample и Waypoint dataclass — хороший основной инструмент.
Связь с практикой. Полный текст файла k09_task2_dataclass_telemetry.py приведён в задаче 9.2.
↑ К оглавлению

6. Enum для режимов полёта

Enum (перечисление) задаёт конечный набор именованных допустимых значений. Вместо произвольной строки "GUIDED" вы работаете с элементом FlightMode.GUIDED. Опечатка в недопустимом режиме проявляется раньше, а сравнения становятся однозначнее.

Чистый код

# Пример: Enum режимов полёта
from enum import Enum

class FlightMode(Enum):
    GUIDED = "GUIDED"
    AUTO = "AUTO"
    RTL = "RTL"

def needs_mission(mode: FlightMode) -> bool:
    return mode is FlightMode.AUTO

mode = FlightMode("GUIDED")
print(mode, mode.value, needs_mission(mode))
Python
  • Построчный разбор

    Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с последовательности символов «# →», содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.

    from enum import Enum
    # → Подключаем базовый класс перечислений.
    
    class FlightMode(Enum):
    # → Объявляем перечисление допустимых учебных режимов.
    
        GUIDED = "GUIDED"
    # → Элемент перечисления: имя GUIDED и значение-строка "GUIDED".
    
        AUTO = "AUTO"
    # → Элемент AUTO.
    
        RTL = "RTL"
    # → Элемент RTL (возврат к точке взлёта).
    
    def needs_mission(mode: FlightMode) -> bool:
    # → Функция принимает именно FlightMode, а не произвольную строку.
    
        return mode is FlightMode.AUTO
    # → Сравнение через is уместно для членов одного Enum.
    
    mode = FlightMode("GUIDED")
    # → Построение Enum из строки значения (как из телеметрии).
    
    print(mode, mode.value, needs_mission(mode))
    # → mode — элемент Enum; value — строка; needs_mission — учебное правило.
    Python
Связь с автопилотом. Реальные перечни режимов ArduPilot шире учебного набора. Здесь Enum демонстрирует принцип. Позднее строку из телеметрии можно преобразовывать в Enum и отдельно обрабатывать неизвестные значения.
Связь с практикой. Полный текст файла k09_task3_enum_modes.py приведён в задаче 9.3.
↑ К оглавлению

7. Композиция объектов

Композиция — это способ сборки системы, при котором один объект содержит внутри себя другие объекты и опирается на их методы. Формулировка «аппарат имеет телеметрию и монитор батареи» обычно точнее, чем попытка унаследовать «аппарат от батареи».

Чистый код

# Пример: композиция VehicleStatus
from dataclasses import dataclass
from enum import Enum

class FlightMode(Enum):
    GUIDED = "GUIDED"

@dataclass
class TelemetrySample:
    altitude_m: float
    mode: FlightMode
    voltage_v: float

class BatteryMonitor:
    def __init__(self, low_v: float = 14.4) -> None:
        self.low_v = low_v

    def is_low(self, voltage_v: float) -> bool:
        return voltage_v < self.low_v

class VehicleStatus:
    def __init__(self, telemetry: TelemetrySample, battery: BatteryMonitor) -> None:
        self.telemetry = telemetry
        self.battery = battery

    def summary(self) -> str:
        batt = "LOW" if self.battery.is_low(self.telemetry.voltage_v) else "OK"
        return f"mode={self.telemetry.mode.value} alt={self.telemetry.altitude_m} batt={batt}"

status = VehicleStatus(
    TelemetrySample(15.0, FlightMode.GUIDED, 14.1),
    BatteryMonitor(),
)
print(status.summary())
Python
  • Построчный разбор

    Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с последовательности символов «# →», содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.

    class FlightMode(Enum):
    # → Режим полёта как Enum.
    
        GUIDED = "GUIDED"
    # → Учебный элемент перечисления.
    
    @dataclass
    # → Телеметрия — данные без сложной логики создания.
    
    class TelemetrySample:
    # → Снимок состояния.
    
        altitude_m: float
    # → Высота.
    
        mode: FlightMode
    # → Режим уже не «голая» строка, а Enum.
    
        voltage_v: float
    # → Напряжение.
    
    class BatteryMonitor:
    # → Отдельный объект с правилом оценки батареи.
    
        def __init__(self, low_v: float = 14.4) -> None:
    # → Порог задаётся при создании монитора.
    
            self.low_v = low_v
    # → Храним порог в экземпляре.
    
        def is_low(self, voltage_v: float) -> bool:
    # → Метод проверки.
    
            return voltage_v < self.low_v
    # → True, если напряжение ниже порога.
    
    class VehicleStatus:
    # → Сборка: статус аппарата «имеет» телеметрию и монитор.
    
        def __init__(self, telemetry: TelemetrySample, battery: BatteryMonitor) -> None:
    # → В конструктор передаём уже созданные части.
    
            self.telemetry = telemetry
    # → Сохраняем ссылку на телеметрию.
    
            self.battery = battery
    # → Сохраняем ссылку на монитор батареи.
    
        def summary(self) -> str:
    # → Метод сводки для печати.
    
            batt = "LOW" if self.battery.is_low(self.telemetry.voltage_v) else "OK"
    # → Композиция: status использует battery и telemetry вместе.
    
            return f"mode={self.telemetry.mode.value} alt={self.telemetry.altitude_m} batt={batt}"
    # → Собираем одну строку состояния.
    
    status = VehicleStatus(
    # → Создаём сборку.
    
        TelemetrySample(15.0, FlightMode.GUIDED, 14.1),
    # → Телеметрия как отдельный объект.
    
        BatteryMonitor(),
    # → Монитор с порогом по умолчанию.
    
    )
    # → Конец создания VehicleStatus.
    
    print(status.summary())
    # → Печатаем сводку.
    Python
Практический смысл. Композиция позволяет заменять части: другой BatteryMonitor с иными порогами, другая телеметрия — при том же интерфейсе summary. Это пригодится в тестах с заглушками и в разбиении companion-сервиса на модули.
Связь с практикой. Полный текст файла k09_task4_composition.py приведён в задаче 9.4.
↑ К оглавлению

8. Точка маршрута и миссия

frozen dataclass — это dataclass с параметром frozen=True. После создания экземпляра поля нельзя изменить. Для координат точки маршрута такая защита уменьшает риск случайной порчи данных в середине построения миссии.

Чистый код

# Пример: точка маршрута и миссия
from dataclasses import dataclass, field

@dataclass(frozen=True)
class Waypoint:
    lat: float
    lon: float
    alt_m: float
    name: str = ""

@dataclass
class Mission:
    home: Waypoint
    points: list[Waypoint] = field(default_factory=list)

    def add(self, wp: Waypoint) -> None:
        self.points.append(wp)

    def count(self) -> int:
        return len(self.points)

mission = Mission(home=Waypoint(55.75, 37.61, 0.0, "HOME"))
mission.add(Waypoint(55.76, 37.62, 40.0, "WP1"))
print(mission.count(), mission.points[0].name)
Python
  • Построчный разбор

    Ниже приведён тот же фрагмент с пояснением после каждой смысловой строки. Строки, которые начинаются с последовательности символов «# →», содержат комментарий преподавателя. Эти строки не являются обязательной частью программы.

    from dataclasses import dataclass, field
    # → field нужен для корректного значения списка по умолчанию.
    
    @dataclass(frozen=True)
    # → frozen=True: после создания менять поля точки нельзя — меньше случайных правок координат.
    
    class Waypoint:
    # → Учебная точка маршрута.
    
        lat: float
    # → Широта.
    
        lon: float
    # → Долгота.
    
        alt_m: float
    # → Высота в метрах.
    
        name: str = ""
    # → Необязательное имя точки.
    
    @dataclass
    # → Миссия изменяема: в неё добавляют точки.
    
    class Mission:
    # → Упорядоченный набор точек после home.
    
        home: Waypoint
    # → Точка «дом» хранится отдельно.
    
        points: list[Waypoint] = field(default_factory=list)
    # → default_factory=list — у каждой миссии свой новый список, а не один общий на всех.
    
        def add(self, wp: Waypoint) -> None:
    # → Добавление маршрутной точки.
    
            self.points.append(wp)
    # → Точка попадает в конец списка.
    
        def count(self) -> int:
    # → Сколько маршрутных точек (без home).
    
            return len(self.points)
    # → Длина списка points.
    
    mission = Mission(home=Waypoint(55.75, 37.61, 0.0, "HOME"))
    # → Создаём миссию с домашней точкой.
    
    mission.add(Waypoint(55.76, 37.62, 40.0, "WP1"))
    # → Добавляем первую маршрутную точку.
    
    print(mission.count(), mission.points[0].name)
    # → Печатаем число точек и имя первой.
    Python
Это не протокол миссий MAVLink. Класс Mission в уроке — учебная модель списка точек. Загрузка миссии в автопилот, поля frame и command, подтверждения ACK будут в отдельных конспектах модуля о миссиях. Сейчас важна структура данных в Python.
Связь с практикой. Полный текст файла k09_task5_waypoint_mission.py приведён в задаче 9.5.
↑ К оглавлению

9. Типичные ошибки

ОшибкаЧто происходитКак следует рассуждать
Забыли self в методе Ошибка о числе аргументов У методов экземпляра первый параметр — self
Список по умолчанию в поле dataclass как [] Один список делится между экземплярами Используйте field(default_factory=list)
Сравнивают Enum со строкой через == без .value Сравнение даёт False Сравнивайте с FlightMode.AUTO или используйте .value осознанно
Меняют поля frozen-объекта FrozenInstanceError Создайте новый экземпляр или снимите frozen, если изменение необходимо
Класс «на всё» с десятками ответственностей Код трудно менять и тестировать Делите по смыслу: телеметрия, батарея, миссия
↑ К оглавлению

10. Практика

В каждой карточке задачи ниже в раскрывающемся блоке приведён полный текст соответствующего учебного файла. Этот текст совпадает с файлами в каталоге code/M01_python/. Рекомендуется такой порядок работы. Сначала внимательно прочитайте условие. Затем раскройте аккордеон, скопируйте код в файл проекта под тем же именем, запустите его в виртуальном окружении и сверьте вывод с ожидаемым результатом.

Задача 9.1. Простой класс

Файл практики: k09_task1_simple_class.py.

Создайте класс BatteryMonitor с порогами в __init__ и методом classify. Проверьте несколько напряжений, в том числе объект со своими порогами.

Ожидаемый результат. Для 15.1 ожидается OK, для 14.2 — LOW при порогах по умолчанию, для 13.7 — CRITICAL. Для strict-объекта 14.5 должно дать LOW.

  • Полный текст файла k09_task1_simple_class.py

    Скопируйте приведённое ниже содержимое в файл code/M01_python/k09_task1_simple_class.py в проекте PyCharm (виртуальное окружение) и запустите программу. Ниже приведён полный учебный текст файла с комментариями.

    # k09_task1_simple_class.py
    # Цель: простой класс с __init__ и методом.
    # Запуск: venv проекта.
    
    class BatteryMonitor:
        """Учебный монитор напряжения бортовой сети.
    
        Это не драйвер реального датчика. Класс только хранит пороги
        и отвечает, в каком учебном диапазоне находится напряжение.
        """
    
        def __init__(self, low_v: float = 14.4, critical_v: float = 14.0) -> None:
            # Пороги задаём при создании объекта и храним в полях экземпляра
            self.low_v = low_v
            self.critical_v = critical_v
    
        def classify(self, voltage_v: float) -> str:
            """Вернуть учебную метку: OK, LOW или CRITICAL."""
            if voltage_v >= self.low_v:
                return "OK"
            if voltage_v >= self.critical_v:
                return "LOW"
            return "CRITICAL"
    
    
    monitor = BatteryMonitor()
    print(monitor.classify(15.1))
    print(monitor.classify(14.2))
    print(monitor.classify(13.7))
    
    strict = BatteryMonitor(low_v=14.8, critical_v=14.2)
    print("strict:", strict.classify(14.5))
    Python

Задача 9.2. Dataclass телеметрии

Файл практики: k09_task2_dataclass_telemetry.py.

Опишите TelemetrySample как dataclass, добавьте метод is_airborne и обойдите список образцов.

Ожидаемый результат. Три строки печати с метками ground или air в зависимости от высоты.

  • Полный текст файла k09_task2_dataclass_telemetry.py

    Скопируйте приведённое ниже содержимое в файл code/M01_python/k09_task2_dataclass_telemetry.py в проекте PyCharm (виртуальное окружение) и запустите программу. Ниже приведён полный учебный текст файла с комментариями.

    # k09_task2_dataclass_telemetry.py
    # Цель: dataclass для снимка телеметрии; method; список объектов.
    
    from dataclasses import dataclass
    
    
    @dataclass
    class TelemetrySample:
        """Один учебный отсчёт телеметрии в заданный момент времени."""
    
        t_s: float
        altitude_m: float
        mode: str
        voltage_v: float
    
        def is_airborne(self, min_alt_m: float = 1.0) -> bool:
            """True, если высота не ниже учебного порога «в воздухе»."""
            return self.altitude_m >= min_alt_m
    
    
    samples = [
        TelemetrySample(0.0, 0.3, "STABILIZE", 16.0),
        TelemetrySample(1.0, 8.0, "GUIDED", 15.8),
        TelemetrySample(2.0, 20.0, "AUTO", 15.6),
    ]
    
    for s in samples:
        flag = "air" if s.is_airborne() else "ground"
        print(f"t={s.t_s} alt={s.altitude_m} mode={s.mode} [{flag}]")
    Python

Задача 9.3. Enum режимов

Файл практики: k09_task3_enum_modes.py.

Объявите FlightMode, функции needs_mission и parse_mode. Обработайте строки GUIDED, AUTO и ACRO.

Ожидаемый результат. GUIDED и AUTO распознаются; для AUTO mission? True; ACRO даёт unknown mode.

  • Полный текст файла k09_task3_enum_modes.py

    Скопируйте приведённое ниже содержимое в файл code/M01_python/k09_task3_enum_modes.py в проекте PyCharm (виртуальное окружение) и запустите программу. Ниже приведён полный учебный текст файла с комментариями.

    # k09_task3_enum_modes.py
    # Цель: Enum для режимов полёта; безопасное сравнение.
    
    from enum import Enum
    
    
    class FlightMode(Enum):
        """Учебное перечисление режимов (не полный список ArduPilot)."""
    
        STABILIZE = "STABILIZE"
        LOITER = "LOITER"
        GUIDED = "GUIDED"
        AUTO = "AUTO"
        RTL = "RTL"
        LAND = "LAND"
    
    
    def needs_mission(mode: FlightMode) -> bool:
        """Нужен ли загруженный маршрут для этого режима (учебное правило)."""
        return mode is FlightMode.AUTO
    
    
    def parse_mode(raw: str) -> FlightMode | None:
        """Преобразовать строку телеметрии в Enum или вернуть None."""
        try:
            return FlightMode(raw)
        except ValueError:
            return None
    
    
    for name in ("GUIDED", "AUTO", "ACRO"):
        mode = parse_mode(name)
        if mode is None:
            print(name, "-> unknown mode")
        else:
            print(name, "->", mode.name, "mission?", needs_mission(mode))
    Python

Задача 9.4. Композиция

Файл практики: k09_task4_composition.py.

Соберите VehicleStatus из TelemetrySample и BatteryMonitor и напечатайте summary.

Ожидаемый результат. Строка вида mode=GUIDED alt=15.0 batt=LOW при напряжении 14.1 и пороге 14.4.

  • Полный текст файла k09_task4_composition.py

    Скопируйте приведённое ниже содержимое в файл code/M01_python/k09_task4_composition.py в проекте PyCharm (виртуальное окружение) и запустите программу. Ниже приведён полный учебный текст файла с комментариями.

    # k09_task4_composition.py
    # Цель: композиция — объект «аппарат» содержит телеметрию и монитор батареи.
    
    from dataclasses import dataclass
    from enum import Enum
    
    
    class FlightMode(Enum):
        GUIDED = "GUIDED"
        AUTO = "AUTO"
        RTL = "RTL"
    
    
    @dataclass
    class TelemetrySample:
        altitude_m: float
        mode: FlightMode
        voltage_v: float
    
    
    class BatteryMonitor:
        def __init__(self, low_v: float = 14.4) -> None:
            self.low_v = low_v
    
        def is_low(self, voltage_v: float) -> bool:
            return voltage_v < self.low_v
    
    
    class VehicleStatus:
        """Учебная сборка: текущая телеметрия + проверка батареи."""
    
        def __init__(self, telemetry: TelemetrySample, battery: BatteryMonitor) -> None:
            # Композиция: VehicleStatus «имеет» телеметрию и монитор
            self.telemetry = telemetry
            self.battery = battery
    
        def summary(self) -> str:
            low = self.battery.is_low(self.telemetry.voltage_v)
            batt = "LOW" if low else "OK"
            return (
                f"mode={self.telemetry.mode.value} "
                f"alt={self.telemetry.altitude_m:.1f} "
                f"batt={batt}"
            )
    
    
    tel = TelemetrySample(altitude_m=15.0, mode=FlightMode.GUIDED, voltage_v=14.1)
    status = VehicleStatus(tel, BatteryMonitor())
    print(status.summary())
    Python

Задача 9.5. Точка маршрута и миссия

Файл практики: k09_task5_waypoint_mission.py.

Реализуйте frozen Waypoint и Mission с add, count и highest_alt_m.

Ожидаемый результат. points: 2; highest_alt_m: 50.0; печать WP1 и WP2.

  • Полный текст файла k09_task5_waypoint_mission.py

    Скопируйте приведённое ниже содержимое в файл code/M01_python/k09_task5_waypoint_mission.py в проекте PyCharm (виртуальное окружение) и запустите программу. Ниже приведён полный учебный текст файла с комментариями.

    # k09_task5_waypoint_mission.py
    # Цель: dataclass точки маршрута + класс миссии со списком точек.
    
    from dataclasses import dataclass, field
    
    
    @dataclass(frozen=True)
    class Waypoint:
        """Учебная точка маршрута. frozen=True — после создания поля не меняем."""
    
        lat: float
        lon: float
        alt_m: float
        name: str = ""
    
    
    @dataclass
    class Mission:
        """Упорядоченный список точек (учебная миссия, не протокол MAVLink)."""
    
        home: Waypoint
        points: list[Waypoint] = field(default_factory=list)
    
        def add(self, wp: Waypoint) -> None:
            self.points.append(wp)
    
        def count(self) -> int:
            # home отдельно; points — маршрутные точки после home
            return len(self.points)
    
        def highest_alt_m(self) -> float:
            alts = [self.home.alt_m] + [p.alt_m for p in self.points]
            return max(alts)
    
    
    mission = Mission(home=Waypoint(55.751244, 37.618423, 0.0, "HOME"))
    mission.add(Waypoint(55.752000, 37.620000, 40.0, "WP1"))
    mission.add(Waypoint(55.753500, 37.615000, 50.0, "WP2"))
    
    print("points:", mission.count())
    print("highest_alt_m:", mission.highest_alt_m())
    for i, wp in enumerate(mission.points, start=1):
        print(f"{i}. {wp.name} alt={wp.alt_m}")
    Python

Задача 9.6. Сохранение в Git

Сохраните практику в истории Git.

git add code/M01_python/k09_*.py
git commit -m "K09: classes dataclasses enum composition mission"
Plain text
↑ К оглавлению

11. Проверьте себя

  1. Чем класс отличается от экземпляра? Приведите пример из монитора батареи.
  2. Зачем в методах экземпляра нужен параметр self?
  3. Какую задачу решает декоратор @dataclass по сравнению с ручным __init__?
  4. Почему режим полёта удобнее хранить в Enum, а не только в str?
  5. Что такое композиция? Чем фраза «статус имеет телеметрию» отличается от наследования «статус является батареей»?
  6. Зачем для Waypoint указывают frozen=True?
  7. Почему default_factory=list используют для поля points, а не points=[] в определении класса?
  8. Почему учебный класс Mission ещё не является загрузкой миссии в автопилот по MAVLink?
↑ К оглавлению

12. Чек-лист самопроверки

  • Я объясняю класс, экземпляр, __init__ и self законченными предложениями.
  • Я создаю dataclass с полями и методом.
  • Я объявляю Enum и преобразую строку в элемент перечисления.
  • Я собираю объект по композиции из нескольких частей.
  • Я моделирую Waypoint и Mission и понимаю ограничение frozen.
  • Я открывал аккордеоны практики и сверял полный код с запуском в виртуальном окружении.
  • Я запускал k09_task1 … k09_task5 в venv.
  • Я сделал git commit по K09.
Результат. Когда пункты закрыты, напишите ментору: K09 чек-лист закрыт. Далее следует урок K10 о потоках, asyncio и очереди сообщений — то есть о том, как совмещать приём телеметрии и отправку команд.
↑ К оглавлению

13. Что дальше

  • Предыдущий урок: K08 · Исключения, журналирование и отладка
  • Текущий урок: K09 · Классы, dataclasses и Enum
  • Следующий урок: K10 · Потоки, asyncio, очередь сообщений
  • Горизонт модуля M1: K12 и контрольная точка G1
Стиль изложения. Текст следует голосу внимательного преподавателя: законченные предложения, явные определения, терминология БПЛА, без сленга. Подпись теоретического блока dual-code — «Чистый код». Практика содержит полный исходный текст в аккордеоне. Стандарт: docs/05_EDITOR_STYLE_GUIDE.md.
↑ К оглавлению

 

 

Вторник, 25 августа 2026
k09. классы, dataclasses и Enum