Аргументы и возвращаемые значения: как передавать и получать данные

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


Базовый принцип: вход и выход

У функции есть два канала обмена данными:

  • Вход: аргументы, которые вы передаёте при вызове.
  • Выход: значение, которое функция возвращает через return.

Простейший пример:

python
def сложить(a, b):
    return a + b
 
результат = сложить(2, 3)
print(результат)  # 5

Здесь a и b — аргументы, 5 — возвращаемое значение.

Важно: если в функции нет return, она возвращает None:

python
def просто_печатает(x):
    print(x)
 
r = просто_печатает(10)
print(r)  # None

Это частая ошибка: ожидают число или строку, а получают None, потому что забыли вернуть значение.


Позиционные и именованные аргументы

Аргументы можно передавать двумя способами: по позиции и по имени.

python
def заказать_кофе(вид, количество, молоко=True):
    тип_молока = "с молоком" if молоко else "без молока"
    return f"{количество} шт. {вид}, {тип_молока}"

Вызов по позиции (порядок важен):

python
заказать_кофе("американо", 2, False)
# "2 шт. американо, без молока"

Вызов с именованными аргументами (порядок не важен, код читаемее):

python
заказать_кофе(количество=3, вид="латте", молоко=True)
# "3 шт. латте, с молоком"

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


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

Значения по умолчанию задаются в заголовке функции и используются, если аргумент не передали:

python
def формат_даты(день, месяц, год, разделитель="-"):
    return f"{день}{разделитель}{месяц}{разделитель}{год}"

Примеры:

python
формат_даты(1, 5, 2025)                 # "1-5-2025"
формат_даты(1, 5, 2025, разделитель="/")  # "1/5/2025"

Важное предупреждение: не используйте изменяемые объекты (список, словарь) как значения по умолчанию. Пример проблемы:

python
# Плохо: список создаётся один раз при определении функции
def добавить_в_список(элемент, список=[]):
    список.append(элемент)
    return список

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

Правильный шаблон:

python
def добавить_в_список(элемент, список=None):
    if список is None:
        список = []
    список.append(элемент)
    return список

Переменное число аргументов: *args и **kwargs

Когда количество аргументов заранее неизвестно, используют *args (позиционные) и **kwargs (именованные).

*args — кортеж позиционных аргументов

python
def сумма(*числа):
    total = 0
    for n in числа:
        total += n
    return total

Вызовы:

python
сумма(1, 2, 3)          # 6
сумма(10, 20, 30, 40)   # 100

Внутри функции числа — это кортеж.

**kwargs — словарь именованных аргументов

python
def показать_параметры(**параметры):
    for ключ, значение in параметры.items():
        print(f"{ключ} = {значение}")

Вызов:

python
показать_параметры(цвет="красный", размер=12, активный=True)

Вывод:

text
цвет = красный
размер = 12
активный = True

На практике *args и **kwargs часто применяют в обёртках, декораторах и функциях, которые должны «пробрасывать» любые аргументы дальше.


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

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

Для неизменяемых объектов (числа, строки, кортежи) эффект выглядит как «передача по значению»:

python
def увеличить(x):
    x = x + 1
 
a = 5
увеличить(a)
print(a)  # 5 — переменная a не изменилась

Для изменяемых объектов (списки, словари) изменения внутри функции видны снаружи:

python
def добавить(список, элемент):
    список.append(элемент)
 
data = [1, 2]
добавить(data, 3)
print(data)  # [1, 2, 3]

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

python
def удвоить_список(список):
    новый = список.copy()
    for i in range(len(новый)):
        новый[i] *= 2
    return новый

Возврат нескольких значений

Функция может вернуть сразу несколько значений — на самом деле это один кортеж:

python
def статистика(числа):
    сумма_значений = sum(числа)
    среднее = сумма_значений / len(числа) if числа else 0
    минимум = min(числа) if числа else None
    максимум = max(числа) if числа else None
    return сумма_значений, среднее, минимум, максимум

Распаковка при вызове:

python
s, avg, mn, mx = статистика([1, 2, 3, 4, 5])
print(s, avg, mn, mx)
# 15 3.0 1 5

Можно вернуть и словарь, если имена полей важнее порядка:

python
def статистика_dict(числа):
    ...
    return {
        "сумма": сумма_значений,
        "среднее": среднее,
        "минимум": минимум,
        "максимум": максимум,
    }

Словарь удобнее, когда порядок значений не очевиден или их много.


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

Допустим, нужно принимать цену в разных форматах, проверять её и возвращать нормализованное значение и статус.

python
def нормализовать_цену(значение, валюта="руб.", минимальная=0):
    # попытка привести к float
    try:
        цена = float(значение)
    except (TypeError, ValueError):
        return None, "некорректный формат"
 
    if цена < минимальная:
        return None, f"цена ниже минимальной ({минимальная})"
 
    return round(цена, 2), "OK"

Использование:

python
цена, статус = нормализовать_цену("199.99")
if статус == "OK":
    print(f"Цена: {цена} {валюта}")
else:
    print("Ошибка:", статус)
 
цена2, статус2 = нормализовать_цена("abc")
print("Ошибка:", статус2)

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


Частые ошибки и как их избежать

  1. Забыли return и пытаются использовать результат.
    Всегда проверяйте, что функция действительно возвращает нужное значение.
  2. Изменяют входные аргументы, когда этого не ожидают.
    Если функция не должна менять данные — работайте с копиями или создавайте новые объекты.
  3. Используют изменяемые объекты как значения по умолчанию.
    Применяйте шаблон arg=None и инициализацию внутри тела функции.
  4. Путают позиционные и именованные аргументы при сложных сигнатурах.
    Когда параметров много, именованные аргументы делают код понятнее и уменьшают риск перепутать значения.

Пример: функция-агрегатор с гибкой передачей параметров

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

python
def продажи_статистика(*цены, валюта="руб.", округление=2):
    if not цены:
        return {"сумма": 0, "среднее": 0, "минимум": None, "максимум": None}
 
    сумма = sum(цены)
    среднее = сумма / len(цены)
    минимум = min(цены)
    максимум = max(цены)
 
    return {
        "сумма": round(сумма, округление),
        "среднее": round(среднее, округление),
        "минимум": round(минимум, округление),
        "максимум": round(максимум, округление),
        "валюта": валюта,
    }

Вызовы:

python
продажи_статистика(100, 200, 300)
продажи_статистика(99.5, 149.9, 299.0, валюта="USD", округление=1)

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

  • Аргументы — это способ передать данные внутрь функции. Используйте позиционные для простоты, именованные — для читаемости и гибкости.
  • Возвращаемое значение — это способ получить результат наружу. Если результат сложный, удобно возвращать кортеж или словарь.
  • Будьте внимательны с изменяемыми объектами: они могут «протекать» между внешней областью и функцией.
  • Значения по умолчанию делайте безопасными: избегайте изменяемых типов, используйте шаблон с None.
  • Если логика становится запутанной, разбейте её на несколько функций с понятными входами и выходами — так код будет проще поддерживать и тестировать.

Добавить комментарий