Добавить в корзинуПозвонить
Найти в Дзене

Все про язык программирования Python в одной статье для начинающих с лёгким акцентом на БАС (беспилотные летательные аппараты)

Любая программа — это не просто последовательность команд, а система, которая должна принимать решения в зависимости от ситуации. Именно управляющие конструкции позволяют задавать поведение: реагировать на данные, проверять условия, повторять действия или, наоборот, завершать их при достижении цели. Без них программа была бы линейной — она просто шла бы от начала до конца без возможности выбора пути. Условия позволяют встроить в код развилки: если одно — делай так, если другое — поступай иначе. Такие конструкции превращают набор инструкций в систему принятия решений. Циклы дают возможность повторять действия многократно, пока выполняется определенное условие. Это незаменимо при работе с сенсорами, данными телеметрии, массовыми вычислениями и перебором маршрутов. Вместо того чтобы копировать один и тот же код десятки раз, создается цикл, который повторяет нужные шаги автоматически, пока задача не решена. Управляющие конструкции делают возможным написание программ, которые реагируют на и
Оглавление

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

Условия позволяют встроить в код развилки: если одно — делай так, если другое — поступай иначе. Такие конструкции превращают набор инструкций в систему принятия решений.

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

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

В простейшей задаче — например, запустить мотор, когда напряжение превышает заданный порог — уже участвует условие. А если нужно отправить сигнал телеметрии только в том случае, когда скорость изменилась больше, чем на 5%? Здесь уже будет вложенное условие, дополненное логическими операциями. Чем сложнее система, тем больше таких ветвлений, и тем важнее, чтобы они были написаны правильно и понятно.

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

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

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

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

Такая система делает код визуально чистым и понятным. Даже без комментариев становится видно, где какие действия выполняются. Условие заканчивается там, где заканчиваются отступы. Цикл охватывает столько строк, сколько отступов под ним. Вложенные блоки просто делают ещё один уровень отступа.

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

Оператор if

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

Основой условных операторов в Python является оператор `if`. Этот оператор проверяет логическое выражение и, если оно истинно, выполняет блок кода, следующий за ним.

-2

Например:

battery_level = 80
if battery_level > 50:
    print("Батарея заряжена на достаточный уровень, можно летать.")

Здесь мы проверяем, заряжена ли батарея квадрокоптера больше чем на 50%. Поскольку это условие истинно, программа выполнит команду, и на экране появится сообщение "Батарея квадрокоптера заряжена на достаточный уровень, можно летать.". Если бы условие оказалось ложным, этот блок кода был бы пропущен, и выполнение программы продолжилось бы дальше.

Иногда требуется задать альтернативный сценарий на случай, если условие не выполнится. Для этого используется оператор `else`. Он позволяет задать блок кода, который выполнится, если первоначальное условие окажется ложным:

-3

battery_level = 30
if battery_level > 50:
    print("Батарея  заряжена на достаточный уровень, можно летать.")
else:
    print("Батарея слишком разряжена, зарядите её перед полетом.")

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

В реальной жизни часто приходится сталкиваться с ситуациями, когда существует несколько возможных условий, и каждое из них требует выполнения своего набора команд. Для таких случаев используется конструкция `elif`, которая является сокращением от "else if". С помощью `elif` можно проверять несколько условий поочередно, и выполнение программы перейдет к следующей секции только в том случае, если предыдущие условия оказались ложными:

-4

battery_level = 50
if battery_level > 50:
    print("Батарея заряжена на достаточный уровень, можно летать.")
elif battery_level == 50:
    print("Батарея заряжена на 50%, будьте осторожны, полет может быть ограничен.")
else:
    print("Батарея слишком разряжена, зарядите её перед полетом.")

Здесь добавлено условие для случая, когда заряд батареи составляет ровно 50%, и программа предупреждает о возможных ограничениях полета.

Важно отметить, что Python позволяет вкладывать один условный оператор в другой. Это означает, что внутри одного блока `if`, `elif` или `else` можно разместить еще один условный оператор, создавая таким образом более сложные логические структуры:

battery_level = 80
gps_signal = True
if battery_level > 50:
    if gps_signal:
        print("Квадрокоптер готов к полету, батарея заряжена и GPS-сигнал доступен.")
    else:
        print("Квадрокоптер готов к полету, но GPS-сигнал недоступен, летайте вручную.")
else:
    print("Батарея слишком разряжена, зарядите её перед полетом.")

В этом примере сначала проверяется уровень заряда батареи. Если батарея заряжена, программа проверяет наличие GPS-сигнала, чтобы определить, может ли квадрокоптер летать в автоматическом режиме или нужно перейти на ручное управление.

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

drone_mode = "Автоматический"
if drone_mode == "Автоматический":
    print("Квадрокоптер находится в автоматическом режиме.")
else:
    print("Квадрокоптер находится в ручном режиме.")

Здесь проверяется, в каком режиме находится квадрокоптер: автоматическом или ручном.

Python также поддерживает логические операторы, такие как `and`, `or`, и `not`, которые позволяют объединять несколько условий в одном выражении. Например:

battery_level = 75
gps_signal = False
if battery_level > 50 and gps_signal:
    print("Квадрокоптер готов к автоматическому полету.")
else:
    print("Либо батарея квадрокоптера разряжена, либо GPS-сигнал недоступен, проверьте все параметры перед полетом.")

В этом примере для полета необходимо выполнение двух условий: заряда батареи больше 50% и наличия GPS-сигнала.

Также можно использовать оператор `not`, который инвертирует логическое выражение:

camera_connected = False
if not camera_connected:
    print("Камера не подключена, подключите её для съемки во время полета.")

Здесь проверяется, подключена ли камера квадрокоптера. Если камера не подключена, программа выводит сообщение о необходимости её подключения.

Оператор match (с версии Python 3.10)

С появлением оператора match в Python стала доступна возможность описывать логику выбора действий на основе сопоставления с образцом, что до этого приходилось реализовывать через длинные цепочки условий if-elif. Такая конструкция удобна, когда переменная может принимать множество разных значений, и в зависимости от них необходимо выполнять разные блоки кода. Вместо последовательной проверки каждого варианта, match сразу рассматривает возможные шаблоны и выбирает нужный, при этом сам код становится чище и логически структурированнее.

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

