Глибока копія дикта в пітоні


341

Я хотів би зробити глибоку копію dictпітона. На жаль, .deepcopy()метод не існує для dict. Як це зробити?

>>> my_dict = {'a': [1, 2, 3], 'b': [4, 5, 6]}
>>> my_copy = my_dict.deepcopy()
Traceback (most recent calll last):
  File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'deepcopy'
>>> my_copy = my_dict.copy()
>>> my_dict['a'][2] = 7
>>> my_copy['a'][2]
7

Останній рядок повинен бути 3.

Я хотів би, щоб зміни my_dictне впливали на знімок my_copy.

Як це зробити? Рішення повинно бути сумісним з Python 3.x.


3
Я не знаю, чи це дублікат, але це: stackoverflow.com/questions/838642/python-dictionary-deepcopy дуже близько.
charleslparker

Відповіді:


473

Як щодо:

import copy
d = { ... }
d2 = copy.deepcopy(d)

Python 2 або 3:

Python 3.2 (r32:88445, Feb 20 2011, 21:30:00) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import copy
>>> my_dict = {'a': [1, 2, 3], 'b': [4, 5, 6]}
>>> my_copy = copy.deepcopy(my_dict)
>>> my_dict['a'][2] = 7
>>> my_copy['a'][2]
3
>>>

16
Дійсно, це працює для спрощеного прикладу, який я наводив. Мої клавіші - це не числа, а об’єкти. Якщо я читаю документацію модуля копіювання, я повинен оголосити метод __copy __ () / __ deepcopy __ () для ключів. Дуже дякую, що повели мене туди!
Олів'є Грегоар

3
Чи є різниця в кодах Python 3.2 та 2.7? Вони здаються мені ідентичними. Якщо так, то краще буде один блок коду та вислів "Працює і для Python 3, і для 2"
MestreLion

30
Також варто згадати, copy.deepcopyчи не безпечно для ниток. Навчився цьому важким шляхом. З іншого боку, в залежності від вашого випадку використання json.loads(json.dumps(d)) є поточно, і працює добре.
пограбувати

1
@rob ви повинні опублікувати цей коментар як відповідь. Це життєздатна альтернатива. Нюанс безпеки нитки - важлива відмінність.
BuvinJ

3
@BuvinJ Проблема полягає в тому, що json.loadsце не вирішує проблему для всіх випадків використання, коли dictатрибути python не можна серіалізувати JSON. Це може допомогти тим, хто має справу лише з простими структурами даних, наприклад, з API, але я не думаю, що це достатньо рішення, щоб повністю відповісти на питання ОП.
пограбувати

36

dict.copy () - функція дрібної копіювання для словника
id - це вбудована функція, яка дає вам адресу змінної

Спочатку вам потрібно зрозуміти, "чому трапляється ця конкретна проблема?"

In [1]: my_dict = {'a': [1, 2, 3], 'b': [4, 5, 6]}

In [2]: my_copy = my_dict.copy()

In [3]: id(my_dict)
Out[3]: 140190444167808

In [4]: id(my_copy)
Out[4]: 140190444170328

In [5]: id(my_copy['a'])
Out[5]: 140190444024104

In [6]: id(my_dict['a'])
Out[6]: 140190444024104

Адреса списку, присутнього в обох диктах для клавіші 'a', вказує на те саме місце.
Тому, коли ви змінюєте значення списку в my_dict, змінюється і список у my_copy.


Рішення для структури даних, згаданої у запитанні:

In [7]: my_copy = {key: value[:] for key, value in my_dict.items()}

In [8]: id(my_copy['a'])
Out[8]: 140190444024176

Або ви можете скористатися глибокою копією, як згадувалося вище.


4
Ваше рішення не працює для вкладених словників. глибока копія є кращою з цієї причини.
Чарльз Плагер

2
@CharlesPlager Погодився! Але також слід помітити, що нарізка списку не працює на дікт value[:]. Рішення було для конкретної структури даних, згаданої у питанні, а не універсального рішення.
theBuzzyCoder

