Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 4 из 20 Начальный 15 мин 100 XP

Умолчания и именованные аргументы в 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)
Python ответит SyntaxError: параметр без умолчания не может идти после параметра с умолчанием. Обязательные параметры объявляются первыми: def greet(name, greeting='Привет'):.

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

смешанные вызовы: позиция и имена
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)
Python ответит SyntaxError: positional argument follows keyword argument. Сначала все позиционные, потом все именованные.

Умолчание с условием: флаги поведения

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

флаг с умолчанием
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 и свежий список внутри функции:

правильный шаблон: 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('остановка')
Не запускается как задумано: datetime.now() вычислится один раз при загрузке файла, и обе строки получат одинаковое время запуска программы. Правильный шаблон — at=None, а внутри функции if at is None: at = datetime.now().

Тот же приём со счётчиком: 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 ответит TypeError: f() missing 1 required positional argument: 'a'. Именованный вызов не избавляет от обязательных параметров — a всё равно нужно передать, хоть позицией, хоть по имени.
Что пошло не такЧто скажет 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))
Проверь себя
0 / 9

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 в параметре?

Карточки терминов
Запомнено: 0 / 8
Практика

Напиши функцию make_tea(kind, sugar=2), которая печатает строку вида «чёрный, сахар: 2». Вызови её трижды: только с «чёрный»; с «зелёный» и sugar=0 по имени; с «травяной» и sugar=1 позицией.

practice.py
Вопросы и ответы по уроку

Как сделать необязательный аргумент функции в 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-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.