Ключевое отличие от if-elif в том, что match не требует многократного сравнения одной и той же переменной. Это избавляет от лишнего дублирования и делает код ближе к модели работы конечного автомата, где каждый входной сигнал приводит к однозначному действию.

Когда используется match, исчезает необходимость помнить порядок условий. Все возможные случаи видны сразу, и нет риска случайно пропустить какой-то вариант. Если ни один шаблон не сработал, остается резервный блок, где можно описать поведение по умолчанию. Такой подход делает код более устойчивым к ошибкам в условиях и упрощает его поддержку, особенно когда вариантов поведения становится больше.

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

Применение match делает поведение программы более предсказуемым. Каждый блок case отвечает только за один вариант и не может случайно перекрыть другой. Это значит, что программа работает по логике «если подходит — выполняй», а не «если не подошло раньше — может подойдёт сейчас». Для задач автоматизации в БАС, где важна стабильность реакции на внешние сигналы и команды, это особенно важно.

Также стоит учитывать, что match позволяет обрабатывать значения по типу. Например, можно различать числа и строки, даже если они имеют схожее значение. Это важно, когда данные поступают из разных источников: один сенсор передает текст, другой — числа, а логика обработки у них разная. Вместо длинных проверок типа через isinstance или приведение, match делает это прозрачно и встроено.

В результате match-case становится не просто альтернативой if-elif, а отдельным инструментом, подходящим для описания сложной логики реакций системы. Особенно полезно использовать его в коде, который отвечает за распределение задач, парсинг сообщений или управление модулями по сигналам от операторов и сенсоров. В таких случаях он не только упрощает структуру, но и уменьшает количество потенциальных ошибок при изменении условий.

Вот несколько примеров, которые демонстрируют использование match-case в Python и показывают, как эта конструкция помогает структурировать логику при работе с разными типами данных:

Простой пример — обработка команды от пользователя:

command = "takeoff"

match command:
    case "takeoff":
        print("Запуск двигателя и набор высоты")
    case "land":
        print("Переход к посадке")
    case "hover":
        print("Удержание позиции в воздухе")
    case _:
        print("Неизвестная команда")

В этом примере match сопоставляет значение переменной command с каждым case. Если команда совпадает, выполняется соответствующий блок. Символ подчеркивания (_) обозначает «любой другой случай».

Пример с числами — обработка кодов ошибки:

error_code = 404

match error_code:
    case 200:
        print("Операция завершена успешно")
    case 404:
        print("Компонент не найден")
    case 500:
        print("Внутренняя ошибка контроллера")
    case _:
        print("Неизвестный код ошибки")

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

Пример с кортежем — данные от сенсора:

sensor_data = ("temperature", 42)

match sensor_data:
    case ("temperature", value):
        print(f"Температура: {value} °C")
    case ("altitude", value):
        print(f"Высота: {value} м")
    case _:
        print("Неизвестный тип данных")

Здесь match не просто проверяет значение, а извлекает элемент кортежа — его удобно сразу использовать внутри блока. Это особенно полезно, когда сенсор возвращает тип и значение в одном сообщении.

Пример с вложенными структурами — данные от внешнего модуля:

message = {"type": "battery", "voltage": 10.7}

match message:
    case {"type": "battery", "voltage": v} if v < 11:
        print("Предупреждение: низкое напряжение")
    case {"type": "battery", "voltage": v}:
        print(f"Напряжение в норме: {v} В")
    case {"type": "gps", "status": "lost"}:
        print("Потерян сигнал GPS")
    case _:
        print("Неизвестное сообщение")

В этом случае match разбирает словарь и может использовать дополнительные условия (if) прямо внутри case, что даёт очень гибкий контроль над логикой.

Такие конструкции отлично подходят для программ, где приходится обрабатывать входящие данные из разных источников: радиосигналов, датчиков, управляющих систем. С помощью match-case можно построить стабильный и читаемый код, который легко масштабировать при добавлении новых состояний и сценариев.

Цикл while

Цикл `while` в Python — это мощный инструмент, который позволяет повторять выполнение определенного блока кода до тех пор, пока выполняется заданное условие. В отличие от цикла `for`, который выполняется фиксированное количество раз, цикл `while` продолжает работать до тех пор, пока условие истинно. Это делает его особенно полезным в ситуациях, когда заранее неизвестно, сколько итераций потребуется для завершения работы.

Работа цикла `while` начинается с проверки условия. Если условие истинно, выполняется тело цикла, то есть набор команд, которые находятся внутри него. После каждой итерации условие проверяется снова. Если оно остается истинным, цикл продолжается. Как только условие становится ложным, выполнение цикла прекращается, и программа переходит к коду, следующему за циклом.

-5

Рассмотрим простой пример, где мы будем выводить числа от 1 до 5:

altitude = 0
while altitude < 10:
    print(f"Текущая высота квадрокоптера: {altitude} метров")
    altitude += 2

В этом примере квадрокоптер поднимается на 2 метра за итерацию, и программа выводит его текущую высоту, пока она не достигнет 10 метров.

Важно понимать, что цикл `while` может легко превратиться в бесконечный цикл, если условие никогда не станет ложным.

altitude = 0
while altitude < 10:
    print(f"Текущая высота квадрокоптера: {altitude} метров")
    # altitude += 2 пропущено

Если забыть обновить переменную ‘altitude’, то цикл будет бесконечно выводить текущую высоту квадрокоптера на уровне 0 метров.

Цикл `while` также часто используется в ситуациях, когда необходимо дождаться выполнения какого-либо события или условия, которое не известно заранее. Представим, что перед полетом нужно ввести правильный пароль, чтобы получить доступ к управлению квадрокоптером:

access_code = ""
while access_code != "drone123":
    access_code = input("Введите код доступа для управления квадрокоптером: ")

print("Доступ разрешен! Вы можете управлять квадрокоптером.")

Здесь программа продолжает запрашивать у пользователя код доступа, пока не будет введен правильный код "drone123". Как только правильный код введен, программа выводит сообщение о том, что доступ разрешен, и пользователь может начать управление квадрокоптером.

В некоторых случаях может потребоваться принудительно завершить цикл `while` до того, как условие станет ложным. Для этого используется оператор `break`, который немедленно прекращает выполнение цикла, независимо от его условия.

-6

Например:

altitude = 0
while altitude < 20:
    print(f"Текущая высота квадрокоптера: {altitude} метров")
    if altitude == 10:
        print("Достигнута целевая высота, остановка набора высоты.")
        break
    altitude += 2

