Глубокая копия словаря, созданного с пониманием, не работает

#python #deep-copy #dictionary-comprehension

#python #глубокая копия #словарь-понимание

Вопрос:

Не могли бы вы помочь мне понять, почему deepcopy не работает для всех элементов в словаре из примера ниже?

 import copy
a = [{'id':1, 'list':[1,2,3], 'num':3}, {'id':2,' list':[4,5,6], 'num':65}]
b = {i['id']:copy.deepcopy(i) for i in a}

In [1]: print(id(a) == id(b))                                                                                                                      
Out[1]: False

In [2]: print(id(a[0]) == id(b[1]))                                                                                                                   
Out[2]: False

In [3]: print(id(a[0]['list']) == id(b[1]['list']))                                                                                                                   
Out[3]: False

In [4]: print(id(a[0]['num']) == id(b[1]['num']))                                                                                                        
Out[4]: True
  

В частности, значения, связанные с 'num' ключом, совпадают, в то время как значения для 'list' ключа, похоже, были успешно скопированы deepcopy . Я предполагаю, что это связано с типом данных сохраняемого значения, не мог бы кто-нибудь указать мне правильное направление?

Спасибо!

Комментарии:

1. Это нормально. Ключи «num» являются неизменяемыми целочисленными объектами. Некоторые из них (от -100 до 256 или около того) предварительно создаются при запуске и всегда используются повторно.

Ответ №1:

Это не имеет ничего общего с пониманием dict, но, как вы предположили, с типом данных:

 >>> import copy
>>> x = 1
>>> copy.deepcopy(x) is x
True
>>> x = [1]
>>> copy.deepcopy(x) is x
False
  

Различие, сделанное @mengban, правильное: у вас есть изменяемые и неизменяемые объекты (это зависит от типа объекта). Типичными примерами неизменяемых объектов являются: целые числа ( 0 , 1 , 2 …), числа с плавающей запятой ( 3.14159 ), а также строки ( "foo" ) и кортежи ( (1, 3) ). Типичными примерами изменяемых объектов являются: списки ( [1, 2, 3] ) или словари ( {'a': 1, 'b': 2} ).

По сути, deepcopy неизменяемый объект возвращает сам объект: фактическая копия не выполняется (есть небольшая хитрость с кортежами: я объясню это позже):

 >>> x = "foo"
>>> copy.deepcopy(x) is x
True
>>> x = (1, 2)
>>> copy.deepcopy(x) is x
True
  

И deepcopy изменяемых объектов создает новый экземпляр объекта, имеющий те же элементы.

Это правильное поведение, потому что, когда вы приобрели глубокую копию o2 объекта o , контракт заключается в том, что это ваша копия. Никакая операция, выполняемая на o , не должна иметь возможности изменять o2 . Если o является неизменяемым, это гарантируется бесплатно. Но если o изменяемый, то вам нужно создать новый экземпляр с тем же содержимым (это подразумевает рекурсивную глубокую копию).

Теперь в чем дело с кортежами?

 >>> o = ([1], [2])
>>> copy.deepcopy(o) is o
False
  

Даже если сам кортеж неизменяем, возможно, один из его элементов может быть изменяемым. Если я дам вам ссылку o2 на значение o (ie o2 = o ), вы можете написать o2[0].append(10) , и мой объект o будет изменен. Следовательно deepcopy , функция ищет изменяемые объекты в кортеже и решает, нужна ли фактическая копия или нет.


Бонус: взгляните на deepcopy реализацию. Типы _deepcopy_dispatch сопоставлений с фактическим копировальным аппаратом:

 _deepcopy_dispatch = d = {}

...
d[int] = _deepcopy_atomic
d[float] = _deepcopy_atomic
d[bool] = _deepcopy_atomic
...
d[str] = _deepcopy_atomic
...
d[list] = _deepcopy_list
...
d[tuple] = _deepcopy_tuple
...
d[dict] = _deepcopy_dict
...
  

В то время _deepcopy_atomic как просто возвращает значение, _deepcopy_list , _deepcopy_tuple , _deepcopy_dict … обычно выполняет углубленную копию.

Вы можете проверить _deepcopy_tuple функцию, чтобы понять процесс. По сути, глубокое копирование каждого элемента до тех пор, пока не будет создана фактическая копия. Если копия была сделана, создайте новый кортеж глубоких копий. Else возвращает начальный кортеж.

Ответ №2:

Если вы не хотите, чтобы в вашем новом словаре была ссылка на ваш словарь, вы можете сделать следующее:

 new_dictionary = json.loads(json.dumps(old_dictionary))
  

Ответ №3:

Существует большая разница между изменяемыми и неизменяемыми типами в python. В общем, типы переменных в Python включают списки, словари и коллекции. Неизменяемые типы включают строки, int, float и кортежи. Повторное присвоение переменной неизменяемого типа фактически воссоздает объект неизменяемого типа и перенаправляет исходную переменную на вновь созданный объект (открывается новый адрес памяти), если никакие другие переменные не ссылаются на исходный объект (то есть счетчик ссылок равен 0), исходный объект будет переработан.

Комментарии:

1. «Повторное присвоение переменной неизменяемого типа фактически воссоздает объект неизменяемого типа и перенаправляет исходную переменную на вновь созданный объект» это абсолютно неверно. Семантика присваивания всегда одинакова, независимо от типа. Повторное присвоение никогда не создает новый объект. Это просто меняет то, на что ссылается имя

2. Кроме того, переменные не имеют типов