$ sudo teach IT
Модуль 6 · Функции · Урок 6.2

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

Как правильно передавать данные в функцию, задавать значения по умолчанию и не попасться на скрытую ловушку изменяемых объектов

Теория~30 минутНовичокпараметры / аргументызначения по умолчаниюименованные аргументыmutable default

В прошлом уроке вы уже писали функции с параметрами вроде def area(width, height): — и это работало. Но там параметры были только "для галочки": один-два простых аргумента по порядку. На практике функции часто принимают данные необязательные, с запасным значением, да ещё и в разном порядке при вызове. Если не разобраться в правилах, легко получить функцию, которая падает или тихо возвращает не то, что вы ожидали. Разберём это по полочкам.

Параметр и аргумент — в чём разница

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

В Python эти две роли называются по-разному. Параметр — это переменная, которую вы описываете в заголовке функции, в строке def. Это то самое пустое поле бланка. Аргумент — это конкретное значение, которое вы передаёте при вызове функции. Это уже заполненный бланк. Термины часто путают даже опытные разработчики, но разница простая: параметр живёт в определении, аргумент — в вызове.

def greet(name):
    print(f"Привет, {name}!")

greet("Алиса")

Разберём построчно:

  • def greet(name): — объявляем функцию greet с одним параметром name. Это то самое поле бланка: пока функция не вызвана, name не хранит никакого конкретного значения.
  • print(f"Привет, {name}!") — тело функции. Когда функция выполнится, на месте {name} в f-строке подставится то, что попало в параметр.
  • greet("Алиса") — вызов функции. Строка "Алиса" здесь — это аргумент: конкретное значение, которое вливается в параметр name. Выведется Привет, Алиса!

Короткая формула для запоминания: параметр — в def, аргумент — в вызове. Когда функция начинает работать, аргумент "вливается" в параметр, и внутри функции они становятся одной и той же переменной.

Позиционные аргументы: порядок имеет значение

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

Такой способ передачи называется позиционным: аргументы сопоставляются с параметрами по позиции (месту), а не по смыслу.

def introduce(first_name, last_name, age):
    print(f"Меня зовут {first_name} {last_name}, мне {age} лет.")

introduce("Иван", "Петров", 25)
introduce(25, "Иван", "Петров")
  • def introduce(first_name, last_name, age): — три параметра, в этом порядке: имя, фамилия, возраст.
  • introduce("Иван", "Петров", 25) — первый аргумент "Иван" идёт в first_name, второй "Петров" — в last_name, третий 25 — в age. Выведется Меня зовут Иван Петров, мне 25 лет.
  • introduce(25, "Иван", "Петров") — тот же порядок сопоставления, но аргументы перепутаны местами: теперь first_name получает 25, а age получает "Петров". Выведется бессмыслица: Меня зовут 25 Иван, мне Петров лет.

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

Параметры со значением по умолчанию

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

Такое значение называется значением по умолчанию (default value). Задаётся оно прямо в заголовке функции через знак = сразу после имени параметра.

def greet(name, greeting="Привет"):
    print(f"{greeting}, {name}!")

greet("Алиса")
greet("Боб", "Добрый день")
  • def greet(name, greeting="Привет"): — у параметра greeting есть значение по умолчанию: строка "Привет". Параметр name значения по умолчанию не имеет — его нужно передавать всегда.
  • greet("Алиса") — передан только один аргумент. Он уходит в name, а greeting берёт значение по умолчанию. Выведется Привет, Алиса!
  • greet("Боб", "Добрый день") — передано два аргумента, второй заменяет умолчание. Выведется Добрый день, Боб!

Правило порядка: параметры со значением по умолчанию всегда идут в заголовке ПОСЛЕ параметров без умолчания. Обратный порядок — синтаксическая ошибка:

def wrong(greeting="Привет", name):  # SyntaxError
    ...

def correct(name, greeting="Привет"):  # так правильно
    ...

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

Именованные аргументы

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

Такой аргумент называется именованным, или keyword argument. Главное его преимущество: порядок передачи перестаёт иметь значение, потому что каждое значение явно подписано.

def create_user(name, age, city):
    print(f"{name}, {age} лет, город: {city}")

create_user("Анна", 30, "Москва")
create_user(age=30, city="Москва", name="Анна")
create_user("Анна", city="Москва", age=30)
  • create_user("Анна", 30, "Москва") — обычный позиционный вызов, порядок важен.
  • create_user(age=30, city="Москва", name="Анна") — все три аргумента именованные, и порядок теперь может быть любым: Python смотрит не на позицию, а на имя перед =.
  • create_user("Анна", city="Москва", age=30) — смешанный вызов: сначала один позиционный аргумент ("Анна" уходит в первый параметр name), затем именованные.

Все три строки выведут одно и то же: Анна, 30 лет, город: Москва. Именованные аргументы особенно полезны, когда у функции много параметров — вызов читается как объяснение, а не как загадка "что тут первое, а что второе".

Ловушка: в смешанном вызове позиционные аргументы обязаны идти раньше именованных. Написать create_user(name="Анна", 30, "Москва") нельзя — Python выдаст SyntaxError, потому что после именованного аргумента он уже не ждёт позиционных.