Здесь цикл прерывается, когда квадрокоптер достигает высоты 10 метров, даже если целевая высота была установлена в 20 метров.

Иногда полезно пропускать оставшуюся часть кода в текущей итерации и сразу переходить к следующей. Для этого в Python используется оператор `continue`.

-7

Например:

altitude = 0
while altitude < 10:
    altitude += 1
    if altitude == 5:
        print("Пропуск проверки на высоте 5 метров.")
        continue
    print(f"Проверка на высоте {altitude} метров.")

В этом примере, когда квадрокоптер достигает высоты 5 метров, программа пропускает проверку и сразу переходит к следующей итерации, пропуская вывод для этой высоты.

Цикл for

Цикл `for` в Python — это один из самых часто используемых инструментов для работы с коллекциями данных и последовательностями. В отличие от цикла `while`, который повторяет свои действия до тех пор, пока выполняется определенное условие, цикл `for` используется для перебора элементов в какой-либо последовательности, такой как список, строка, кортеж или диапазон чисел. Это делает его особенно удобным для работы с массивами данных и другими итерируемыми объектами.

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

Цикл `for` отлично подходит для работы со строками, поскольку строки в Python являются последовательностями символов. Например, вы можете использовать `for`, чтобы пройтись по каждому символу строки:

word = "Phantom 4"
for letter in word:
    print(letter)

В этом случае переменная `letter` будет поочередно принимать значения каждого символа строки `"Phantom 4"`, и каждый символ будет выводиться на экран.

Еще одна мощная возможность цикла `for` заключается в использовании функции `range()`, которая генерирует последовательность чисел. Эта функция особенно полезна, когда нужно выполнить цикл определенное количество раз или когда требуется работать с индексами элементов.

for i in range(5):
    print(i)

Здесь `range(5)` создает последовательность чисел от 0 до 4 (всего 5 чисел), и цикл `for` последовательно выводит каждое из этих чисел на экран.

Функция `range()` может принимать дополнительные аргументы, такие как начальное значение и шаг. Например, `range(2, 10, 2)` сгенерирует последовательность 2, 4, 6, 8:

for i in range(2, 10, 2):
    print(i)

Этот цикл выведет только четные числа от 2 до 8.

Вызов функции

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

range(10)

0, 1, 2, 3, 4, 5, 6, 7, 8, 9

range(1, 10)

1, 2, 3, 4, 5, 6, 7, 8, 9

range(3, 7)

3, 4, 5, 6

range(7, 3)

пустая последовательность

range(2, 15, 3)

2, 5, 8, 11, 14

range(9, 2, -1)

9, 8, 7, 6, 5, 4, 3

range(3, 10, -2)

пустая последовательность

Иногда в процессе выполнения цикла `for` требуется досрочно прервать его выполнение. Для этого используется оператор `break`, который немедленно завершает цикл, или оператор `continue`, который пропускает оставшуюся часть текущей итерации и переходит к следующей:

for i in range(10):
    if i == 5:
        break # Завершить цикл при достижении i = 5
    print(i)

В этом примере цикл завершится при достижении значения `i`, равного 5, и числа от 0 до 4 будут выведены на экран.

Если нам сама переменная цикла не нужна, то можно пользоваться переменной ‘ _’. Это особенно полезно, когда вам нужно выполнить определенное количество итераций, но значения элементов не важны.

for _ in range(5):
    print("Этот текст будет напечатан 5 раз")

В примере мы используем переменную _, чтобы выполнить тело цикла 5 раз, не обращая внимания на значения элементов.

Функция enumerate() в Python используется для добавления счетчика к итерируемому объекту и возвращает его в виде объекта, который можно использовать в цикле for. Это особенно полезно, когда вам нужно получить как индекс, так и значение элемента в последовательности.

Пример использования enumerate() с строкой:

word = "Phantom 4"
for index, letter in enumerate(word):
    print(f"Индекс: {index}, Символ: {letter}")

В этом примере функция enumerate(word) возвращает объект, который содержит пары (индекс, символ) для каждого символа в строке "Phantom 4". Цикл for проходит по каждой паре, и переменные index и letter принимают значения индекса и символа соответственно. Затем эти значения выводятся на экран.

Вложенные циклы

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

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

Давайте рассмотрим простой пример вложенных циклов, чтобы лучше понять, как они работают. Допустим, мы хотим вывести на экран таблицу умножения для чисел от 1 до 3:

for i in range(1, 4):
    for j in range(1, 4):
        print(f"{i} * {j} = {i * j}")
    print()  # Печатает пустую строку для разделения результатов

В этом примере внешний цикл `for i in range(1, 4)` отвечает за перебор чисел от 1 до 3. Внутри этого цикла находится еще один цикл `for j in range(1, 4)`, который также перебирает числа от 1 до 3. Внутренний цикл выполняется полностью для каждого значения `i`, создавая таким образом полную таблицу умножения. После завершения внутреннего цикла выполняется команда `print()`, которая добавляет пустую строку для разделения блоков вывода.

Результат выполнения этого кода будет следующим:

1 * 1 = 1
1 * 2 = 2
1 * 3 = 3

2 * 1 = 2
2 * 2 = 4
2 * 3 = 6

3 * 1 = 3
3 * 2 = 6
3 * 3 = 9

Здесь видно, что для каждого значения переменной `i` внутренний цикл `j` проходит через весь диапазон значений, создавая все возможные комбинации произведений чисел.

Иногда вложенные циклы используются для создания сложных паттернов или рисунков. Например, мы можем использовать два вложенных цикла для вывода треугольного паттерна:

rows = 5

for i in range(1, rows + 1):
    for j in range(i):
        print("*", end="")  # end в print говорит что будет в конце строки, по умолчанию стоит знак переноса строки ‘\n’.
    print()

Здесь внешний цикл отвечает за количество строк, которые нужно вывести, а внутренний цикл отвечает за количество символов `*` в каждой строке. С каждым новым значением `i` количество звездочек увеличивается, создавая треугольник.

Результат будет следующим:

*
**
***
****
*****

Вложенные циклы также могут использоваться вместе с операторами `break` и `continue`, чтобы более точно контролировать процесс выполнения. Например, можно прервать выполнение внутреннего цикла или пропустить определенные итерации в зависимости от условия:

