Что произойдет при изменении вложенного объекта (словаря) в копии списка
Что проверяют
Понимаете ли, что копия списка копирует ссылки, а не сами объекты, и знаете ли, когда этого достаточно, а когда нет.
Как отвечать
Все привычные способы дают поверхностную копию: a.copy(), срез a[:], list(a), copy.copy(a). Новый список создаётся, но элементы внутри те же самые объекты. Значит изменение вложенного словаря будет видно через оба списка.
a = [{"k": 1}, {"k": 2}]
b = a.copy()
b[0]["k"] = 99 # мутация общего словаря
a[0]["k"] # 99
b[0] = {"k": 0} # замена элемента, только в b
a[0]["k"] # по-прежнему 99Разница между двумя строками и есть суть ответа: мутация вложенного объекта видна обоим спискам, а переприсваивание элемента задевает только свой список, потому что меняет ссылку в нём, а не объект.
Полную независимость даёт copy.deepcopy(a): он рекурсивно копирует вложенное и запоминает уже скопированные объекты, поэтому циклические ссылки его не зацикливают. Платить приходится временем и памятью, а на объектах с внешними ресурсами, соединениями или файлами, он ломается. Когда вложенность одна, дешевле обойтись выражением вида [dict(d) for d in a].
Где спотыкаются
Проверяют независимость копии сравнением самих списков. То, что a is not b, говорит лишь о том, что список новый, а про элементы не говорит ничего. Проверять нужно a[0] is b[0].
Второе: вводят deepcopy везде на всякий случай. Если элементы неизменяемы, то есть числа, строки, кортежи из неизменяемого, поверхностной копии хватает, а глубокая на больших структурах становится заметной статьёй расходов.
Третье: не узнают тот же эффект в других обличьях. Выражение [[0] * 3] * 3 создаёт три ссылки на один и тот же список, и запись в одну строку меняет все. Туда же изменяемое значение по умолчанию у аргумента функции: оно создаётся один раз при объявлении и живёт между вызовами.