Как скопировать словарь и редактировать только копию

856

Может кто-нибудь, пожалуйста, объясните мне это? Это не имеет никакого смысла для меня.

Я копирую словарь в другой и редактирую второй, и оба меняются. Почему это происходит?

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = dict1
>>> dict2
{'key2': 'value2', 'key1': 'value1'}
>>> dict2["key2"] = "WHY?!"
>>> dict1
{'key2': 'WHY?!', 'key1': 'value1'}
MadSc13ntist
источник
4
PythonTutor отлично подходит для визуализации ссылок на Python. Вот этот код на последнем шаге . Вы можете увидеть dict1и dict2указать на тот же самый диктат.
wjandrea

Ответы:

883

Python никогда не копирует объекты неявным образом. Когда вы устанавливаете dict2 = dict1, вы заставляете их ссылаться на один и тот же точный объект dict, поэтому, когда вы изменяете его, все ссылки на него продолжают ссылаться на объект в его текущем состоянии.

Если вы хотите скопировать диктовку (что редко), вы должны сделать это явно с

dict2 = dict(dict1)

или

dict2 = dict1.copy()
Майк Грэм
источник
26
Возможно, было бы лучше сказать «dict2 и dict1 указывают на один и тот же словарь», вы не меняете dict1 или dict2, но на что они указывают.
GrayWizardx
276
Также обратите внимание, что dict.copy () неглубокий, если в нем есть вложенный список / etc, то изменения будут применены к обоим. IIRC. Deepcopy избежит этого.
Уилл
16
Это не совсем правильно, что python никогда не копирует объекты неявным образом. Примитивные типы данных, такие как int, float и bool, также обрабатываются как объекты (просто сделайте a, dir(1)чтобы увидеть это), но они неявно копируются.
Даниэль Куллманн
17
@danielkullmann, я думаю, что у вас могут возникнуть недоразумения по поводу Python, основанные на том, как работают другие языки, с которыми вы работали. В Python: а) Нет понятия «примитивные типы данных». int, floatи boolэкземпляры являются реальными объектами Python, и б) объекты этих типов не копируются неявным образом при их передаче, точно не на уровне семантического Python, и даже не как детали реализации в CPython.
Майк Грэм
39
Необоснованная риторика типа «Глубокая копия считается вредной» бесполезна. При прочих равных условиях поверхностное копирование сложной структуры данных с гораздо большей вероятностью приведет к неожиданным проблемам, чем глубокое копирование той же структуры. Копия, в которой модификации изменяют исходный объект, не является копией; это ошибка Поэтому большинство случаев использования должны вызывать, copy.deepcopy()а не dict()или dict.copy(). Imran «s краткий ответ на правой стороне здравого смысла, в отличие от этого ответа.
Сесил Карри
647

Когда вы назначаете dict2 = dict1, вы не делаете копию dict1, это приводит к тому, dict2что вы просто получаете другое имя dict1.

Для того, чтобы скопировать изменяемые типы , как словари, использование copy/ deepcopyв copyмодуле.

import copy

dict2 = copy.deepcopy(dict1)
Имран
источник
80
Для любого словаря, с которым я когда-либо работал, глубокое копирование - это то, что мне нужно ... Я просто потерял несколько часов из-за ошибки, возникшей из-за того, что я не получил полную копию вложенного словаря, и мои изменения во вложенных записях влияли на оригинал ,
flutefreak7
7
Тоже самое. DeepCopy () делает свое дело. Помешал моим вложенным диктовкам во вращающемся кэше, добавив метку времени к «копии» исходного события. Спасибо!
fxstein
8
Это на самом деле должно быть помечено как правильный ответ; Этот ответ является общим и работает для словаря словарей.
orezvani
30
Это должен быть принятый ответ. Необоснованная риторика «Глубокое копирование считается вредным», встроенная в раздел комментариев текущего принятого ответа, явно вызывает проблемы синхронизации при копировании вложенных словарей (таких, как документированные здесь) и должна быть оспорена как таковая.
Сесил Карри
Deepcopy - это путь в случае сложной структуры словаря. dict1.copy () просто копирует значения ключей как ссылки, а не как объекты.
Рохит Н
182

Хотя dict.copy()и dict(dict1)создает копию, они являются только мелкими копиями. Если вы хотите глубокую копию, copy.deepcopy(dict1)требуется. Пример:

