get + setdefault: почему выводится 3 3
@python_quizРазберем этот квиз
Коротко: в выражении, где вторым аргументом у dict.get() вызывается dict.setdefault(), setdefault выполняется и добавляет ключ в словарь до того, как get проверит наличие ключа. В результате оба значения оказываются равны 3.
Код из задания

Пошаговый разбор
- Создается словарь: config = {"timeout": 10}.
- Python должен вычислить аргументы для вызова метода get. Перед вызовом самого get выражение config.setdefault("retries", 3) выполняется.
- config.setdefault("retries", 3):
- Поскольку ключ "retries" отсутствует, setdefault добавляет его в словарь со значением 3 и возвращает 3.
- Теперь вызов выглядит как config.get("retries", 3). Но к этому моменту в config уже есть ключ "retries": 3.
- dict.get("retries", 3) возвращает значение по ключу — 3 (а не дефолт), потому что ключ уже есть.
- Итого: value == 3 и config["retries"] == 3, печатается "3 3".
Демонстрация и сравнение вариантов
Оригинальный пример (поведение из квиза):

Если использовать get с литеральным дефолтом (без setdefault), ключ не создается:

Аналогичный эффект создания ключа при явном вызове setdefault:

Важные замечания и рекомендации
- Встроенные функции/методы получают уже вычисленные аргументы: побочные эффекты, вызванные в аргументе, произойдут до выполнения самого метода.
- dict.get(key, default) возвращает default только если ключа нет; но если вы передаёте в качестве default выражение с побочным эффектом, этот эффект выполнится в момент вычисления аргумента, даже если ключ уже существует в словаре (в данном случае побочный эффект — добавление ключа через setdefault).
- Если вы не хотите, чтобы при запросе значения словарь модифицировался, не используйте побочные выражения в аргументе default. Предпочтительнее:
- передавать простой литерал как default (он не модифицирует словарь);
- явно проверить наличие ключа и при необходимости установить значение:
`python
if "retries" not in config:
config["retries"] = 3
value = config["retries"]
`
- или вызывать setdefault отдельно, если нужно именно гарантированно создать ключ:
`python
value = config.setdefault("retries", 3)
`
Вывод
В выражении с одновременным вызовом get и setdefault setdefault выполнится раньше и создаст ключ, поэтому и get вернёт значение этого ключа. Такое поведение удобно, но на практике лучше избегать скрытых побочных эффектов в аргументах — они затрудняют чтение кода и могут привести к неожиданным модификациям структуры данных.