17

Python 3.x

з імпорту копії

my_dict = {'one': 1, 'two': 2}
new_dict_deepcopy = deepcopy(my_dict)

Без глибокого копіювання я не можу видалити словник імені хоста з мого доменного словника.

Без глибокої копії я отримую таку помилку:

"RuntimeError: dictionary changed size during iteration"

... коли я намагаюся видалити потрібний елемент зі свого словника всередині іншого словника.

import socket
import xml.etree.ElementTree as ET
from copy import deepcopy

домен - об’єкт словника

def remove_hostname(domain, hostname):
    domain_copy = deepcopy(domain)
    for domains, hosts in domain_copy.items():
        for host, port in hosts.items():
           if host == hostname:
                del domain[domains][host]
    return domain

Приклад виведення: [orginal] domeins = {'localdomain': {'localhost': {'all': '4000'}}}

[new] domeins = {'localdomain': {}}}

Отже, що тут відбувається - це ітерація над копією словника, а не повторення над самим словником. За допомогою цього методу ви зможете видалити елементи за потребою.


-3

Мені подобається і багато чому навчився у Лассе В. Карлсена. Я змінив його на наступний приклад, який досить добре виділяє різницю між дрібними копіями словника та глибокими копіями:

    import copy

    my_dict = {'a': [1, 2, 3], 'b': [4, 5, 6]}
    my_copy = copy.copy(my_dict)
    my_deepcopy = copy.deepcopy(my_dict)

Тепер якщо ти змінишся

    my_dict['a'][2] = 7

і робити

    print("my_copy a[2]: ",my_copy['a'][2],",whereas my_deepcopy a[2]: ", my_deepcopy['a'][2])

Ви отримуєте

    >> my_copy a[2]:  7 ,whereas my_deepcopy a[2]:  3

1
Чому ви вважаєте, що ця відповідь відрізняється від відповіді Лассе В. Карлсена ? Що додає, що інша відповідь не говорить?
Олів'є Грегоар

Привіт, Олів'є! Я не намагаюся брати за мету відповідь Лассе В. Карлсена - він по суті вирішив проблему, яку я мав, і я заборгував його. Мій коментар не відрізняється, він просто доповнює. З тієї простої причини, що вона протиставляє "копію" з "глибокою копією". Це було джерелом моєї проблеми, бо я помилився, коли використовував їх у рівнозначному порядку. Ура.
Рафаель Монтейро

-9

Більш просте (на мій погляд) рішення - створити новий словник та оновити його вмістом старого:

my_dict={'a':1}

my_copy = {}

my_copy.update( my_dict )

my_dict['a']=2

my_dict['a']
Out[34]: 2

my_copy['a']
Out[35]: 1

Проблема такого підходу полягає в тому, що він може бути недостатньо глибоким. тобто не є рекурсивно глибоким. досить хороший для простих об'єктів, але не для вкладених словників. Ось приклад, коли він може бути недостатньо глибоким:

my_dict1={'b':2}

my_dict2={'c':3}

my_dict3={ 'b': my_dict1, 'c':my_dict2 }

my_copy = {}

my_copy.update( my_dict3 )

my_dict1['b']='z'

my_copy
Out[42]: {'b': {'b': 'z'}, 'c': {'c': 3}}

За допомогою Deepcopy () я можу усунути напівглибоку поведінку, але, думаю, треба вирішити, який підхід підходить для вашої програми. У більшості випадків вас можуть не хвилювати, але слід пам’ятати про можливі підводні камені ... остаточний приклад:

import copy

my_copy2 = copy.deepcopy( my_dict3 )

my_dict1['b']='99'

my_copy2
Out[46]: {'b': {'b': 'z'}, 'c': {'c': 3}}

12
Це робить неглибоку копію дикту, про що запитуючий не просив. Об'єкти, які він містить, самі не копіюються. І простіший спосіб дрібного копіювання - це my_dict.copy()!
Blckknght
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.