**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})
def wrapper(*args, **kwargs):
print('до вызова')
return func(*args, **kwargs)
| *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 такой защиты нет — любое имя считается допустимым, и опечатка превращается в молчаливую потерю настройки.
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)
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 внутри функции?
Напиши функцию print_card(**fields), которая печатает каждую пару «ключ: значение» с новой строки. Вызови её так: print_card(title='Записка', text='Позвони мне') — и добавь третий вызов с другим набором полей, например who='Вера' и when='вечером'.
Как принять в функцию произвольное число именованных аргументов?
Объявить параметр с двумя звёздочками: 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-канал или свой блог — так о проекте узнают новые читатели.
Что дальше
Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.
Что читать дальше
Функции · Урок 4
Умолчания и именованные аргументы в Python
Необязательные параметры с запасными значениями и вызов по имени: def f(x, n=2) и f(x=5, n=3). Разбираем порядок параметров и знакомимся с ловушкой изменяемого умолчания.
Функции · Урок 10
*args в Python: сколько аргументов угодно
Функция, которой нельзя заранее сказать, сколько значений придет: два числа, пять или ни одного. Разбираем звёздочку: упаковку в кортеж и распаковку при вызове.
Функции · Урок 16
Декораторы в Python: обёртка вокруг функции
Собираем декоратор руками: функция-обёртка, знак @, пропуск любых аргументов насквозь и functools.wraps, чтобы функция не теряла имя.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
Множества · Урок 14
Функция возвращает несколько значений: return с кортежем
Функция возвращает пару значений одной строкой: запятая в return собирает кортеж, распаковка раскладывает его по переменным. min и max за один проход, звёздочка для остатка.
функция возвращает кортеж pythonpython несколько значений из функции
json · Урок 6
Красивый JSON: indent и sort_keys
Одна строка против лесенки: indent=2 делает json читаемым для глаз, sort_keys наводит стабильный порядок ключей — и diff перестаёт врать.
печать словаря в json красивоjson конфиг python
Функции · Урок 9
sorted с key в Python: сортировка по правилу
Порядок по своему правилу: слова по длине, пары по цене, участники по очкам. Разбираем sorted, параметр key, reverse и стабильность Timsort.
key функция pythonpython функции уроки