for i in range(1, 4):
    for j in range(1, 4):
        if j == 2:
            continue # Пропускает выполнение текущей итерации, если j равно 2
        print(f"i = {i}, j = {j}")

В этом примере, когда `j` становится равным 2, оператор `continue` пропускает оставшуюся часть внутреннего цикла и переходит к следующей итерации.

Результат будет таким:

i = 1, j = 1
i = 1, j = 3
i = 2, j = 1
i = 2, j = 3
i = 3, j = 1
i = 3, j = 3

Управляющие конструкции — это каркас, на котором строится логика любой программы. Без них невозможно организовать принятие решений, проверку условий, повторение действий в ответ на внешние сигналы. В контексте БАС это становится особенно заметно: система должна не просто выполнять набор команд, а реагировать на изменения среды, корректировать маршрут, отслеживать телеметрию, анализировать поведение модулей. Всё это невозможно без четкой структуры условий и циклов.

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

Чем лучше освоено понимание условий и циклов, тем проще в будущем будет писать код, который не просто работает, а работает надежно в условиях нестабильной связи, переменных данных, ограниченного времени отклика. Это та база, на которой дальше строится полноценная архитектура программного обеспечения БАС.

Функции

Когда программа становится хоть немного сложнее, чем простая цепочка команд, возникает необходимость как-то упорядочить код. Появляется желание не повторяться, разбить логику на отдельные части, а часто и переиспользовать одни и те же куски в разных местах. Именно в такие моменты на помощь приходят модули и функции — два ключевых инструмента, без которых невозможно представить работу в Python.

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

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

Кроме основ — создания модулей, их импорта и запуска — важно понимать, как работают аргументы функций, как передавать данные разными способами, как уточнять типы с помощью аннотаций. И даже у лаконичных лямбда-функций и у экономных генераторов есть своё место — они позволяют писать компактный, гибкий и в то же время понятный код.

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

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

Функции можно сравнить с небольшими программками, которые сами по себе, то есть автономно, не исполняются, а встраиваются в обычную программу. Нередко их так и называют – подпрограммы. Других ключевых отличий функций от программ нет. Функции также при необходимости могут получать и возвращать данные. Только обычно они их получают не с ввода (клавиатуры, файла и др.), а из вызывающей программы. Сюда же они возвращают результат своей работы.

Существует множество встроенных в язык программирования функций. С некоторыми такими в Python мы уже сталкивались. Это print(), input(), int(), float(), str(). Код их тела нам не виден, он где-то "спрятан внутри языка". Нам же предоставляется только интерфейс – имя функции.

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

Предположим, надо три раза подряд запрашивать на ввод пару чисел и складывать их. С этой целью можно использовать цикл:

i = 0
while i < 3:
    a = int(input())
    b = int(input())
    print(a+b)
    i += 1

Однако, что если перед каждым запросом чисел, надо выводить надпись, зачем они нужны, и каждый раз эта надпись разная. Мы не можем прервать цикл, а затем вернуться к тому же циклу обратно. Придется отказаться от него, и тогда получится длинный код, содержащий в разных местах одинаковые участки:

print("Сколько бананов и ананасов для обезьян?")
a = int(input())
b = int(input())
print("Всего", a+b, "шт.")

print("Сколько жуков и червей для ежей?")
a = int(input())
b = int(input())
print("Всего", a+b, "шт.")

print("Сколько рыб и моллюсков для выдр?")
a = int(input())
b = int(input())
print("Всего", a+b, "шт.")

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

Определение функции. Оператор def

Итак, функция – отдельная, функционально независимая часть программы, выполняющая определенную задачу.

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

-8

Рассмотрим код:

def count_food():
    a = int(input())
    b = int(input())
    print("Всего", a+b, "шт.")

Это пример определения функции. Как и другие сложные инструкции вроде условного оператора и циклов функция состоит из заголовка и тела. Заголовок заканчивается двоеточием и переходом на новую строку. Тело имеет отступ.

Ключевое слово def сообщает интерпретатору, что перед ним определение функции. За def следует имя функции. Оно может быть любым, также как и всякий идентификатор, например, переменная. В программировании весьма желательно давать всему осмысленные имена. Так в данном случае функция названа "посчитать_еду" в переводе на русский.

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

После двоеточия следует тело, содержащее инструкции, которые выполняются при вызове функции. Следует различать определение функции и ее вызов. В программном коде они не рядом и не вместе. Можно определить функцию, но ни разу ее не вызвать. Нельзя вызвать функцию, которая не была определена. Определив функцию, но ни разу не вызвав ее, вы никогда не выполните ее тела.

Именование функций

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

Python и тут требует соблюдения тех же правил, что при именовании переменных:

  1. в имени функции используются только латинские буквы a-z, A-Z, цифры и символ нижнего подчеркивания (_);
  2. имя функции не может начинаться с цифры;
  3. имя функции по возможности должно отражать ее назначение;
  4. символы верхнего и нижнего регистра различаются.

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

  • функцию, которая рисует прямоугольник можно назвать draw_box();
  • функцию, которая печатает чек, можно назвать print_check();
  • функцию, которая вычисляет заработную плату до удержаний, можно назвать calculate_gross_рау().

Каждое из этих имен дает описание того, что функция делает.

Вызов функции

Рассмотрим полную версию программы с функцией:

def count_food():
    a = int(input())
    b = int(input())
    print("Всего", a+b, "шт.")

print("Сколько бананов и ананасов для обезьян?")
count_food()

print("Сколько жуков и червей для ежей?")
count_food()

print("Сколько рыб и моллюсков для выдр?")
count_food()

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

Когда функция вызывается, поток выполнения программы переходит к ее определению и начинает исполнять ее тело. После того, как тело функции исполнено, поток выполнения возвращается в основной код в то место, где функция вызывалась. Далее исполняется следующее за вызовом выражение.

В языке Python определение функции должно предшествовать ее вызовам. Это связано с тем, что интерпретатор читает код строка за строкой и о том, что находится ниже по течению, ему еще неизвестно. Поэтому если вызов функции предшествует ее определению, то возникает ошибка (выбрасывается исключение NameError):

print("Сколько рыб и моллюсков для выдр?")
count_food()

def count_food():
    a = int(input())
    b = int(input())
    print("Всего", a+b, "шт.")

Функции с параметрами

Функции с параметрами объявляются так же как функции без параметров, только с указанием в скобках:

def название_функции(параметры):
    блок кода

Давайте напишем функцию draw_box() так, чтобы она принимала параметры, задающие высоту и ширину прямоугольника:

def draw_box(height, width):    # функция принимает два параметра
    for i in range(height):
        print('*' * width)

Теперь наша функция draw_box() принимает два целочисленных параметра height – высота прямоугольника и width – ширина прямоугольника, и для ее вызова нам нужно обязательно их указать.

Чтобы вывести звездный прямоугольник размерами 5 на 7 мы пишем код:

draw_box(5, 7)

Результатом такого вызова функции draw_box(5, 7) будет:

*******
*******
*******
*******
*******

На место параметров мы можем подставлять не только целочисленные константы, но и значения переменных. Следующий программный код:

n = 3
m = 9
draw_box(n, m)

Для большей ясности давайте разберем еще один пример. Напишем функцию print_hello(n), которая принимает одно натуральное число n и печатает слово Hello ровно n раз.

def print_hello(n):
    print('Hello' * n)

Следующий программный код:

print_hello(3)
print_hello(5)
count = 2
print_hello(count)

выведет:

HelloHelloHello
HelloHelloHelloHelloHello
HelloHello

Функцию print_hello() можно сделать более гибкой, если передавать в нее еще один параметр – текст для вывода:

def print_text(txt, n):
    print(txt * n)

Следующий программный код:

print_text('Hello', 5)
print_text('A', 10)

выведет:

HelloHelloHelloHelloHello
AAAAAAAAAA

Что такое параметры и аргументы

Аргумент – это любая порция данных, которая передается в функцию, когда функция вызывается.

Параметр – это переменная, которая получает аргумент, переданный в функцию.

Для функции draw_box(height, width):

def draw_box(height, width):
    for i in range(height):
        print('*' * width)

параметрами являются переменные height и width.

В момент вызова функции draw_box(height, width):

height = 10
draw_box(height, 9)

аргументами являются height и 9.

Локальные переменные

Локальными называются переменные, объявленные внутри функции и доступные только ей самой. Программный код за пределами функции к ним доступа не имеет.

Рассмотрим функцию print_texas(), которая выводит информацию о количестве птиц, обитающих в Техасе.

def print_texas():
    birds = 5000
    print('В Техасе обитает', birds, 'птиц.')

В теле функции мы создаем переменную birds, которой присваивается значение, равное 5000. Такая переменная является локальной для функции print_texas(). Всякий раз, когда переменной внутри функции присваивается значение, в результате создается локальная переменная. Она принадлежит функции, в которой создается,  и к ней получает доступ только программный код этой функции.

Термин «локальная» указывает на то, что переменная может использоваться лишь в этом месте — внутри функции, в которой создается.

Если программный код одной функции попытается обратиться к локальной переменной, принадлежащей другой функции, произойдет ошибка.

Рассмотрим следующий программный код:

def print_texas():
    birds = 5000
    print('В Техасе обитает', birds, 'птиц.')
def print_california():
    print('В Калифорнии обитает', birds, 'птиц.')

Функция print_california() обращается к локальной переменной birds функции print_texas(). Вызов функции print_california(), приводит к ошибке:

NameError: name 'birds' is not defined

Локальные переменные скрыты от других функций, поэтому другие функции могут иметь собственные локальные переменные с тем же именем. Например,

def print_texas():
    birds = 5000
    print('В Техасе обитает', birds, 'птиц.')
def print_california():
    birds = 9000
    print('В Калифорнии обитает', birds, 'птиц.')

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

Когда выполняется функция print_texas(), видима переменная birds, значение которой равно 5000. Когда выполняется функция print_california(), видима переменная birds, значение которой равно 9000.

Глобальные переменные

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

Рассмотрим следующий программный код:

birds = 5000   # глобальная переменная
def print_texas():
    print('В Техасе обитает', birds, 'птиц.')
def print_california():
    print('В Калифорнии обитает', birds, 'птиц.')

В самом начале программы создаем глобальную переменную birds, значение которой равно 5000. Далее описываем две функции, обращающиеся к глобальной переменной. Результатом выполнения следующего кода:

print_texas()
print_california()

будет:

В Техасе обитает 5000 птиц.
В Калифорнии обитает 5000 птиц.

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

Рассмотрим следующий программный код:

birds = 5000  # глобальная переменная
def print_texas():
    birds = 1000 # локальная переменная
    print('В Техасе обитает', birds, 'птиц.')
def print_california():
    birds = 7000 # локальная переменная
    print('В Калифорнии обитает', birds, 'птиц.')

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

Результатом выполнения следующего кода:

print_texas()
print_california()

будет:

В Техасе обитает 1000 птиц.
В Калифорнии обитает 7000 птиц.

Ключевое слово global

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

Рассмотрим следующий программный код:

def print_texas():
    global birds
    birds = 5000
    print('В Техасе обитает', birds, 'птиц.')
def print_california():
    print('В Калифорнии обитает', birds, 'птиц.')
print_texas()
print_california()

Результатом выполнения следующего кода:

print_texas()
print_california()

будет:

В Техасе обитает 5000 птиц.
В Калифорнии обитает 5000 птиц.

Функции с возвратом значения

Функция с возвратом значения похожа на функцию без возврата значения тем, что:

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

Однако когда функция с возвратом значения завершается, она возвращает значение в ту часть программы, которая ее вызвала. Возвращаемое из функции значение используется как любое другое: оно может быть присвоено переменной, выведено на экран, использовано в математическом выражении (если это число) и т. д.

Мы уже сталкивались со многими функциями с возвратом значений:

  • функция int() – преобразует строку к целому числу и возвращает его;
  • функция range() – возвращает последовательность целых чисел 0, 1, 2, ...;

Функцию с возвратом значения пишут точно так же, как и без, но она должна иметь инструкцию return.

Вот общий формат определения функции с возвратом значения в Python:

def название_функции():
    блок кода
    return выражение

В функции должна быть инструкция return, принимающая форму:

return выражение

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

Использование нескольких return

В одной функции может быть сколько угодно инструкций return. Рассмотрим функцию convert_grade(), которая переводит стобалльную оценку в пятибалльную:

def convert_grade(grade):
    if grade >= 90:
        return 5
    elif grade >= 80:
        return 4
    elif grade >= 70: 
        return 3
    elif grade >= 60:
        return 2
    else:
        return 1