>>> source = {'a': 1, 'b': {'m': 4, 'n': 5, 'o': 6}, 'c': 3}
>>> copy1 = x.copy()
>>> copy2 = dict(x)
>>> import copy
>>> copy3 = copy.deepcopy(x)
>>> source['a'] = 10  # a change to first-level properties won't affect copies
>>> source
{'a': 10, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> copy1
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> copy2
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> copy3
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> source['b']['m'] = 40  # a change to deep properties WILL affect shallow copies 'b.m' property
>>> source
{'a': 10, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> copy1
{'a': 1, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> copy2
{'a': 1, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> copy3  # Deep copy's 'b.m' property is unaffected
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}

Относительно мелких и глубоких копий из документации по модулю Pythoncopy :

Разница между мелким и глубоким копированием относится только к составным объектам (объектам, которые содержат другие объекты, такие как списки или экземпляры классов):

  • Мелкая копия создает новый составной объект, а затем (насколько это возможно) вставляет в него ссылки на объекты, найденные в оригинале.
  • Глубокая копия создает новый составной объект, а затем рекурсивно вставляет в него копии объектов, найденных в оригинале.
gpanda
источник
2
это должен быть правильный ответ, так как он не выполняет явную петлю над диктовкой и может использоваться для других первичных структур.
Nikkolasg
27
Просто чтобы уточнить: w=copy.deepcopy(x)это ключевая линия.
Алкогольный день
В чем разница между dict2 = dict1и dict2 = copy.deepcopy(dict1)?
TheTank
1
@TheTank, y = x заставляет два имени (ссылки) ссылаться на один и тот же объект, т. Е. "Y is x" - True. Любое изменение, внесенное в объект через x, эквивалентно такому же изменению через y. Однако u, v, w являются ссылками на новые объекты, значения которых были скопированы из x во время создания экземпляра. Что касается различий между u, v (мелкая копия) и w (глубокая копия), пожалуйста, проверьте docs.python.org/2/library/copy.html
gpanda
63

В python 3.5+ существует более простой способ получить мелкую копию с помощью оператора ** распаковки **. Определено Пепом 448 .

>>>dict1 = {"key1": "value1", "key2": "value2"}
>>>dict2 = {**dict1}
>>>print(dict2)
{'key1': 'value1', 'key2': 'value2'}
>>>dict2["key2"] = "WHY?!"
>>>print(dict1)
{'key1': 'value1', 'key2': 'value2'}
>>>print(dict2)
{'key1': 'value1', 'key2': 'WHY?!'}

** распаковывает словарь в новый словарь, который затем назначается dict2.

Мы также можем подтвердить, что каждый словарь имеет отдельный идентификатор.

>>>id(dict1)
 178192816

>>>id(dict2)
 178192600

Если необходима глубокая копия, то метод copy.deepcopy () по-прежнему остается верным .

PabTorre
источник
3
Это ужасно похоже на указатели в C ++. Прекрасно подходит для выполнения задачи, но для удобства чтения мне не нравятся операторы такого типа.
Эрнесто
1
Он выглядит как-то странно, но при объединении нескольких словарей синтаксис выглядит довольно гладко.
PabTorre
2
Будьте осторожны с этим, он выполняет только поверхностную копию.
Себастьян Дресслер
Вы правы @SebastianDressler, я внесу коррективы. Thnx.
PabTorre
2
Полезно, если вы хотите создать копию с некоторыми политиками:dict2 = {**dict1, 'key3':'value3'}
evg656e
48

Самые лучшие и самые легкие способы создать копию в виде Словаря как в Python 2.7 и 3 являются ...

Чтобы создать копию простого (одноуровневого) словаря:

1. Использование метода dict () вместо генерации ссылки, указывающей на существующий dict.

my_dict1 = dict()
my_dict1["message"] = "Hello Python"
print(my_dict1)  # {'message':'Hello Python'}

my_dict2 = dict(my_dict1)
print(my_dict2)  # {'message':'Hello Python'}

# Made changes in my_dict1 
my_dict1["name"] = "Emrit"
print(my_dict1)  # {'message':'Hello Python', 'name' : 'Emrit'}
print(my_dict2)  # {'message':'Hello Python'}

2. Использование встроенного метода update () в словаре Python.

my_dict2 = dict()
my_dict2.update(my_dict1)
print(my_dict2)  # {'message':'Hello Python'}

# Made changes in my_dict1 
my_dict1["name"] = "Emrit"
print(my_dict1)  # {'message':'Hello Python', 'name' : 'Emrit'}
print(my_dict2)  # {'message':'Hello Python'}

Чтобы создать копию вложенного или сложного словаря:

Используйте встроенный модуль копирования , который обеспечивает общие операции мелкого и глубокого копирования. Этот модуль присутствует в Python 2.7 и 3. *

import copy

my_dict2 = copy.deepcopy(my_dict1)
AKay Nirala
источник
6
Я считаю, что dict()создает мелкую копию, а не глубокую копию. Это означает, что если у вас есть вложенный элемент, dictто внешний dictбудет копией, а внутренний дикт будет ссылкой на исходный внутренний диктат.
Шмуэльс
@shmuels да, оба эти метода создадут поверхностную копию, а не глубокую. Смотрите, обновленный ответ.
AKay Nirala
37

Вы также можете просто сделать новый словарь с пониманием словаря. Это позволяет избежать импорта копии.

dout = dict((k,v) for k,v in mydict.items())

Конечно, в python> = 2.7 вы можете сделать:

dout = {k:v for k,v in mydict.items()}

Но для обратного сравнения лучший метод лучше.

Лихий Адам Хьюз
источник
4
Это особенно полезно, если вы хотите больше контролировать, как и что именно копируется. +1
Приближается к
14
Обратите внимание, что этот метод не выполняет глубокое копирование, и если вы хотите, чтобы мелкое копирование без необходимости управления копируемыми ключами, d2 = dict.copy(d1)также не требует импорта.
Ярек Пиорковский
1
@ JarekPiórkowski: или вы можете вызвать метод, как метод:d2 = d1.copy()
Азат Ибраков
Обратите внимание, что вам не нужно понимание в первом примере. dict.itemsуже возвращает пару ключ / значение итеративно. Так что вы можете просто использовать dict(mydict.items())(вы также можете просто использовать dict(mydict)). Может быть полезно иметь понимание, если вы хотите отфильтровать записи.
Пол Руни
22

В дополнение к другим предоставленным решениям, вы можете использовать **для интеграции словарь в пустой словарь, например,

shallow_copy_of_other_dict = {**other_dict},

Теперь у вас будет «мелкая» копия other_dict.

Применительно к вашему примеру:

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = {**dict1}
>>> dict2
{'key1': 'value1', 'key2': 'value2'}
>>> dict2["key2"] = "WHY?!"
>>> dict1
{'key1': 'value1', 'key2': 'value2'}
>>>

Указатель: разница между мелкими и глубокими копиями

d4rty
источник
1
В результате получается мелкая копия, а не глубокая копия.
sytech
1
Я пытался это, но у меня возникли проблемы. Это работает только для Python 3.5 и выше. python.org/dev/peps/pep-0448
ThatGuyRob
19

Операторы присваивания в Python не копируют объекты, они создают привязки между целью и объектом.

Таким образом, dict2 = dict1это приводит к другому связыванию между dict2и объектом, на который dict1ссылаются.

если вы хотите скопировать диктовку, вы можете использовать copy module. Модуль копирования имеет два интерфейса:

copy.copy(x)
Return a shallow copy of x.

copy.deepcopy(x)
Return a deep copy of x.

Разница между мелким и глубоким копированием относится только к составным объектам (объектам, которые содержат другие объекты, такие как списки или экземпляры классов):

Неполная копия создает новый объект соединения , а затем (по мере возможности) вставляет ссылки в него на объекты найдены в оригинале.

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

Например, в Python 2.7.9:

>>> import copy
>>> a = [1,2,3,4,['a', 'b']]
>>> b = a
>>> c = copy.copy(a)
>>> d = copy.deepcopy(a)
>>> a.append(5)
>>> a[4].append('c')

и результат:

>>> a
[1, 2, 3, 4, ['a', 'b', 'c'], 5]
>>> b
[1, 2, 3, 4, ['a', 'b', 'c'], 5]
>>> c
[1, 2, 3, 4, ['a', 'b', 'c']]
>>> d
[1, 2, 3, 4, ['a', 'b']]
отпускает
источник
10

Вы можете копировать и редактировать вновь созданную копию за один раз, вызывая dictконструктор с дополнительными ключевыми аргументами:

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = dict(dict1, key2="WHY?!")
>>> dict1
{'key2': 'value2', 'key1': 'value1'}
>>> dict2
{'key2': 'WHY?!', 'key1': 'value1'}
Фрерих Раабе
источник
9

Поначалу меня это тоже смутило, потому что я пришел из C-фона.

В Си переменная - это место в памяти с определенным типом. Присвоение переменной копирует данные в ячейку памяти переменной.

Но в Python переменные действуют больше как указатели на объекты. Таким образом, присвоение одной переменной другой не делает копию, она просто указывает на то, что имя переменной указывает на тот же объект.

Крейг МакКуин
источник
5
переменные python действуют больше как ссылки на c ++
Ruggero Turra
7
Потому что все в Python является объектом! diveintopython.net/getting_to_know_python/… (да, этот ответ запоздал на много лет, но, возможно, он кому-то пригодится!)
мрачный
1
Я считаю, что семантика языка Python говорит, что нет никаких «переменных». Они называются «именными ссылками»; это означает, что ссылка на объект является синтаксической строкой в ​​коде. Объект может иметь много именованных ссылок на него. Неизменяемые объекты, такие как int, float и str, имеют только один экземпляр для каждого процесса. Целое число 1 в памяти не изменяется на 2 или другое значение по тому же адресу памяти, когда вы делаете это myvalue = 1 myvalue = 2
DevPlayer
7

Каждая переменная в python (например, dict1или strили __builtins__является указателем на какой-то скрытый платонический «объект» внутри машины).

Если вы установите dict1 = dict2, вы просто указываете dict1на тот же объект (или область памяти, или любую аналогию, которая вам нравится), как dict2. Теперь объект, на который ссылается, dict1является тем же объектом, на который ссылается dict2.

Вы можете проверить: dict1 is dict2должно быть True. Кроме того, id(dict1)должно быть так же, как id(dict2).

Вы хотите dict1 = copy(dict2)или dict1 = deepcopy(dict2).

Разница между copyа deepcopy? deepcopyудостоверится, что элементы dict2(вы указали это в списке?) также являются копиями.

Я не использую deepcopyмного - обычно плохая практика писать код, который нуждается в этом (по моему мнению).

wisty
источник
Я только что понял, что мне нужно всегда использовать глубокую копию, чтобы, когда я копирую вложенный словарь и начинаю изменять вложенные записи, эффекты возникают только на копии, а не на оригинале.
flutefreak7
6

dict1является символом, который ссылается на базовый объект словаря. Назначение dict1на dict2просто присваивает ту же ссылку. Изменение значения ключа с помощью dict2символа изменяет базовый объект, что также влияет dict1. Это смущает.

Гораздо проще рассуждать об неизменных значениях, чем о ссылках, поэтому по возможности делайте копии:

person = {'name': 'Mary', 'age': 25}
one_year_later = {**person, 'age': 26}  # does not mutate person dict

Это синтаксически так же, как:

one_year_later = dict(person, age=26)
Петрус Терон
источник
5

dict2 = dict1не копирует словарь. Он просто дает программисту второй способ ( dict2) ссылаться на тот же словарь.


источник
5
>>> dict2 = dict1
# dict2 is bind to the same Dict object which binds to dict1, so if you modify dict2, you will modify the dict1

Есть много способов скопировать объект Dict, я просто использую

dict_1 = {
           'a':1,
           'b':2
         }
dict_2 = {}
dict_2.update(dict_1)
imcaozi
источник
12
dict_2 = dict_1.copy()гораздо эффективнее и логичнее.
Жан-Франсуа Фабр
2
Обратите внимание, что если у вас есть dict внутри dict1, с помощью dict_1.copy () изменения, которые вы делаете для внутреннего dict в dict_2, также применяются к внутреннему dict в dict_1. В этом случае вы должны использовать copy.deepcopy (dict_1).
очередь
1

Как объяснили другие, встроенный dictне делает то, что вы хотите. Но в Python2 (и, вероятно, тоже 3) вы можете легко создать ValueDictкласс, который будет копировать, =чтобы вы могли быть уверены, что оригинал не изменится.

class ValueDict(dict):

    def __ilshift__(self, args):
        result = ValueDict(self)
        if isinstance(args, dict):
            dict.update(result, args)
        else:
            dict.__setitem__(result, *args)
        return result # Pythonic LVALUE modification

    def __irshift__(self, args):
        result = ValueDict(self)
        dict.__delitem__(result, args)
        return result # Pythonic LVALUE modification

    def __setitem__(self, k, v):
        raise AttributeError, \
            "Use \"value_dict<<='%s', ...\" instead of \"d[%s] = ...\"" % (k,k)

    def __delitem__(self, k):
        raise AttributeError, \
            "Use \"value_dict>>='%s'\" instead of \"del d[%s]" % (k,k)

    def update(self, d2):
        raise AttributeError, \
            "Use \"value_dict<<=dict2\" instead of \"value_dict.update(dict2)\""


# test
d = ValueDict()

d <<='apples', 5
d <<='pears', 8
print "d =", d

e = d
e <<='bananas', 1
print "e =", e
print "d =", d

d >>='pears'
print "d =", d
d <<={'blueberries': 2, 'watermelons': 315}
print "d =", d
print "e =", e
print "e['bananas'] =", e['bananas']


# result
d = {'apples': 5, 'pears': 8}
e = {'apples': 5, 'pears': 8, 'bananas': 1}
d = {'apples': 5, 'pears': 8}
d = {'apples': 5}
d = {'watermelons': 315, 'blueberries': 2, 'apples': 5}
e = {'apples': 5, 'pears': 8, 'bananas': 1}
e['bananas'] = 1

# e[0]=3
# would give:
# AttributeError: Use "value_dict<<='0', ..." instead of "d[0] = ..."

Пожалуйста, обратитесь к шаблону модификации lvalue, обсуждаемому здесь: Python 2.7 - чистый синтаксис для модификации lvalue . Ключевое наблюдение состоит в том , что strи intведет себя как ценности в Python (даже если они на самом деле неизменные объекты под капотом). Пока вы наблюдаете это, пожалуйста, также обратите внимание, что в этом нет ничего особенного strили волшебного int. dictможет использоваться во многом таким же образом, и я могу вспомнить много случаев, когда ValueDictимеет смысл.

personal_cloud
источник
0

следующий код, который содержит dicts, который следует синтаксису json более чем в 3 раза быстрее, чем Deepcopy

def CopyDict(dSrc):
    try:
        return json.loads(json.dumps(dSrc))
    except Exception as e:
        Logger.warning("Can't copy dict the preferred way:"+str(dSrc))
        return deepcopy(dSrc)
Карстен Тиелепап
источник
0

я столкнулся со странным поведением при попытке глубокого копирования словарного свойства класса без назначения его переменной

new = copy.deepcopy(my_class.a) не работает, т.е. модифицирует new модифицируетmy_class.a

но если вы делаете, old = my_class.aа затемnew = copy.deepcopy(old) он работает отлично, т.е. изменение newне влияетmy_class.a

Я не уверен, почему это происходит, но надеюсь, что это поможет сэкономить несколько часов! :)

Anushk
источник
Итак, как вы делаете глубокую копию my_class.a?
Энтони
Не лучший способ. Хороший ответ ниже.
Дэвид Бошемин
-1

потому что dict2 = dict1, dict2 содержит ссылку на dict1. Оба dict1 и dict2 указывают на одно и то же место в памяти. Это обычный случай при работе с изменяемыми объектами в Python. Когда вы работаете с изменяемыми объектами в Python, вы должны быть осторожны, так как это трудно отладить. Например, следующий пример.

 my_users = {
        'ids':[1,2],
        'blocked_ids':[5,6,7]
 }
 ids = my_users.get('ids')
 ids.extend(my_users.get('blocked_ids')) #all_ids
 print ids#output:[1, 2, 5, 6, 7]
 print my_users #output:{'blocked_ids': [5, 6, 7], 'ids': [1, 2, 5, 6, 7]}

В этом примере предполагается получить все идентификаторы пользователей, включая заблокированные идентификаторы. Это мы получили из переменной ids, но мы также непреднамеренно обновили значение my_users . Когда вы расширили идентификаторы с заблокированными, my_users были обновлены, потому что идентификаторы ссылаются на my_users .

Вкредди Коматиредды
источник
-1

Копирование с использованием цикла for:

orig = {"X2": 674.5, "X3": 245.0}

copy = {}
for key in orig:
    copy[key] = orig[key]

print(orig) # {'X2': 674.5, 'X3': 245.0}
print(copy) # {'X2': 674.5, 'X3': 245.0}
copy["X2"] = 808
print(orig) # {'X2': 674.5, 'X3': 245.0}
print(copy) # {'X2': 808, 'X3': 245.0}
Йеспер Йоахим Соренсен
источник
1
Это работает только для простых словарей. Почему бы не использовать deepcopy, который построен специально для этой цели?
Энтони
Не лучший способ. Хороший ответ ниже.
Дэвид Бошемин
-6

Вы можете использовать напрямую:

dict2 = eval(repr(dict1))

где объект dict2 является независимой копией dict1, поэтому вы можете изменять dict2, не затрагивая dict1.

Это работает для любого типа объекта.

Viiik
источник
4
Этот ответ неверен и не должен использоваться. Например, определенный пользователем класс может не иметь подходящих __repr__для восстановления посредством eval, и класс объекта не может находиться в текущей вызываемой области. Даже придерживаясь встроенных типов, это не удастся, если один и тот же объект хранится под несколькими ключами, как dict2тогда было бы два отдельных объекта. dict1Вместо этого будет содержаться самоссылочный словарь, в котором он содержится Ellipsis. Было бы лучше использоватьdict1.copy()
сыр Eldritch
Ожидается, что объекты (или «значения») не всегда будут иметь точное представление в виде символьных строк, а не в обычном удобочитаемом виде в любом случае.
Алексей