Как merge(*dicts, **extra) объединяет словари в Python
@python_quizРазберем этот квиз
Коротко: функция берет произвольное количество позиционных словарей и именованных аргументов, обновляет один результирующий словарь последовательно и печатает его. Важно — последняя запись перезаписывает предыдущие значения по ключу.
Исходный код и поведение

Что делает код:
- *dicts собирает все позиционные аргументы (в данном случае два словаря) в кортеж.
- **extra собирает именованные аргументы в словарь extra.
- Для каждого словаря из dicts вызывается res.update(d) (здесь это сделано внутри list comprehension, но результат списка игнорируется).
- Затем res.update(extra) — обновляет res ключами из именованных аргументов.
- В конце печатается res.
Результат выполнения:

Почему значение 'a' равно 3: ключ 'a' сначала был в позиционном словаре со значением 1, но затем был перезаписан именованным аргументом a=3, потому что update(extra) выполняется позже.
Анализ вариантов (кратко)
- Вариант с {'a': 1, 'b': 2, 'c': 4} предполагает, что значение из именованного аргумента не перезаписывает существующее — неверно, потому что update выполняется после добавления позиционных словарей.
- Вариант с {'a': 3, 'b': 2, 'c': 4} соответствует реальному поведению функции — именованный аргумент перезаписал 'a'.
- Вариант с дублирующимся ключом в выводе {'a': 1, 'b': 2, 'a': 3, 'c': 4} невозможно: в словаре не может быть два одинаковых ключа.
- Вариант с ошибкой о двойной передаче ключа неверен в этом случае: передача одинакового ключа в позиционном словаре и через именованный аргумент не вызывает исключения внутри такого сигнатуры функции (*dicts, **extra).
Нюансы и рекомендации
- List comprehension для сайд-эффектов
- Строка [res.update(d) for d in dicts] создаёт ненужный список из None и считается плохой практикой.
- Лучше использовать обычный цикл:
`python
for d in dicts:
res.update(d)
`
- Порядок обновления имеет значение
- Позиционные словари обновляются первыми, затем именованные аргументы. Последние записи побеждают при совпадении ключей.
- Если нужно обнаруживать конфликты
- Если вы хотите запретить перезапись ключей и поднять ошибку при конфликте, можно проверить пересечения ключей:
`python
def merge_strict(*dicts, **extra):
res = {}
for d in dicts:
overlap = set(res) & set(d)
if overlap:
raise ValueError(f"Duplicate keys in positional dicts: {overlap}")
res.update(d)
overlap_extra = set(res) & set(extra)
if overlap_extra:
raise ValueError(f"Duplicate keys between positional dicts and kwargs: {overlap_extra}")
res.update(extra)
return res
print(merge_strict({'a':1}, {'b':2}, a=3, c=4)) # вызовет ошибку из-за 'a'
`
- Возврат вместо печати
- Лучше, чтобы функция возвращала словарь, а не печатала его:
`python
def merge_return(*dicts, **extra):
res = {}
for d in dicts:
res.update(d)
res.update(extra)
return res
result = merge_return({'a':1}, {'b':2}, a=3, c=4)
print(result)
`
- Варианты передачи аргументов
- Если вы сделаете распаковку словаря в вызове, например merge(**{'a':1}), то этот словарь попадёт в именованные аргументы (extra), а не в positional dicts.
- Если цель — объединить несколько словарей и приоритет задать в определённом порядке, контролируйте порядок вызова update.
Вывод
Функция ведёт себя предсказуемо: обновления выполняются последовательно, и последние записи по ключу заменяют предыдущие. Использование list comprehension ради побочного эффекта нежелательно; предпочтительнее чистые циклы и возвращение результата вместо принта. Если перезапись ключей недопустима, добавьте проверку конфликтов.