# основная программа
grade = int(input('Введите вашу отметку по 100-балльной системе: '))
print(convert_grade(grade))

В функции convert_grade() используется 5 инструкций return. Каждая из них возвращает соответствующее значение и завершает работу функции.

Функцию convert_grade() можно переписать с помощью одной инструкции return:

def convert_grade(grade):
    result = -1
    if grade >= 90:
        result = 5
    elif grade >= 80:
        result = 4
    elif grade >= 70: 
        result = 3
    elif grade >= 60:
        result = 2
    else:
        result = 1

    return result

Функции с возвратом значения предоставляют те же преимущества, что функции без возврата значения:

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

Лямбда-функции в Python

Лямбда-функции в Python — это способ написать небольшую функцию прямо в месте её использования, без необходимости давать ей имя или описывать с помощью стандартной конструкции def. Они часто используются там, где требуется короткое логическое действие — например, при сортировке, фильтрации, работе с коллекциями или при передаче функции как аргумента.

Ключевым словом является lambda, за которым сразу идет список параметров, затем — двоеточие и выражение, которое будет вычислено и возвращено. Лямбда-функции всегда возвращают результат, даже если это не указывается явно, и всегда ограничены одним выражением. Они не могут содержать сложных конструкций, вроде условий if с телом, циклов или блоков.

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

Лямбда-функции в языке Python представляют собой способ создания небольших анонимных функций — то есть таких, у которых нет имени, и которые обычно используются там, где нужно выполнить простое действие без необходимости создавать полноценную функцию через def.

Синтаксис у них компактный и выглядит следующим образом:

lambda аргументы: выражение

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

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

add = lambda x, y: x + y
print(add(3, 5))  # 8

Хотя лямбда и экономит строки кода, использовать её стоит только в тех местах, где простота и краткость важнее читаемости. Когда логика сложная, лучше предпочесть обычное определение функции через def.

Генераторы в Python — это особый тип функций, которые позволяют создавать последовательности значений на лету, без необходимости хранить их все сразу в памяти. Такая возможность особенно полезна при работе с большими объемами данных или при обработке непрерывных потоков, например, в телеметрии БАС или при последовательном анализе сенсорных данных. В отличие от обычной функции, которая возвращает результат и завершает выполнение, генератор может приостанавливать свою работу и продолжать её с того же места при следующем обращении.

Генераторы функции

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

Генераторы часто используются в циклах for, где каждый вызов автоматически получает следующее значение из последовательности. Такой подход позволяет писать компактный и читаемый код, избегая излишнего потребления ресурсов. Например, вместо того чтобы сначала создать длинный список всех значений, которые требуется обработать, генератор позволяет получать и использовать их по одному, в нужный момент времени.

Кроме того, генераторы позволяют обрабатывать данные «на лету», без промежуточных структур, что идеально подходит для задач, в которых важна скорость отклика и постоянный поток данных. Это может быть актуально, например, при анализе траектории БАС, где данные с GPS и других модулей поступают непрерывно, и необходимо обрабатывать их без задержек.

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

В случае необходимости генератор можно превратить в список, передав его в функцию list(). Однако это приведёт к тому, что все значения будут извлечены и сохранены в память, так что теряется основное преимущество генератора — его экономичность. Поэтому такой способ используется только тогда, когда действительно нужно получить всю последовательность целиком и нет риска превышения памяти.

Если нужно завершить генератор досрочно, это можно сделать с помощью return, после чего дальнейшие попытки извлечь значения приведут к исключению StopIteration. Такое поведение позволяет управлять процессом генерации более гибко и задавать чёткие правила выхода из цикла.

Таким образом, генераторы — это не просто синтаксическая возможность, а мощный инструмент для построения эффективных и масштабируемых алгоритмов, особенно в инженерных задачах, связанных с обработкой потоков данных и управлением последовательностями состояний.

Генераторы в Python — это функции, которые возвращают не одно значение сразу, а создают итератор, по которому можно проходить поэтапно, запоминая промежуточное состояние. Они позволяют создавать последовательности данных без необходимости хранить всё сразу в памяти. Это особенно полезно при работе с большими объёмами информации или с потоковыми данными.

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

Вот простой пример:

def countdown(n):
    while n > 0:
        yield n
        n -= 1

Эта функция не возвращает список чисел от n до 1 сразу. Она позволяет проходить по ним по одному:

for i in countdown(5):
    print(i)

На выходе будет:

5
4
3
2
1

Следующий пример показывает, как можно использовать генератор для чтения больших файлов построчно:

def read_lines(file_path):
    with open(file_path, 'r') as file:
        for line in file:
            yield line.strip()

Такой подход экономит память, особенно если файл состоит из миллионов строк. Генератор создаёт объект, который можно передавать другим функциям, применять к нему фильтрацию, преобразования и другие операции — всё это происходит поэтапно, без перегрузки памяти.

Ещё один пример — генерация бесконечной последовательности:

def infinite_counter(start=0):
    while True:
        yield start
        start += 1

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

Таким образом, генераторы дают контроль над последовательностью данных, позволяют экономить ресурсы, а их использование делает код более выразительным и модульным.

Модули в Python

Модули в Python — это основа организации кода, которая позволяет разбивать большие программы на более мелкие, понятные и переиспользуемые части. Они дают возможность не писать всё в одном файле, а создавать наборы функций, классов и переменных, которые можно загружать в любой другой файл и использовать там, где нужно. Это похоже на ящик с инструментами: если нужно воспользоваться отверткой, не обязательно изобретать её заново — достаточно взять нужный инструмент из ящика.

