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

Функции

Функции в программе робота

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

Коротко

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

Зачем это нужно

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

Можно один раз определить действие millimeters_to_centimeters, а затем вызывать его с разными измерениями:

def millimeters_to_centimeters(distance_mm):
return distance_mm / 10


front_cm = millimeters_to_centimeters(275)
left_cm = millimeters_to_centimeters(430)
right_cm = millimeters_to_centimeters(190)

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

Главная идея

Функцию удобно представлять как модуль с контрактом:

аргументы -> функция -> возвращаемый результат

В определении указывают имя и параметры:

def clamp_speed(speed, minimum, maximum):
if speed < minimum:
return minimum
if speed > maximum:
return maximum
return speed

Вызов передаёт конкретные аргументы:

safe_speed = clamp_speed(72, 0, 60)
print(safe_speed) # 60

Здесь speed, minimum и maximum — параметры определения. Числа 72, 0 и 60 — аргументы вызова. После return функция завершается, а значение 60 становится результатом выражения clamp_speed(...).

ЧастьРольПример
defНачинает определение функцииdef clamp_speed(...):
ИмяОбозначает действиеclamp_speed
ПараметрыПринимают входные значенияspeed, minimum, maximum
ТелоВыполняет вычисления и командыпроверки if
returnЗавершает вызов и передаёт результатreturn maximum
ВызовЗапускает тело с аргументамиclamp_speed(72, 0, 60)

Определение и вызов

После строки def ставятся круглые скобки, двоеточие и блок с отступом. Само определение не выполняет тело как обычную команду движения: оно создаёт объект функции и связывает его с именем. Тело запускается при вызове.

def signal_ready():
print("Система готова")


signal_ready()
signal_ready()

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

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

def read_simulated_sensor():
return 245


distance_mm = read_simulated_sensor()
print(distance_mm)

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

Параметры и аргументы

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

def is_obstacle_close(distance_mm, limit_mm):
return distance_mm <= limit_mm


front_alert = is_obstacle_close(115, 120)
side_alert = is_obstacle_close(180, 100)

print(front_alert) # True
print(side_alert) # False

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

Аргументы можно указывать по позиции или по имени:

def motor_command(left_percent, right_percent):
return left_percent, right_percent


straight = motor_command(40, 40)
turn_left = motor_command(left_percent=15, right_percent=45)

print(straight)
print(turn_left)

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

Значения параметров по умолчанию

Параметру можно назначить значение по умолчанию. Тогда вызывающий код вправе его не передавать:

def corrected_distance(raw_mm, offset_mm=0):
return raw_mm + offset_mm


plain = corrected_distance(250)
calibrated = corrected_distance(250, offset_mm=-8)

print(plain) # 250
print(calibrated) # 242

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

Так писать не стоит:

def remember_measurement(value, history=[]):
history.append(value)
return history

Безопаснее использовать None как метку «список не передан»:

def remember_measurement(value, history=None):
if history is None:
history = []
history.append(value)
return history

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

Что возвращает функция

return может возвращать число, строку, логическое значение, список или другой объект. Несколько результатов часто собирают в кортеж:

def wheel_speeds(base_speed, correction):
left = base_speed - correction
right = base_speed + correction
return left, right


left_speed, right_speed = wheel_speeds(40, 8)
print(left_speed, right_speed) # 32 48

Если функция достигает конца без return, результатом становится None. То же происходит при return без значения:

def print_status(message):
print(message)


result = print_status("Старт")
print(result) # None

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

print и return — разные действия

print показывает значение в консоли. return передаёт значение из функции в место вызова. Напечатанное значение не становится автоматически результатом функции.

Локальные имена и область видимости

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

def average_distance(readings_mm):
total = sum(readings_mm)
return total / len(readings_mm)


average = average_distance([220, 210, 205])
print(average)

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

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

Функции как этапы управления

Поток данных через функции к приводам

Алгоритм робота удобно разделить на три этапа:

  1. подготовить показание датчика;
  2. выбрать режим движения;
  3. сформировать команды левому и правому мотору.
def normalize_distance(raw_mm, offset_mm=0):
corrected = raw_mm + offset_mm
return max(0, corrected)


def choose_motion(distance_mm, stop_mm=120, slow_mm=300):
if distance_mm <= stop_mm:
return "stop"
if distance_mm <= slow_mm:
return "slow"
return "forward"


def speeds_for_motion(motion):
if motion == "stop":
return 0, 0
if motion == "slow":
return 25, 25
return 50, 50


raw_distance_mm = 268
distance_mm = normalize_distance(raw_distance_mm, offset_mm=-6)
motion = choose_motion(distance_mm)
left_speed, right_speed = speeds_for_motion(motion)

print(distance_mm, motion, left_speed, right_speed)

Каждая функция решает одну задачу и имеет наблюдаемый результат. Для проверки choose_motion не нужен настоящий дальномер: достаточно передать заранее выбранные расстояния. Для проверки speeds_for_motion не нужны моторы: можно сравнить возвращённую пару с ожидаемой командой.

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

Документация и аннотации типов

Короткая строка документации объясняет контракт функции. Аннотации подсказывают ожидаемые типы редактору и читателю:

def limit_percent(value: float, maximum: float = 100.0) -> float:
"""Ограничить неотрицательную команду заданным максимумом."""
return max(0.0, min(value, maximum))

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

Признак удачной функцииЗачем он нужен
Имя описывает действиеВызов читается как часть алгоритма
Одна ясная задачаПроще проверить и изменить
Явные параметрыВидны входные зависимости
Предсказуемый результатФункции легче соединять
Короткий контрактПонятны единицы, допустимые значения и эффект
Аппаратные эффекты отделены от расчётовЛогику можно тестировать без робота

Что запомнить о функциях

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

Практика

Задание 1. Перевод единиц

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

Задание 2. Безопасная скорость

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

Задание 3. Решение по двум датчикам

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

Задание 4. Именованные аргументы

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

Задание 5. Разделение алгоритма

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

Задание 6. Контракт

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

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

  1. Чем определение функции отличается от вызова?
  2. В чём разница между параметром и аргументом?
  3. Что происходит после выполнения return?
  4. Какой результат даёт функция без return?
  5. Зачем нужны именованные аргументы?
  6. Почему изменяемый список опасен как значение параметра по умолчанию?
  7. Где доступно локальное имя функции?
  8. Проверяет ли обычный Python аннотации типов автоматически при каждом вызове?
Ориентиры для самопроверки

Сначала ответьте без подсказки. Ответ можно считать полным, если вы:

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

Если один из пунктов объяснить не получается, найдите соответствующую главу статьи, перечитайте её и повторите ответ.

Словарь статьи

  • Функция — вызываемый именованный блок кода.
  • Определение функции — конструкция def, которая создаёт функцию.
  • Вызов — выражение, запускающее тело функции.
  • Параметр — имя входного значения в определении функции.
  • Аргумент — конкретное значение, переданное при вызове.
  • Возвращаемое значение — результат, который функция передаёт оператором return.
  • Значение по умолчанию — значение параметра, используемое при отсутствии соответствующего аргумента.
  • Именованный аргумент — аргумент, связанный с параметром по имени.
  • Область видимости — часть программы, где имя доступно.
  • Аннотация типа — метаданные об ожидаемом типе параметра или результата.
  • Побочный эффект — изменение внешнего состояния, например вывод текста или команда мотору.

Связанные темы

Источники