Умолчания и именованные аргументы в Python
Необязательные параметры с запасными значениями и вызов по имени: def f(x, n=2) и f(x=5, n=3). Разбираем порядок параметров и знакомимся с ловушкой изменяемого умолчания.
Редакция Питоники
Урок про аргументы ввёл жёсткое правило: сколько параметров — столько аргументов. Но половина аргументов в реальном коде — повторение одного и того же: степень два, валюта по умолчанию, режим «обычный». Python позволяет дать параметру запасное значение — и не передавать его в большинстве вызовов. Это умолчание, вторая ступень владения аргументами.
Значение по умолчанию: параметр с запасным значением
Умолчание пишется как присваивание прямо в объявлении: параметр = запасное значение:
def power(base, exponent=2):
print(f'{base} в степени {exponent} = {base ** exponent}')
power(5)
power(5, 3)
5 в степени 2 = 25 5 в степени 3 = 125
base — обязательный параметр: без него вызов не пройдёт. exponent имеет умолчание 2: в первом вызове он не передан — функция взяла запасное значение, во втором аргумент 3 заменил умолчание. Так один вызов становится короче, а функция — удобнее: частый случай написан в одну строку, редкий остаётся возможным.
Именованные аргументы: вызов по имени
Второй приём того же урока: передавать аргументы не позицией, а именем параметра. Имя пишется прямо в вызове: параметр = значение:
def order(item, quantity):
print(f'{item} — {quantity} шт.')
order('Ручка', 10)
order(quantity=3, item='Тетрадь')
Ручка — 10 шт. Тетрадь — 3 шт.
Второй вызов перечислил аргументы в обратном порядке — и смысл не перепутался: имя в вызове указывает, какому параметру предназначено значение. Именованный вызов длиннее, но самодокументируется: order(item='Тетрадь', quantity=3) читается как инструкция, а не как две безымянные величины. Это же лекарство от перепутанного порядка из урока про аргументы.
Порядок параметров: сначала обязательные
Обычные параметры и параметры с умолчанием смешивать можно, но в строгом порядке: сначала без умолчаний, потом с ними. Иначе Python не сможет разложить вызов:
# Так не запускается: параметр с умолчанием стоит перед обязательным
def greet(greeting='Привет', name):
print(greeting, name)
Правильный порядок делает вызовы гибкими: обязательные данные идут позицией, остальное — по желанию, по имени или умолчанием:
def profile(name, city='Москва', age=18):
print(f'{name}, {city}, {age} лет')
profile('Анна')
profile('Борис', 'Казань')
profile('Вера', age=25)
profile('Глеб', city='Сочи', age=30)
Анна, Москва, 18 лет Борис, Казань, 18 лет Вера, Москва, 25 лет Глеб, Сочи, 30 лет
Четыре вызова — четыре способа. Анна: только обязательный аргумент, оба умолчания сработали. Борис: второй аргумент positional — попал в city. Вера: пропустили city, но задали age по имени — умолчание города осталось на месте. Глеб: все параметры заданы по имени. После именованного аргумента в вызове дальше могут идти только именованные — позиционный в хвост не поставить:
# Так не запускается: позиционный аргумент после именованного
order(item='Тетрадь', 3)
Умолчание с условием: флаги поведения
Умолчания сочетаются с любыми аргументами — и позволяют функции менять поведение по флагу:
def repeat(text, times=2, upper=False):
if upper:
text = text.upper()
print(text * times)
repeat('ха')
repeat('ха', times=3)
repeat('ха', 3, True)
хаха хахаха ХАХАХА
Третий вызов передал все аргументы позициями — и заодно включил флаг: upper=True перевёл текст в верхний регистр ещё до умножения, и напечатались три «ХА». Флаг по умолчанию выключен: большинство вызовов его даже не упоминают, а включается он одним именованным аргументом.
Ловушка: изменяемое умолчание
Самая известная ловушка Python прячется в умолчаниях из списков и словарей. Кажется, что каждый вызов без аргумента получает свежий пустой список:
def add_task(task, tasks=[]):
tasks.append(task)
return tasks
print(add_task('полить цветы'))
print(add_task('вынести мусор'))
['полить цветы'] ['полить цветы', 'вынести мусор']
Второй вызов вернул список с двумя делами — хотя в него передали одно. Значение по умолчанию вычисляется один раз при объявлении функции, а не при каждом вызове. Один и тот же список живёт в функции между всеми вызовами без аргумента: первый вызов дописал в него «полить цветы», второй — «вынести мусор» в тот же список. Правильный шаблон — умолчание None и свежий список внутри функции:
def add_task(task, tasks=None):
if tasks is None:
tasks = []
tasks.append(task)
return tasks
print(add_task('полить цветы'))
print(add_task('вынести мусор'))
['полить цветы'] ['вынести мусор']
None — безопасное умолчание: ничего не создаётся заранее, а проверка if tasks is None делает новый список при каждом вызове без аргумента. Если передали готовый список — функция допишет в него, как и просил вызывающий код.
Умолчания и имена аргументов — язык, на котором функция объясняет свои привычки: что обязательно, что подставится само и как вызвать без запоминания порядка. В уроке про аргументы правило «сколько параметров — столько аргументов» было абсолютным, теперь у него есть разумные послабления. Следующий шаг — область видимости: где живут переменные, созданные внутри функции.
Умолчание — это обещание, а не магия
Умолчание читается вызывающим кодом как подсказка: «я не передаю этот параметр, потому что в девяти случаях из десяти значение одно и то же». Если это правда — отличный инструмент. Если неправда, вы прячете решение от того, кто вызывает функцию, и меняете поведение программы, не заметив этого: вызвали send_report(email) и не передали attach=False, а письмо ушло с вложением.
| Умолчание уместно | Умолчание опасно |
|---|---|
| Значение почти всегда одно: степень 2, разделитель по умолчанию | Значение редкое, его лучше видеть явно при вызове |
| Параметр технический: retries=3, verbose=False | Параметр часть предметной области: discount=10 |
| Вызывающий код не обязан знать про эту настройку | Настройка важна для результата — молчаливый режим опасен |
| Ошибка при отсутствии параметра вряд ли возможна | Отсутствие параметра — это ошибка в данных, а не повод молчать |
Практическое правило из нашей практики: если параметр можно безболезненно назвать «настройкой поведения», умолчание уместно. Если от его значения зависит смысл результата — передавайте явно, даже если это один и тот же код в трёх местах. Явное лучше неявного, когда речь идёт о деньгах и правах доступа.
Умолчание вычисляется один раз
Тонкое место, на котором спотыкаются даже опытные. Если в умолчании стоит вычисление — datetime.now(), счётчик, чтение файла, — оно выполняется в момент объявления функции, а не при каждом вызове. Значит, все вызовы получат одно и то же «замороженное» значение.
from datetime import datetime
def log_event(message, at=datetime.now()):
print(f'{at}: {message}')
log_event('запуск')
log_event('остановка')
Тот же приём со счётчиком: def next_id(prefix, counter=[0]) даст один и тот же номер для всех вызовов без аргумента. Правило простое: в умолчании — только неизменяемые константы (2, 'Москва', False, None). Всё, что требует вычисления, делается внутри функции по флагу is None.
Когда параметров становится много
Три параметра с умолчаниями — нормально. Пять — уже запах: вызов превращается в простыню, в которой половина значений не меняется от строки к строке. Настройки начинают выглядеть как словарь, и рано или поздно их состав станет меняться от заказа к заказу.
def send_report(email, subject='Отчёт за неделю', body='См. вложение', attach=True):
print('Кому:', email)
print('Тема:', subject)
print('Вложение:', attach)
send_report('anna@example.com')
send_report('borya@example.com', attach=False)
Кому: anna@example.com Тема: Отчёт за неделю Вложение: True Кому: borya@example.com Тема: Отчёт за неделю Вложение: False
Пока набор настроек фиксирован, такой вызов читается. Как только настроек станет больше пяти или они начнут добавляться — переходим на словарь: **kwargs принимает любое количество именованных параметров и складывает их в один словарь. Это урок 11, а пока держимся простого правила: до трёх параметров — обычные, дальше думаем.
def f(a, b=2):
return a + b
f(b=5)
| Что пошло не так | Что скажет Python | Что делать |
|---|---|---|
| Обязательный параметр после умолчания | SyntaxError: parameter without a default follows parameter with a default | Обязательные параметры — первыми |
| Позиционный аргумент после именованного | SyntaxError: positional argument follows keyword argument | Сначала позиционные, потом именованные |
| Именованный аргумент, а обязательный пропущен | TypeError: missing 1 required positional argument | Передайте обязательный параметр |
| Список в умолчании | Ошибки нет — значения накапливаются | Шаблон None и свежий контейнер |
| datetime.now() в умолчании | Ошибки нет — время заморожено | Умолчание None, вычисление внутри |
Сначала предскажи ответ в голове — это главный навык программиста.
def f(a, b=10):
return a + b
print(f(1))
print(f(1, 2))
def f(x, y):
print(f'{x}-{y}')
f(y=2, x=1)
def collect(x, box=[]):
box.append(x)
return box
print(collect(1))
print(collect(2))
1. Функция объявлена как def f(a, b=2):. Какой вызов допустим и оставит b равным 2?
2. Что даёт именованный вызов f(y=2, x=1) для функции def f(x, y):?
3. Что произойдёт при объявлении def f(a=1, b):?
4. Можно ли смешивать позиционные и именованные аргументы в одном вызове?
5. Что не так с объявлением def add(x, items=[]):?
6. Как правильно исправить ловушку изменяемого умолчания?
7. Почему нельзя писать def log(msg, at=datetime.now())?
8. Что произойдёт при вызове f(b=5) для функции def f(a, b=2)?
9. Что означает умолчание None в параметре?
Напиши функцию make_tea(kind, sugar=2), которая печатает строку вида «чёрный, сахар: 2». Вызови её трижды: только с «чёрный»; с «зелёный» и sugar=0 по имени; с «травяной» и sugar=1 позицией.
Как сделать необязательный аргумент функции в Python?
Дать параметру значение по умолчанию: def greet(name, greeting='Привет'): — вызов greet('Анна') подставит умолчание, а greet('Анна', 'Здравствуйте') заменит его. Обязательные параметры объявляются до параметров с умолчаниями.
Урок 2: аргументы функцииМожно ли смешивать позиционные и именованные аргументы?
Да: сначала позиционные, потом именованные — f('Анна', age=25) допустим. Позиционный аргумент после именованного даёт SyntaxError: positional argument follows keyword argument.
Почему список в значении по умолчанию «запоминает» прошлые вызовы?
Умолчание вычисляется один раз при объявлении функции: все вызовы без аргумента получают один и тот же список и дописывают в него. Лечение — умолчание None и создание нового списка внутри функции после проверки if items is None.
Зачем нужны именованные аргументы?
Они не зависят от порядка — вызов не перепутается, как в уроке про позиционные аргументы, — и читаются как инструкция: repeat('стоп', upper=True) понятен без заглядывания в объявление функции.
Почему значение по умолчанию вычисляется только один раз?
Потому что Python создаёт его в момент объявления функции, при выполнении строки def, — и дальше все вызовы без аргумента получают то же самое значение. Поэтому datetime.now() в умолчании замерзает на времени запуска программы. Всё, что нужно вычислять, делается внутри функции по проверке if параметр is None.
Можно ли указать в значении по умолчанию вызов другой функции?
Можно: def parse(value, cast=int) — и внутри тела вызывается cast(value). Но такая функция тоже вычислится один раз, в момент объявления. Если нужно вычислять на каждый вызов, ставьте None и вызывайте функцию внутри тела по проверке is None.
Сколько параметров с умолчаниями допустимо у функции?
Пока три-четыре — спокойно. Больше — сигнал, что у функции слишком много настроек и пора переходить на словарь: **kwargs принимает любое число именованных параметров и складывает их в словарь. Это разбирается в уроке 11.
Понравился урок? Сошлитесь на него
«Значение по умолчанию вычисляется один раз при объявлении функции, а не при каждом вызове.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Функции · Урок 2
Аргументы функции в Python: данные внутрь
Функция принимает данные через параметры: greet('Анна') здоровается с Анной. Разбираем позиционные аргументы, порядок и количество — и типичную ошибку TypeError.
Функции · Урок 3
Return в Python: функция возвращает результат
print показывает значение человеку, return отдаёт программе. Учимся возвращать результат, класть его в переменную и использовать в выражениях — и не путать return с print.
Функции · Урок 10
*args в Python: сколько аргументов угодно
Функция, которой нельзя заранее сказать, сколько значений придет: два числа, пять или ни одного. Разбираем звёздочку: упаковку в кортеж и распаковку при вызове.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
FastAPI · Урок 3
Параметры запросов: path, query и валидация в FastAPI
Осваиваем path- и query-параметры FastAPI: типы, дефолты, валидация через Query и чтение ошибки 422.
fastapi query параметрыpath параметры fastapi
sqlite3 · Урок 6
Агрегаты и GROUP BY в SQL: COUNT, SUM, AVG и HAVING
Считаем, суммируем и усредняем по всей таблице и по группам: COUNT, SUM, AVG, GROUP BY и фильтр групп HAVING.
sql агрегатные функциисреднее значение sql
Функции · Урок 11
**kwargs в Python: именованные на подхвате
Позиционные аргументы умеет собирать звёздочка — а какие именованные прислал вызов, заранее не знал никто. Разбираем двойную звёздочку и словарь настроек.
именованные аргументы pythonpython функции уроки