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

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

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

**kwargs в Python: именованные на подхвате

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

Редакция Питоники

Прошлый урок научил функцию принимать сколько угодно позиционных значений: звёздочка собрала их в кортеж. Но значения бывают и именованные — такие вызовы, как `paint(color='зелёный')`, читаются лучше позиционных. А теперь представьте функцию настройки: кому-то нужен цвет и размер, кому-то — режим и число попыток. Какой набор именованных аргументов придёт в следующий вызов, функция знать не обязана. Для такого случая у звёздочки есть сестра — двойная.

Что такое **kwargs в Python: коротко

Двойная звёздочка в объявлении функции собирает все именованные аргументы вызова в словарь: def show(kwargs). Внутри это обычный словарь — настройки достают через get(ключ, умолчание), пары перебирают через items(). При вызове двойная звёздочка работает наоборот: f(options) превращает пары словаря в именованные аргументы.

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

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

двойная звёздочка упаковывает вызов
def show(**kwargs):
    print(kwargs)

show(color='красный', size=42)
show()
Вывод
{'color': 'красный', 'size': 42}
{}

Двойная звёздочка в объявлении собирает именованные аргументы в словарь: имя становится ключом, значение — значением. Вызов с двумя именованными аргументами дал словарь из двух пар, вызов без аргументов — пустой словарь, и ошибки при этом нет. Имя kwargs — от key word arguments, аргументы-ключи, — снова соглашение, а не правило языка: **options или **settings работают так же. Внутри функции это обычный словарь, с которым вы работали в курсе словарей: перебор по items(), проверка ключей, длина.

Перебор kwargs: пары ключ — значение

Словарь kwargs удобно печатать парами — ключ и значение через items():

профиль с неизвестным набором полей
def show_profile(name, **details):
    print('Имя:', name)
    for key, value in details.items():
        print(f'{key}: {value}')

show_profile('Анна', city='Тверь', hobby='гитара')
Вывод
Имя: Анна
city: Тверь
hobby: гитара

Обратите внимание: словарь получил только то, что не влезло в обычные параметры. Аргумент 'Анна' встал в name, а словарь details забрал лишь «лишние» именованные — город и увлечение. Если бы вызов прислал только имя, словарь остался бы пустым. Тот же приём работает рядом с умолчаниями: параметры с запасными значениями ловят известные настройки, а двойная звёздочка — всё остальное:

известное — в параметры, остальное — в словарь
def order(item, quantity=1, **extras):
    print(item, quantity, extras)

order('чай', sugar=2)
order('кофе')
Вывод
чай 1 {'sugar': 2}
кофе 1 {}

Полный набор: обычные, *args и **kwargs

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

три вида параметров в одном вызове
def demo(a, b, *args, **kwargs):
    print('a =', a, '| b =', b)
    print('args =', args)
    print('kwargs =', kwargs)

demo(1, 2, 3, 4, mode='быстро', tries=5)
Вывод
a = 1 | b = 2
args = (3, 4)
kwargs = {'mode': 'быстро', 'tries': 5}

Python разложил вызов по правилу: первые два позиционных значения — в a и b, оставшиеся позиционные — в кортеж args, именованные — в словарь kwargs. Порядок объявления менять нельзя: сначала обычные параметры, потом *args, потом **kwargs — запись def broken(**kwargs, *args) даже не запустится. Такой полный набор — заготовка для функций-обёрток, которым нужно пропустить через себя любой вызов, не зная его аргументов; в полную силу это заработает в уроке про декораторы.

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

Двойная звёздочка, как и одинарная, работает в обе стороны: в объявлении — упаковка, при вызове — распаковка. Словарь, собранный заранее, превращается в именованные аргументы:

словарь становится вызовом
def paint(color, shape):
    print(shape, '-', color)

options = {'color': 'зелёный', 'shape': 'круг'}
paint(**options)
paint('жёлтый', **{'shape': 'квадрат'})
Вывод
круг - зелёный
квадрат - жёлтый

Запись paint(**options) эквивалентна paint(color='зелёный', shape='круг'): каждая пара словаря стала именованным аргументом. Приём ценен, когда настройки собраны в словарь заранее — прочитаны из файла, получены от пользователя, — и функция хочет получить их обычными параметрами. Ключи такого словаря обязаны быть строками, причём строками-именами: словарь с ключом-числом распаковать не выйдет.

Конфиг-паттерн: kwargs.get

Самый частый бытовой сценарий kwargs — настройки с запасными значениями. Функция объявляет разумные умолчания, а вызов переопределяет те, что нужны:

отчёт с настройками по умолчанию
def make_report(title, **options):
    width = options.get('width', 20)
    footer = options.get('footer', 'конец отчёта')
    print('=' * width)
    print(title)
    print(footer)