Чтобы лучше понять, стоит начать с самой идеи модуля. Модуль — это просто обычный .py файл, в котором написан Python-код. Он может содержать любые конструкции языка: переменные, функции, классы, логические блоки. Главное — такой файл может быть импортирован в другой файл, и тогда весь код модуля станет доступен там, где он нужен.

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

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

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

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

  1. Модульность и организация кода: Модули помогают разделить код на логические единицы, предоставляющие определенную функциональность. Это упрощает чтение, понимание и поддержку кода.
  2. Повторное использование кода: Код, написанный в виде модуля, можно легко использовать повторно в различных проектах и сценариях. Это сокращает время разработки и уменьшает вероятность дублирования кода.
  3. Изоляция пространства имен: Модули обеспечивают изоляцию пространств имен, что позволяет избегать конфликтов между переменными и функциями с одинаковыми именами в разных частях программы.
  4. Повышение уровня абстракции: Модули позволяют разработчику строить сложные программы, скрывая детали реализации и предоставляя высокоуровневый интерфейс для взаимодействия с функциональностью.
  5. Деление на подмодули: Модули могут быть разделены на подмодули, которые могут импортировать и использовать другие модули. Это предоставляет еще один уровень структурирования кода.
  6. Упрощение тестирования и отладки: Модули легче тестировать и отлаживать по отдельности, что упрощает разработку и обеспечивает более высокую надежность программы.
  7. Распространение и совместное использование кода: Оформление программы в виде модулей облегчает ее распространение и использование другими разработчиками. Собственные модули можно загрузить на Python Package Index (PyPI) или в другие репозитории пакетов, что позволяет их устанавливать и импортировать аналогично сторонним библиотекам.

Создание и использование собственных модулей является важным инструментом в арсенале Python-разработчика, и его применение может существенно улучшить качество кода и сократить время разработки.

Основы создания модулей

Для написания простого модуля в Python вам потребуется создать новый текстовый файл с расширением `.py`. В этот файл вы разместите свои переменные, функции и классы, которые хотите использовать в других частях вашей программы или проекта. Рассмотрим пример простого модуля с именем `simple_module.py`:

# simple_module.py
# переменная модуля
greeting = "Hello, world!"
# функция модуля
def multiply(a, b):
    return a * b
# функция для вывода приветствия
def say_hello():
    print(greeting)

Здесь мы создали простой модуль с переменной `greeting`, функцией `multiply()` и функцией `say_hello()`.

Теперь давайте импортируем и используем наш простой модуль в другом файле Python, например в файле `main.py`:

import simple_module
result = simple_module.multiply(2, 3)  # Использование функции multiply из simple_module
print(result)  # Выводит 6
simple_module.say_hello()  # Выводит "Hello, world!"

В этом примере мы импортировали наш модуль `simple_module` в файл `main.py` и использовали его функции и переменные. Отметим, что модуль импортируется только один раз; при повторном импорте модуля в других частях кода используется уже импортированный ранее экземпляр модуля.

Основы импортирования: инструкции `import` и `from ... import`

В Python импортирование модулей позволяет использовать код, определенный в одном файле, в других файлах, повышая уровень повторяемости и организации кода. Для выполнения операций импорта доступны две инструкции: `import` и `from ... import`.

`import` позволяет импортировать целый модуль в ваш код. При использовании `import` модуль будет доступен с префиксом имени модуля.

import math
print(math.sqrt(16))  # Вывод: 4.0

В этом примере мы импортируем модуль `math` и вызываем функцию `math.sqrt()` для вычисления квадратного корня числа 16.

`from ... import` позволяет импортировать конкретные функции, классы или переменные из модуля. Они будут доступны без префикса имени модуля.

from math import sqrt
print(sqrt(16))  # Вывод: 4.0

Здесь мы импортировали только функцию `sqrt()` из модуля `math`. Теперь мы можем вызывать эту функцию напрямую, без префикса `math.`.

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

from math import sqrt, pow
   print(sqrt(16))  # Вывод: 4.0
   print(pow(2, 3))  # Вывод: 8.0

Если имя модуля или функции слишком длинное или конфликтует с другими именами в вашем коде, можно использовать псевдоним с помощью инструкции `as`.

import math as m
   print(m.sqrt(16))  # Вывод: 4.0
   from math import sqrt as square_root
   print(square_root(16))  # Вывод: 4.0

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

from math import *
   print(sqrt(16))  # Вывод: 4.0

Использование инструкций импорта `import` и `from ... import` позволяет применять код модулей в других частях программы и обеспечивает удобное средство использования, расширения и повторного использования кода.

Порядок поиска модулей в Python: `sys.path` и переменная среды `PYTHONPATH`

В Python, порядок поиска модулей определяется списком sys.path и переменной среды PYTHONPATH. Эти два элемента в совокупности определяют, где интерпретатор Python будет искать модули для импорта.

1. sys.path: Это список строк, который содержит путь к каталогам, в которых Python должен искать модули при выполнении операции импорта. По умолчанию sys.path уже содержит несколько путей для поиска стандартных библиотек и сторонних модулей. Вы можете отобразить текущие значения sys.path, выполнив следующий код:

import sys
print(sys.path)

2. PYTHONPATH: Это переменная среды, которую можно установить перед запуском сценария Python или добавить к системным переменным среды операционной системы. При наличии PYTHONPATH интерпретатор Python будет добавлять указанные в этом параметре пути к списку sys.path. Значение переменной PYTHONPATH может содержать несколько путей, разделенных двоеточием (для Linux и macOS) или точкой с запятой (для Windows).

При импорте модуля Python последовательно проверяет пути в sys.path (с учетом PYTHONPATH, если она установлена), и использует первый найденный модуль. Если модуль не найден, возникает ошибка ImportError.

Чтобы добавить новый путь к sys.path во время выполнения программы, можно использовать следующий код:

import sys
sys.path.append('/новый/путь/к/модулю')

Теперь Python будет искать модули также и в указанном каталоге.

Важно помнить, что любые изменения, внесенные в sys.path во время выполнения программы, будут действовать только во время текущего сеанса. Они не сохраняются после завершения работы программы. Если вам постоянно нужно добавлять определенный путь для поиска модулей, нужно использовать переменную среды PYTHONPATH или добавить путь в системные переменные среды.

Зависимости между модулями

Циклическая зависимость в программировании возникает, когда два или более модуля взаимозависимы друг от друга непосредственно или через цепочку из других модулей. Это может привести к серьезным проблемам, таким как ошибки импорта, неопределенное поведение и сложности в поддержке кода. Особенно важно обходить такие ситуации в Python, из-за того, как работает импорт модулей.

Рассмотрим пример циклической зависимости:

module_a.py
import module_b
def function_a():
    print("Используется функция A")
    module_b.function_b()

module_b.py
import module_a
def function_b():
    print("Используется функция B")
    module_a.function_a()

В этом примере module_a импортирует module_b, который в свою очередь импортирует module_a. Такая ситуация может быть причиной ошибок или не стабильной работы.

