k05. Управление потоком и функции
UAV Python Pro · Конспект K05 · Модуль M1. Python intensive: управление потоком и функции · Редакция 1.2 · После CHECKPOINT-K04
На занятии K03 вы уже знакомились с простыми условными ветвлениями, которые записываются с помощью ключевого слова if. На занятии K04 вы научились хранить связанные значения в структурах list и dict. Теперь нам необходимо научиться, во-первых, выполнять одно и то же действие многократно, то есть пользоваться циклами, и, во-вторых, выносить повторяющуюся логику в отдельные именованные блоки кода, которые называются функциями. Каждая функция имеет собственный вход, то есть набор принимаемых данных, и собственный выход, то есть возвращаемый результат. Без этих средств программы для обработки телеметрии и для проверок безопасности постепенно превращаются в длинный, монолитный код, который трудно читать и трудно изменять. Практические задания выполняются в виртуальном окружении проекта K01; соответствующие файлы расположены в каталоге code/M01_python/k05_*.py.
Главная мысль этого занятия состоит из двух положений. Первое положение относится к циклам. Цикл необходим тогда, когда одно и то же действие требуется выполнить многократно; такое повторение производится либо для каждого элемента какой-либо коллекции, либо до тех пор, пока некоторое условие остаётся истинным. Второе положение относится к функциям. Функция необходима тогда, когда повторяющемуся действию требуется дать собственное имя, передать в него входные данные и получить от него результат. Для инженера, который работает с БПЛА, полезно выработать устойчивую привычку: разделять чистый расчёт и побочный эффект. Чистым расчётом мы называем функцию, которая только вычисляет и возвращает ответ. Побочным эффектом мы называем то, что функция делает сверх возвращаемого значения, например печатает текст на экран; позднее такая функция может отправлять команду по протоколу MAVLink. Допустимо объединять расчёт и побочный эффект в одной функции, однако отладка подобного кода потребует больше усилий.
code/M01_python/k05_task1_while_countdown.py и соседние файлы, имена которых начинаются с префикса k05_ и заканчиваются расширением .py.Содержание
1. Цели урока
По завершении этого занятия вы будете обладать следующими умениями.
- Вы сможете объяснить различие между циклом
forи цикломwhileи выбрать из них тот, который соответствует поставленной задаче. - Вы научитесь осознанно применять операторы
breakиcontinue. - Вы научитесь объявлять функцию с помощью ключевого слова
def, передавать в неё аргументы и возвращать результат с помощью оператораreturn. - Вы научитесь писать краткий docstring, то есть описание функции, сформулированное своими словами.
- Вы научитесь задавать аргументам значения по умолчанию и вызывать функцию с указанием имён аргументов.
- Вы научитесь читать и записывать простые подсказки типов вида
(x: float) -> str. - Вы научитесь отличать чистую функцию, которая только вычисляет результат, от функции с побочным эффектом, которая, например, печатает текст, а позднее сможет отправлять команду.
- Вы научитесь собирать несколько небольших функций для работы со структурой
list[dict], в которой хранится телеметрия из занятия K04.
2. Словарь урока
| Термин | Объяснение простыми словами |
|---|---|
| Цикл | Конструкция языка, которая многократно повторяет выполнение некоторого блока кода. |
| for | Цикл вида «для каждого элемента коллекции», например для каждого элемента списка или строки. |
| while | Цикл вида «пока условие остаётся истинным». Ответственность за то, чтобы условие в определённый момент стало ложным, лежит на программисте. |
| Итерация | Одно однократное выполнение тела цикла. |
| break | Оператор, который немедленно завершает выполнение текущего цикла. |
| continue | Оператор, который пропускает оставшуюся часть тела цикла и переходит к следующей итерации. |
| Функция | Именованный блок кода, который можно вызывать многократно, передавая ему различные данные. |
| def | Ключевое слово Python, которое означает «объявляю функцию». |
| Параметр | Имя входа функции, которое указывается в заголовке, записанном после ключевого слова def. Например, в объявлении def f(voltage_v): слово voltage_v является параметром. |
| Аргумент | Конкретное значение, которое передаётся в функцию при её вызове. В вызове f(15.0) число 15.0 является аргументом. |
| return | Оператор, который возвращает результат из функции в ту точку программы, откуда эта функция была вызвана, и завершает выполнение функции. |
| None | Специальное значение «ничего». Если оператор return в функции отсутствует, функция всё равно возвращает значение None. |
| docstring | Строка документации, которая располагается сразу под строкой def и заключается в тройные кавычки. Она описывает, что именно делает функция. |
| Type hint | Подсказка о типах данных, которая указывается в заголовке функции. На начальном этапе обучения Python не проверяет такие подсказки строго, однако они облегчают чтение кода. |
| Побочный эффект (side effect) | Действие, которое функция выполняет сверх возврата значения: печать текста, запись в файл, отправка команды, изменение глобальных данных. |
| Чистая функция | Функция, которая ведёт себя как математическая формула: её результат зависит только от входных данных, она не изменяет окружение и, в частности, не печатает текст и не отправляет команды. |
3. Циклы for и while
Сначала мы рассмотрим два вида циклов по отдельности. В конце этого раздела мы поместим отдельный блок, который объясняет, как выбрать между циклами for и while; в этом блоке будут приведены примеры из повседневной жизни и из области беспилотной авиации, а также будет дано полное, а не сокращённое объяснение.
for — напоминание о том, что было на занятии K 04
Цикл for следует читать следующим образом: «для каждого элемента, который содержится в данной коллекции, выполни тело цикла».
Такой цикл удобно применять в тех случаях, когда набор элементов уже известен заранее. Этим набором может быть список режимов, список точек маршрута или набор словарей телеметрии. Программисту не нужно заранее вычислять, сколько повторений потребуется; интерпретатор Python сам последовательно проходит по всем элементам.
# Имеется готовый список из трёх режимов
modes = ["GUIDED", "AUTO", "RTL"]
# На каждом шаге переменная mode содержит очередную строку из списка
for mode in modes:
print("mode:", mode)
while — повторение, пока условие остаётся истинным
Цикл while устроен иным образом. У него нет списка элементов; вместо списка у него есть условие. Интерпретатор Python многократно выполняет тело цикла до тех пор, пока это условие остаётся истинным, то есть пока оно принимает значение True.
На программиста возлагается важная обязанность: внутри тела цикла while должно происходить изменение, которое влияет на условие, чтобы в некоторый момент условие стало ложным. Если такое изменение отсутствует, цикл не завершится никогда; подобное состояние называется бесконечным циклом.
# Учебный пример: «выполни три шага ожидания, затем выведи ready»
# Этот пример не является ожиданием реального heartbeat MAVLink,
# он лишь демонстрирует форму цикла while
steps_left = 3 # переменная хранит, сколько шагов ещё осталось
print("start wait loop")
while steps_left > 0:
# Пока steps_left больше нуля, печатаем сообщение и уменьшаем счётчик
print(f"waiting... steps_left={steps_left}")
steps_left = steps_left - 1 # без этой строки условие всегда остаётся True
print("ready")
Разберём этот пример по шагам.
- В начальный момент переменная
steps_leftравна трём. Условие «три больше нуля» истинно, поэтому выполнение входит в цикл. - Тело цикла печатает сообщение и уменьшает значение переменной
steps_leftдо двух. - Условие проверяется снова и снова оказывается истинным; так продолжается до тех пор, пока переменная не достигнет нуля.
- Когда переменная
steps_leftстановится равной нулю, условие оказывается ложным; цикл завершается, и печатается сообщениеready.
Stop. В коде, который работает на борту аппарата, бесконечные циклы без паузы и без условия выхода по какому-либо событию представляют собой отдельную опасность. Пока мы тренируемся только на коротких учебных циклах while.Как выбрать между циклами for и while
Ниже приводится не краткая формула, а полный порядок рассуждения, который позволяет принять правильное решение.
Шаг первый. Сформулируйте вопрос: «Имеется ли у меня список или иная коллекция, по которой необходимо пройти?»
- Если ответ положительный, то почти всегда следует выбрать цикл for.
- Примерами таких коллекций служат все точки миссии, все строки лога режимов и все записи телеметрии в переменной
samples.
Шаг второй. Если готовой коллекции нет, сформулируйте следующий вопрос: «Требуется ли повторять действие до наступления некоторого события или до тех пор, пока счётчик не достигнет нуля?»
- Если ответ положительный, то следует рассмотреть цикл while.
- Учебными примерами служат следующие задачи: уменьшать переменную
steps_left, пока она не станет равной нулю; повторять попытку, пока флагreadyне примет значениеTrue. В обоих случаях флаг или счётчик программист устанавливает самостоятельно в коде.
Шаг третий. Приведите формальное обоснование выбора.
- Цикл
forприменяется тогда, когда заранее известен набор элементов и требуется обработать каждый из них в этом наборе. - Цикл
whileприменяется тогда, когда заранее неизвестно количество повторений и продолжение работы определяется исключительно значением условия. - В обоих случаях условие или перебор элементов должны быть записаны в коде явно и корректно. Интерпретатор не может принимать интуитивных решений; он руководствуется только значениями переменных и результатами проверок, которые вы записали.
Шаг четвёртый. Подготовка к будущему материалу (можно прочитать, не выполняя).
Когда мы перейдём к библиотеке pymavlink, нам часто будет требоваться следующая конструкция: повторять чтение сообщений до тех пор, пока не будет получено сообщение нужного типа либо пока не истечёт отведённое время. По своей форме такая конструкция ближе к циклу while или к циклу, который завершается оператором break по истечении таймаута. Сейчас писать код для pymavlink не требуется. Достаточно запомнить общее правило: цикл while соответствует формулировке «пока не выполнено условие», а цикл for соответствует формулировке «для каждого элемента уже имеющегося набора».
for; если коллекции нет, а имеется лишь условие продолжения, используйте цикл while. При сомнении начните с цикла for; цикл while добавляйте в тех случаях, когда использование цикла for выглядит неестественным.Операторы break и continue
Оператор break немедленно завершает выполнение текущего цикла. Код, который в теле цикла расположен ниже оператора break, на данной итерации не выполняется. Последующие итерации также не выполняются.
Оператор continue не выполняет оставшуюся часть тела цикла на текущей итерации, однако не завершает цикл полностью. После оператора continue выполнение переходит к следующему элементу в цикле for либо к повторной проверке условия в цикле while.
modes = ["STABILIZE", "LOITER", "GUIDED", "AUTO", "RTL"]
print("--- continue: не печатаем STABILIZE ---")
for mode in modes:
if mode == "STABILIZE":
continue # перейти к следующему mode, print ниже не выполнять
print("see:", mode)
print("--- break: остановиться на AUTO ---")
for mode in modes:
print("checking:", mode)
if mode == "AUTO":
print("found AUTO, stop search")
break # выходим из цикла for полностью
STABILIZE. Во втором цикле после того, как встретится значение AUTO, новые строки, начинающиеся с префикса «checking:», появляться не будут.4. Функции: def, return, docstring
Циклы позволяют повторять некоторый код. Функции, в свою очередь, дают этому коду имя и позволяют вызывать его из различных мест программы, передавая при каждом вызове различные данные. Мы рассмотрим материал в следующем порядке: назначение функций, синтаксис объявления, описание функции.
Зачем нужны функции
Если функции не используются, то один и тот же расчёт, например перевод сантиметров в метры, определение класса батареи или выделение режима AUTO, приходится воспроизводить повторным копированием и вставкой. Впоследствии программист исправляет такой фрагмент в одном месте и забывает исправить его в другом. Функция даёт этому расчёту одно имя и создаёт одно место, в котором выполняется правка.
Write, то есть «Записать», представляет собой именованное действие с понятным входом, которым является текущая миссия, и с понятным эффектом. Функция в языке Python является собственной «кнопкой» программиста внутри кода. Эта аналогия уместна лишь до известного предела: кнопка Mission Planner выполняет множество скрытых действий, тогда как ваша функция должна быть понятной по своему имени и кратко описанной в docstring.Синтаксис объявления функции def и оператор return
def alt_cm_to_m(alt_cm: int) -> float:
"""Перевести высоту из сантиметров в метры (учебный пример, часто встречается в MAVLink)."""
# 100 сантиметров равны одному метру
return alt_cm / 100.0
# Вызов функции: передаём аргумент 1530 и сохраняем результат в переменную
meters = alt_cm_to_m(1530)
print(meters) # 15.3
Разберём объявление функции по составным частям.
- Ключевое слово
defсообщает интерпретатору, что объявляется функция. - Имя
alt_cm_to_mобозначает функцию; желательно выбирать имя в форме глагола или указывать, какой результат возвращает функция. - Имя
alt_cmявляется параметром, то есть входной величиной функции. - Оператор
returnпередаёт результат вызывающему коду и завершает выполнение функции. - Строка, которая расположена сразу под строкой
defи заключена в тройные кавычки, называется docstring; подробнее о ней говорится в следующем подразделе.
print выводит текст на экран для человека. Оператор return передаёт значение внутрь программы: полученное значение можно сохранить в переменную, передать в другую функцию или проверить с помощью оператора if. Функция может одновременно и вычислять, и печатать, однако для обучения полезно на первых порах разделять эти две роли.def add(a, b):
return a + b
x = add(2, 3) # переменная x получит значение 5
print(x)
Если функция не содержит оператора return, то результатом её вызова является значение None:
def only_print(msg):
print(msg)
# оператор return отсутствует
result = only_print("hello")
print(result) # None
Документирующая строка (docstring)
Docstring представляет собой краткое описание того, что делает функция; оно располагается сразу под строкой def и заключается в тройные кавычки. Это описание читают и люди, и инструменты разработки. На начальном этапе обучения достаточно одного-трёх предложений на русском языке.
def format_voltage(voltage_v: float) -> str:
"""Вернуть строку напряжения с двумя знаками после запятой."""
return f"{voltage_v:.2f} V"
↑ К оглавлению5. Аргументы и значения по умолчанию
Этот раздел часто вызывает затруднения. Поэтому мы разложим материал на три отдельных приёма, затем рассмотрим один учебный пример функции battery_status и только после этого ответим на вопрос, зачем следует вызывать функцию по именам аргументов.
Три способа передать данные в функцию
Первый способ. Передача по позиции, то есть по порядку.
Программист записывает значения в том же порядке, в котором параметры перечислены в объявлении def. Первое переданное значение попадает в первый параметр, второе значение — во второй параметр и так далее.
def describe(name, volts):
return f"{name}: {volts}"
# Первое значение попадает в параметр name, второе — в параметр volts
print(describe("battery", 15.0))
Второй способ. Передача по имени параметра.
Программист явно указывает имя параметра и присваивает ему значение в форме «имя_параметра = значение». В этом случае интерпретатору не нужно сопоставлять значения по порядку: он помещает каждое значение в тот параметр, имя которого было указано.
# То же самое, но имена параметров указаны явно
print(describe(name="battery", volts=15.0))
# Порядок указания имён можно изменить — смысл вызова не нарушается
print(describe(volts=15.0, name="battery"))
Третий способ. Значение по умолчанию (default).
В заголовке объявления def параметру можно присвоить готовое значение по умолчанию в форме «параметр = значение». Если при вызове соответствующий аргумент не был передан, интерпретатор подставит значение по умолчанию. Если же аргумент был передан, будет использовано переданное значение.
def greet(who, prefix="Hello"):
return f"{prefix}, {who}"
print(greet("pilot")) # Hello, pilot (prefix взят из default)
print(greet("pilot", prefix="Privet")) # Privet, pilot
def параметры, которым задано значение по умолчанию, должны располагаться после параметров, у которых такого значения нет. В противном случае интерпретатор Python сообщит об ошибке SyntaxError.Соберём все три способа в одной учебной функции, которая относится к батарее:
def battery_status(
voltage_v: float,
low_v: float = 14.4,
critical_v: float = 14.0,
) -> str:
"""Классифицировать напряжение. Пороги являются учебными и не предназначены для реального борта."""
if voltage_v >= low_v:
return "OK"
if voltage_v >= critical_v:
return "LOW"
return "CRITICAL"
# Передали только напряжение.
# Параметр low_v получит значение 14.4, параметр critical_v — значение 14.0 из значений по умолчанию.
print(battery_status(15.0))
# Передали все три значения явно, по именам параметров.
# Благодаря этому видно, какое значение является напряжением,
# какое — порогом low, а какое — порогом critical.
print(battery_status(voltage_v=14.2, low_v=14.5, critical_v=14.0))
Зачем следует вызывать функцию по именам аргументов
Сравним два вызова одной и той же функции.
Первый вариант — только числа, переданные по порядку.
print(battery_status(14.2, 14.5, 14.0))
Через месяц, вернувшись к этой строке, вы увидите три числа. Какое из них представляет текущее напряжение? Какое представляет порог «низкое напряжение»? Какое представляет порог «критическое напряжение»? Чтобы ответить на эти вопросы, необходимо открыть объявление функции battery_status и изучить порядок параметров. При таком способе легко переставить местами числа 14.5 и 14.0 и получить незаметную логическую ошибку.
Второй вариант — те же числа, но с указанием имён параметров.
print(battery_status(voltage_v=14.2, low_v=14.5, critical_v=14.0))
В этом случае строка вызова объясняет саму себя.
- Запись
voltage_v=14.2означает, что текущее напряжение равно 14,2 вольта. - Запись
low_v=14.5означает, что порог «низкое напряжение» равен 14,5 вольта. - Запись
critical_v=14.0означает, что порог «критическое напряжение» равен 14,0 вольта.
Вам не требуется удерживать в памяти правило, что «второй аргумент соответствует порогу low». Такой вызов напоминает заполненную форму, в которой после каждого имени следует соответствующее значение.
alt_cm_to_m(1530). Как только аргументов становится несколько и они становятся похожи друг на друга, например три следующих подряд значения напряжения, следует вызывать функцию по именам аргументов. Такой способ снижает количество ошибок и облегчает возвращение к коду спустя некоторое время.6. Подсказки типов на начальном уровне
Подсказка типа представляет собой отметку в заголовке функции, которая указывает, какого типа значение поступает на вход и какое значение возвращается. На начальном этапе обучения интерпретатор Python не остановит выполнение программы только по той причине, что вы передали значение «не того» типа, если только ошибка не возникнет внутри самой логики функции. Тем не менее такие подсказки облегчают чтение кода и помогают замечать ошибки уже при чтении программы в среде PyCharm.
def alt_cm_to_m(alt_cm: int) -> float:
"""cm -> m"""
return alt_cm / 100.0
Эта запись читается следующим образом: «функция принимает значение alt_cm типа int и возвращает значение типа float».
| Запись | Смысл |
|---|---|
| x: int | ожидается целое число |
| x: float | ожидается дробное число |
| x: str | ожидается строка |
| x: bool | ожидается значение True или False |
| -> float | функция возвращает значение типа float |
| -> None | функция не возвращает полезного значения; как правило, такая функция только печатает текст |
list[dict[str, float]], появятся по мере необходимости. В файлах практики, имена которых начинаются с префикса k05_, уже присутствуют простые подсказки типов; заимствуйте этот стиль.7. Чистая функция и побочный эффект
Этот раздел является одним из наиболее важных инженерных разделов модуля M1. Мы вернёмся к нему, когда познакомимся с командами MAVLink.
Чистой функцией называется функция, которая при одинаковых аргументах всегда возвращает один и тот же результат и не изменяет окружение: она не печатает текст, не записывает файл, не отправляет команду и не изменяет глобальные переменные.
Побочным эффектом называется всё то, что функция выполняет сверх возврата значения. На текущем этапе это, как правило, оператор print. В дальнейшем побочным эффектом могут быть запись в последовательный порт serial, отправка данных по протоколу UDP или запрос на выполнение команды arm.
def is_above_min_alt(altitude_m: float, min_alt_m: float) -> bool:
"""Чистая функция: только сравнивает значения и возвращает bool."""
return altitude_m >= min_alt_m
def report_alt_gate(altitude_m: float, min_alt_m: float) -> None:
"""Побочный эффект: печатает отчёт человеку.
Позже похожая функция может отправлять данные по MAVLink.
Сейчас важно следующее: сначала вычисляется решение (pure),
затем выводится сообщение (side effect).
"""
ok = is_above_min_alt(altitude_m, min_alt_m)
if ok:
print(f"GO: alt={altitude_m} >= min={min_alt_m}")
else:
print(f"STOP: alt={altitude_m} < min={min_alt_m}")
print("pure:", is_above_min_alt(12.0, 10.0))
report_alt_gate(12.0, 10.0)
report_alt_gate(3.0, 10.0)
arm и не связана с моторами. Мы изучаем лишь структуру кода.8. Типичные ошибки
| Ошибка | Что вы наблюдаете | Как следует рассуждать |
|---|---|---|
| Программист забывает изменять переменную в цикле while | Программа перестаёт отвечать | Каждая итерация должна приближать момент выхода из цикла |
| Программист ожидает результат, но внутри функции выполняется только оператор print | Снаружи функция возвращает None, либо результат кажется пустым | Если результат используется далее, функция должна содержать оператор return |
| Оператор return расположен в середине функции, а программист ожидает, что код ниже всё равно выполнится | Незаметная логическая ошибка | Оператор return немедленно завершает выполнение функции |
| Параметр без значения по умолчанию расположен после параметра со значением по умолчанию | Ошибка SyntaxError | Сначала следует перечислять обязательные параметры, затем параметры со значениями по умолчанию |
| Имя функции не отражает её назначение | Спустя некоторое время код становится трудным для чтения | Имя do_stuff неудачно; имя alt_cm_to_m удачно |
| Функция выполняет слишком много различных задач | Код становится сложным для изменения | Разделите код: одна задача — одна функция |
9. Практика
Все файлы находятся в каталоге code/M01_python/ и содержат комментарии. Запускайте их в виртуальном окружении.
Задача 5.1. Цикл while
Файл k05_task1_while_countdown.py. С помощью цикла while выполните отсчёт шагов счётчика до значения ready.
Задача 5.2. Операторы break и continue
Файл k05_task2_break_continue.py. Пропустите значение STABILIZE и остановитесь на значении AUTO.
Задача 5.3. Объявление def и оператор return
Файл k05_task3_functions_basic.py. Выполните перевод сантиметров в метры и отформатируйте значение напряжения.
Задача 5.4. Значения по умолчанию и вызов по именам
Файл k05_task4_args_defaults.py. Функция battery_status с учебными порогами. Сравните вызов, в котором значения переданы только числами, и вызов, в котором указаны имена voltage_v, low_v и critical_v.
Задача 5.5. Чистая функция и побочный эффект
Файл k05_task5_pure_vs_side_effect.py. Сначала вычислите решение, возвращающее тип bool, затем выведите отчёт.
Задача 5.6. Несколько функций для работы с телеметрией
Файл k05_task6_mission_helpers.py. Определите максимальную высоту, выполните фильтрацию по значению mode и составьте текстовую сводку.
Задача 5.7. Запись изменений в репозиторий
git add code/M01_python/k05_*.py
git commit -m "K05: while break functions args pure vs side effects"
10. Проверьте себя
- В каких случаях следует выбирать цикл for, а в каких — цикл while? Приведите по одному учебному примеру из области беспилотной авиации.
- Чем оператор break отличается от оператора continue?
- Что выполняет оператор return и чем он не является оператор print?
- Что представляет собой аргумент со значением по умолчанию?
- Чем вызов battery_status(14.2, 14.5, 14.0) хуже вызова battery_status(voltage_v=14.2, low_v=14.5, critical_v=14.0)?
- Что означает запись def f(x: float) -> str?
- Что такое побочный эффект? Почему решение «можно выполнить / нельзя выполнить» лучше вычислять отдельно от оператора print, а в дальнейшем — отдельно от отправки команды?
- Зачем нужен docstring для функции, которой пока пользуетесь только вы сами?
- Почему предпочтительнее несколько небольших функций, чем одна функция объёмом в двести строк?
11. Чек-лист самопроверки
- Я записываю цикл for по списку и понимаю, что означает итерация.
- Я записываю цикл while и умею объяснить, каким образом он завершается.
- Я могу сформулировать полное объяснение, когда следует выбрать for, а когда while, не ограничиваясь короткой незаконченной фразой.
- Я применяю операторы break и continue по их назначению.
- Я объявляю функцию с помощью def, использую return и добавляю docstring.
- Я передаю аргументы по позиции и по имени и понимаю различие между этими способами.
- Я задаю параметрам значения по умолчанию.
- Я читаю простые подсказки типов.
- Я отличаю чистую функцию от функции, которая выводит текст (побочный эффект).
- Я собрал вспомогательные функции для обработки структуры list[dict], содержащей телеметрию.
- Я запускал файлы от k05_task1 до k05_task6 в виртуальном окружении.
- Я записал изменения по занятию K05 в репозиторий с помощью git.
K05 чек-лист закрыт. Следующим занятием является K06, посвящённое модулям, пакетам, виртуальному окружению venv и инструменту pip; на нём будет рассмотрено, как организовать проект и как устанавливать библиотеки, необходимые для работы с pymavlink.12. Что дальше
- Предыдущее занятие: K04 · Коллекции.
- Текущее занятие: K05 · Циклы и функции (редакция 1.2).
- Следующее занятие: K06 · Модули, пакеты, venv, pip.
- Горизонт модуля M1: занятие K12 «CLI Fake GCS» и контрольный рубеж G1.
docs/05_EDITOR_STYLE_GUIDE.md.