make_report('Продажи', width=10)
print()
make_report('Итоги', width=10, footer='всё посчитано')
Вывод
==========
Продажи
конец отчёта

==========
Итоги
всё посчитано

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

ошибка: ключ пришёл дважды
# Так не запускается: конфликт имени и распаковки
def show(x, **kwargs):
    print(x, kwargs)

show(x=1, **{'x': 2})
Python ответит TypeError: show() got multiple values for keyword argument 'x'. Именованный аргумент x=1 и ключ x из распакованного словаря претендуют на один параметр — конфликт. Читается как обычный TypeError про аргументы, об их ошибках — в уроке 19.
фрагмент: универсальная обёртка
def wrapper(*args, **kwargs):
    print('до вызова')
    return func(*args, **kwargs)
Фрагмент: функция func объявлена выше. Обёртка ничего не знает об аргументах func — она собирает вызов в кортеж и словарь и распаковывает их насквозь, один в один. Так устроены декораторы из урока 16.
*args**kwargs
Собирает позиционные аргументыСобирает именованные аргументы
Получается кортежПолучается словарь
Одна звёздочка в объявлении и при вызовеДве звёздочки в объявлении и при вызове
Распаковывает списки и кортежиРаспаковывает словари со строковыми ключами
Идёт в объявлении после обычных параметровИдёт в объявлении последним

Функции научились принимать любые вызовы. Самое время оглядеться вокруг: Python набит готовыми функциями, которые не нужно ни объявлять, ни импортировать — len, sum, min, max и десятки других. Инструментальный ящик языка откроем в следующем уроке.

kwargs — обычный словарь

Отдельно стоит сказать: kwargs не какой-то особый тип. Это обычный словарь, который Python собрал за вас перед вызовом тела. С ним работают все методы и приёмы, которые вы знаете из курса словарей.

всё, что умеет словарь
def show(**kwargs):
    print('ключей:', len(kwargs))
    print('есть width:', 'width' in kwargs)
    print('имена:', list(kwargs.keys()))

show(width=20, color='красный')
Вывод
ключей: 2
есть width: True
имена: ['width', 'color']

Проверить наличие ключа, узнать число настроек, получить список имён — всё это обычные операции со словарём. Именно поэтому **kwargs легко сочетается с get, setdefault и распаковкой в другие вызовы.

Опечатка в имени проходит молча

Это главная цена гибкости. Обычные параметры защищены: если передать несуществующее имя, Python ответит TypeError. С **kwargs такой защиты нет — любое имя считается допустимым, и опечатка превращается в молчаливую потерю настройки.

wIDTH вместо width
def make_report(title, **options):
    width = options.get('width', 20)
    print(title, 'ширина', width)

make_report('Продажи', width=30)
make_report('Продажи', wIDTH=30)
Вывод
Продажи ширина 30
Продажи ширина 20

Вторая строка — тихий провал. Имя wIDTH отличается от width, но Python не знает, какие имена вы имели в виду: ключ wIDTH попал в словарь как обычный, а get('width', 20) своего ключа не нашёл и вернул запасное значение. Отчёт напечатался — с неверной шириной, и никто об этом не узнал.

Каждый вызов получает свой словарь

Хорошая новость про изменяемость: в отличие от списка в значении по умолчанию, kwargs создаётся заново при каждом вызове. Два вызова не поделят один словарь — правки внутри функции не утекут наружу.

словари вызовов не пересекаются
def collect(**items):
    items['added'] = True
    return items

print(collect(a=1))
print(collect(b=2))
Вывод
{'a': 1, 'added': True}
{'b': 2, 'added': True}

В первом вызове есть ключ a, во втором — b; ключ added добавился в оба, но это разные словари. Правило из урока про умолчания здесь не работает: **kwargs — это не значение по умолчанию, а новый словарь на каждый вызов.

Когда **kwargs, а когда обычные параметры

СитуацияКак объявитьПочему так
Набор настроек известен заранееОбычные параметры с умолчаниямиОпечатка в имени даст TypeError
Набор настроек меняется от вызова к вызову**kwargsЛишние настройки не ломают вызов
Настроек всего одна-двеОбычные параметрыПроще читать, подсказки редактора полезнее
Настройки надо передать дальшеkwargs и f(kwargs)Пробрасываются насквозь, ничего не теряется

Мы держимся правила: если имена настроек известны — объявляйте их обычными параметрами. **kwargs оставляем для случаев, когда состав настроек заранее неизвестен: чтение конфига, плагины, обёртки.

Практика: отчёт, собранный из настроек

Соберём вместе: обязательное значение — обычным параметром, необязательные настройки — через **kwargs, а запасные значения — через get.