Ловушка изменяемого значения по умолчанию

Представьте, что стандартное значение бланка — не пустое поле, а общий блокнот, который выдают всем клиентам "на подпись". Один клиент что-то в нём написал — и следующий получает уже не чистый блокнот, а исписанный. В Python ровно такая проблема возникает, если в качестве умолчания задать список.

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

def add_item(item, lst=[]):
    lst.append(item)
    return lst

print(add_item("яблоко"))
print(add_item("банан"))

Большинство новичков ожидают ['яблоко'], а затем ['банан']. Реальный вывод другой:

['яблоко']
['яблоко', 'банан']

Список накапливается между вызовами. Причина в том, как Python обрабатывает значения по умолчанию: они вычисляются один раз — в момент, когда Python читает строку def, а не заново при каждом вызове функции. Список [] создаётся один раз и "прикрепляется" к функции навсегда. Каждый вызов add_item без аргумента lst получает один и тот же список — уже изменённый предыдущим вызовом.

Эта ситуация называется mutable default argument trap (ловушка изменяемого значения по умолчанию). Затрагивает она любые изменяемые типы: списки, словари, множества.

Правильное решение — использовать None как умолчание, а настоящий список создавать заново внутри функции:

def add_item(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst
  • def add_item(item, lst=None): — теперь умолчание — None, а это неизменяемое значение, оно не может "накапливаться".
  • if lst is None: — проверяем, передал ли вызывающий свой список. Если нет — lst всё ещё равен None.
  • lst = [] — создаём новый пустой список заново, при каждом вызове отдельный, а не один на всех.
  • lst.append(item) и return lst — дальше работаем как раньше, но уже с безопасным списком.

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

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

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

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

def double_first(numbers):
    numbers[0] = numbers[0] * 2

my_list = [10, 20, 30]
double_first(my_list)
print(my_list)
  • def double_first(numbers): — параметр numbers получит ту же самую ссылку, что и переданный список, а не его копию.
  • numbers[0] = numbers[0] * 2 — меняем первый элемент списка, на который указывает numbers. Это тот же список, что и my_list снаружи.
  • double_first(my_list) — вызов передаёт ссылку на my_list.
  • print(my_list) — выведет [20, 20, 30]: оригинал изменился, хотя мы явно не писали my_list = ... в основном коде.

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

def try_to_change(x):
    x = x * 2

number = 5
try_to_change(number)
print(number)
  • x = x * 2 — это не изменение "на месте", а создание новой локальной переменной x внутри функции. Снаружи её никто не видит.
  • print(number) — выведет 5: оригинальная переменная number не тронута, потому что числа неизменяемые.

Если нужно защитить список от изменений внутри функции, передайте туда копию, а не оригинал:

my_list = [10, 20, 30]
double_first(my_list[:])
print(my_list)
  • my_list[:] — срез без границ, он создаёт новый список с теми же элементами, а не ссылку на старый.
  • double_first(my_list[:]) — функция получает и меняет эту копию, оригинал my_list её не видит.
  • print(my_list) — выведет [10, 20, 30]: оригинал остался цел.

Итого: изменяемые объекты (список, словарь, множество) передаются по ссылке — функция может изменить оригинал. Неизменяемые (число, строка, кортеж) передаются так, что оригинал в безопасности.

Частые ошибки

Параметр без умолчания после параметра с умолчанием

def f(a=1, b): ... — SyntaxError. Параметры без умолчания всегда идут первыми.

Позиционный аргумент после именованного

f(a=1, 2) — SyntaxError. Как только в вызове встретился именованный аргумент, все следующие тоже должны быть именованными.

Список или словарь как значение по умолчанию

def f(lst=[]): ... — список общий для всех вызовов и накапливается. Используйте lst=None и создавайте список внутри функции.

Ожидание, что список внутри функции — копия

Список передаётся по ссылке. Если функция не должна менять оригинал — передавайте lst[:] или list(lst).

Что важно запомнить

  • Параметр — переменная в заголовке def. Аргумент — значение, переданное при вызове.
  • Позиционные аргументы сопоставляются по порядку — перепутать порядок легко и незаметно.
  • Параметры со значением по умолчанию всегда идут в заголовке после параметров без умолчания.
  • Именованные аргументы позволяют передавать значения в любом порядке, явно указав имя параметра; после первого именованного все следующие тоже именованные.
  • Никогда не используйте список, словарь или множество как значение по умолчанию — используйте None и создавайте объект внутри функции.
  • Изменяемые объекты (список, словарь, множество) передаются по ссылке: функция может изменить оригинал. Неизменяемые (число, строка, кортеж) — оригинал в безопасности.

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

6 вопросов

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

Напишите функцию create_profile(name, age, city="Unknown"), которая принимает имя (name), возраст (age) и необязательный город (city) со значением по умолчанию "Unknown".

Функция должна возвращать строку по шаблону: Name: {name}, Age: {age}, City: {city}.

Например, create_profile("Alice", 25) должна вернуть Name: Alice, Age: 25, City: Unknown.

Именованные аргументы

Premium

Форматирование цены

Premium