В прошлом уроке вы уже писали функции с параметрами вроде 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.