Циклические зависимости - это результат проблем с архитектурой кода. Лучший способ решения - переосмысление структуры кода и избегание чрезмерных межмодульных связей. Eсли вы не уверены, что ваш код действительно нуждается в определенной зависимости или функции, стоит отказаться от нее.

Ленивая загрузка и отложенное выполнение импорта

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

1. Ленивая загрузка (Lazy loading): Ленивая загрузка означает загрузку модуля (или других ресурсов) только тогда, когда он реально нужен, а не загружать его сразу при запуске программы. Это может быть полезно для оптимизации производительности, особенно в больших приложениях с множеством модулей и библиотек. Чтобы реализовать ленивую загрузку, вы можете использовать отложенный импорт внутри функций или методов там, где модуль используется впервые.

Например:

def my_function():
    # Импортируем модуль только при вызове функции
    import my_module
    my_module.my_method()

2. Отложенное выполнение импорта (Deferred execution of import): Отложенное выполнение импорта - это другой метод реализации ленивой загрузки. В этом случае, вместо того чтобы импортировать модуль в начале файла или внутри функции, вы можете импортировать его внутри блока кода, который вызывается только при определенных условиях. Это полезно, когда модуль нужен только в некоторых случаях, и его импорт замедляет выполнение кода.

Например:

user_choice = input("Введите что-нибудь")
def perform_action(user_choice):
    if user_choice == "special_action":
        # Импортируем модуль только при определенных условиях
        import my_special_module
        my_special_module.special_action()
    else:
        print("Другое действие")
perform_action(user_choice)

Хотя ленивая загрузка и отложенное выполнение импорта могут улучшить производительность, у них есть недостатки, такие как более сложный код и возможные проблемы с нарушением структуры модулей. До начала использования этих методов, убедитесь, что они действительно необходимы в вашем коде и структура модулей продумана правильно.

В Python переменная `__name__` является встроенной переменной, которая используется для определения того, как был запущен ваш Python-код. Эта переменная представляет собой строку с именем текущего модуля. Однако, если файл был запущен как главный файл-скрипт, то `__name__` будет автоматически установлено в значение `"__main__"`.

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

Рассмотрим пример:

Допустим, у вас есть следующий код в файле `module_example.py`:

def my_function():
    print("Эта функция была вызвана из другого модуля")
print("Это сообщение печатается каждый раз, когда этот модуль исполняется")

Если вы будете импортировать этот модуль в другой Python-скрипт, например:

import module_example
module_example.my_function()

При выполнении кода вы увидите следующий вывод:

Это сообщение печатается каждый раз, когда этот модуль исполняется

Эта функция была вызвана из другого модуля

Для предотвращения выполнения кода при импорте модуля, вы можете использовать проверку `__name__`:

def my_function():
    print("Эта функция была вызвана из другого модуля")
if __name__ == "__main__":
    print("Это сообщение печатается только при прямом вызове этого модуля")

Таким образом, когда вы импортируете `module_example`, появится только следующий вывод:

Эта функция была вызвана из другого модуля

Значение `__name__` широко используется в Python для проверки, является ли данный файл главным файлом-скриптом или модулем, импортируемым другими скриптами. Это позволяет вам контролировать исполнение вашего кода и писать отдельные части кода для исполнения только в основном скрипте.

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

Подобная ситуация часто возникает в интерактивной среде, например, в Jupyter Notebook или при разработке в REPL-сессии, где модуль дорабатывается параллельно с отладкой основного скрипта. Чтобы изменения вступили в силу без перезапуска всего интерпретатора, используется явная перезагрузка.

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

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

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

Перезагрузка модулей в Python становится актуальной в тех случаях, когда уже импортированный модуль изменяется во время работы программы или в процессе отладки. Обычно модуль в Python импортируется один раз за выполнение скрипта. Даже если в середине программы внести изменения в исходный файл, они не будут подхвачены, пока программа не завершится и не запустится заново. Это связано с тем, что при импорте Python сохраняет ссылку на модуль в системном словаре sys.modules, и все последующие обращения к модулю берутся оттуда.

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

Стандартный подход к перезагрузке заключается в использовании модуля importlib, который с версии Python 3.4 предоставляет функцию reload. Она позволяет обновить содержимое уже импортированного модуля. Однако стоит помнить, что эта операция не обнуляет автоматически объекты, созданные на основе старой версии модуля, и может привести к ситуации, когда в программе одновременно присутствуют устаревшие экземпляры и новые определения.

Такая практика особенно распространена при отладке вспомогательных библиотек, написанных для управления БАС, обработки данных сенсоров или отрисовки интерфейса. Если изменения касаются только логики внутри модуля, а структура импортов не изменилась, перезагрузка позволяет значительно сократить время между итерациями. В противном случае, при сложных зависимостях, рекомендуется всё же перезапускать процесс целиком, чтобы избежать накопления конфликтов и утечек.

Важно также учитывать, что в случае импорта по шаблону from module import function, перезагрузка модуля никак не затронет уже импортированные элементы. Они останутся в памяти и будут ссылаться на старую реализацию. Чтобы изменения вступили в силу, следует использовать полную форму импорта import module, а затем обращаться к функциям и классам через точечную нотацию.

Таким образом, перезагрузка модулей — полезный, но тонкий инструмент, требующий понимания устройства импорта в Python. Он не предназначен для массового применения в финальных продуктах, но может значительно упростить тестирование, отладку и обучение в интерактивных средах.

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

Для ручной перезагрузки в большинстве случаев используется модуль importlib. Ранее использовавшийся reload из imp устарел, и теперь применяется importlib.reload() — функция, которая принудительно загружает обновлённый файл модуля, даже если он уже есть в памяти. Это позволяет увидеть изменения, не выходя из среды разработки или интерпретатора.

Такой подход полезен в Jupyter Notebook, в интерактивных сессиях или в системах, где нужно обновлять модули на лету. Однако важно понимать, что перезагрузка касается только самого модуля. Если другие модули или переменные уже получили ссылки на объекты до перезагрузки, они не обновятся автоматически. Также стоит учитывать, что повторная загрузка может повлиять на внутренние состояния модуля, особенно если в нём используются глобальные переменные или инициализация выполняется при импорте.

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

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

Всё это — модули, функции, аргументы, генераторы — формирует фундамент современной разработки на Python. Без этой базы невозможно строить надёжные, расширяемые и поддерживаемые проекты, будь то маленькие скрипты или сложные системы с сотнями компонентов.

Было полезно? Ставьте 👍