отчёт из настроек
def make_report(title, **options):
    width = options.get('width', 20)
    footer = options.get('footer', 'конец отчёта')
    print('=' * width)
    print(title)
    print(footer)

make_report('Продажи', width=14, footer='итог за день')
Вывод
==============
Продажи
итог за день

Настройки заранее можно хранить в словаре и подставлять одним движением: make_report('Продажи', **settings). Такая схема удобна, пока состав настроек плавает. Как только он зафиксируется — переходите на обычные параметры с умолчаниями: опечатка в имени сразу станет ошибкой, а не тихой заменой настройки.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

def demo(**kw):
    print(kw)

demo(a=1, b=2)
def demo(a, **kw):
    print(a, kw)

demo(7, mode='быстро')
def paint(color, shape):
    print(shape, '-', color)

options = {'shape': 'круг', 'color': 'зелёный'}
paint(**options)
Проверь себя
0 / 9

1. Что такое kwargs внутри функции def f(**kwargs)?

2. Что напечатает def show(**kwargs): print(kwargs) при вызове show(x=1)?

3. В каком порядке объявляют обычные параметры, *args и **kwargs?

4. Что делает двойная звёздочка в вызове paint(**options), где options — словарь?

5. Зачем в kwargs.get('width', 20) нужен второй аргумент?

6. Что произойдёт при вызове paint(**{123: 'значение'})?

7. Функция make_report(title, **options) читает options.get('width', 20). Что будет, если вызвать её как make_report('Отчёт', wIDTH=30)?

8. Что вернёт len(kwargs) внутри функции def f(**kwargs)?

9. Можно ли изменить kwargs внутри функции?

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

Напиши функцию print_card(**fields), которая печатает каждую пару «ключ: значение» с новой строки. Вызови её так: print_card(title='Записка', text='Позвони мне') — и добавь третий вызов с другим набором полей, например who='Вера' и when='вечером'.

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

Как принять в функцию произвольное число именованных аргументов?

Объявить параметр с двумя звёздочками: def f(**kwargs). Все именованные аргументы вызова соберутся в словарь: вызов f(color='красный', size=42) даст {'color': 'красный', 'size': 42}. Словарь перебирают через items(), отсутствующие настройки достают через kwargs.get(ключ, умолчание).

Урок 4: умолчания и именованные аргументы

Чем *args отличается от **kwargs?

Одна звёздочка собирает позиционные аргументы в кортеж, двойная — именованные в словарь. В объявлении они идут по порядку: сначала обычные параметры, потом *args, потом **kwargs: def f(a, b, *args, **kwargs). При вызове распаковка тоже зеркальная: * раскладывает списки и кортежи, ** — словари.

Как передать словарь в функцию как набор аргументов?

Двойной звёздочкой при вызове: paint(**options) эквивалентно paint(color=..., shape=...) по парам словаря. Ключи словаря должны быть строками-именами Python; словарь с числовым ключом или ключом с дефисом распаковать не выйдет — будет TypeError.

Зачем обёртки пишут def wrapper(*args, **kwargs)?

Чтобы пропустить через себя любой вызов, не зная его аргументов: обёртка собирает вызов в кортеж и словарь, а внутри распаковывает их насквозь — func(*args, **kwargs). Это фундамент декораторов: так таймер или логирование оборачивают любые функции, от нуля аргументов до десяти.

Урок 16: декораторы — обёртка вокруг функции

Что произойдёт, если в имени аргумента опечатка?

С **kwargs — ничего страшного и ничего полезного: опечатка не вызовет ошибку, а создаст лишний ключ в словаре. Настройка с этим именем не применится, и функция молча возьмёт запасное значение. С обычными параметрами та же опечатка даёт TypeError — поэтому для известного набора имён мы их и объявляем явно.

Можно ли изменить kwargs внутри функции?

Да, kwargs — обычный словарь: можно добавлять ключи, удалять и обновлять. В отличие от списка в значении по умолчанию, каждый вызов получает свой собственный словарь, поэтому правки не утекают в следующий вызов.

Как узнать, какие именно настройки пришли в функцию?

Через словарь: list(kwargs.keys()) даст имена, len(kwargs) — количество, а цикл по kwargs.items() — все пары. Порядок ключей совпадает с порядком их появления в вызове.

Зачем в обёртке писать def wrapper(*args, **kwargs)?

Чтобы не знать заранее, как вызывают оборачиваемую функцию: обёртка собирает любой вызов в кортеж и словарь и распаковывает его насквозь — func(*args, **kwargs). Так пишут таймеры, логирование и проверки прав доступа.

Понравился урок? Сошлитесь на него

«Двойная звёздочка в объявлении собирает именованные аргументы в словарь: имя становится ключом, значение — значением.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

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

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

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