Как скопировать словарь и редактировать только копию
Может кто-нибудь, пожалуйста, объясните мне это? Это не имеет никакого смысла для меня.
Я копирую словарь в другой и редактирую второй, и оба меняются. Почему это происходит?
>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = dict1
>>> dict2
{'key2': 'value2', 'key1': 'value1'}
>>> dict2["key2"] = "WHY?!"
>>> dict1
{'key2': 'WHY?!', 'key1': 'value1'}
25 ответов
Python никогда не копирует объекты неявным образом. Когда вы установите dict2 = dict1
вы заставляете их ссылаться на один и тот же точный объект dict, поэтому, когда вы изменяете его, все ссылки на него продолжают ссылаться на объект в его текущем состоянии.
Если вы хотите скопировать диктовку (что редко), вы должны сделать это явно с
dict2 = dict(dict1)
или же
dict2 = dict1.copy()
Когда вы назначаете dict2 = dict1
Вы не делаете копию dict1
, это приводит к dict2
быть просто другим именем для dict1
,
Чтобы скопировать изменяемые типы, такие как словари, используйте copy
/ deepcopy
из copy
модуль.
import copy
dict2 = copy.deepcopy(dict1)
>>> x={'a': 1, 'b': {'m': 4, 'n': 5, 'o': 6}, 'c': 3}
>>> u=x.copy()
>>> v=dict(x)
>>> import copy
>>> w=copy.deepcopy(x)
>>> x['a']=10
>>> x
{'a': 10, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> u
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> v
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> w
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> x['b']['m']=40
>>> x
{'a': 10, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> u
{'a': 1, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> v
{'a': 1, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> w
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
Потому что 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.
В 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() по-прежнему остается верным.
Лучший и самый простой способ создать копию dict в 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)
Вы также можете просто сделать новый словарь с пониманием словаря. Это позволяет избежать импорта копии.
dout = dict((k,v) for k,v in mydict.items())
Конечно, в python >= 2.7 вы можете сделать:
dout = {k:v for k,v in mydict.items()}
Но для обратного сравнения лучший метод лучше.
В дополнение к другим предоставленным решениям вы можете использовать **
интегрировать словарь в пустой словарь, например,
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'}
>>>
Операторы присваивания в 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']]
Вы можете копировать и редактировать вновь созданную копию за один раз, вызвав dict
конструктор с дополнительными ключевыми аргументами:
>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = dict(dict1, key2="WHY?!")
>>> dict1
{'key2': 'value2', 'key1': 'value1'}
>>> dict2
{'key2': 'WHY?!', 'key1': 'value1'}
Поначалу меня это тоже смутило, потому что я происходил из языка Си.
В Си переменная - это место в памяти с определенным типом. Присвоение переменной копирует данные в ячейку памяти переменной.
Но в Python переменные действуют больше как указатели на объекты. Таким образом, присвоение одной переменной другой не делает копию, она просто указывает, что имя переменной указывает на тот же объект.
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)
Каждая переменная в 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
много - обычно плохая практика писать код, который нуждается в этом (на мой взгляд).
>>> 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)
dict2 = dict1
не копирует словарь. Это просто дает вам программист второй путь (dict2
) ссылаться на тот же словарь.
Просто для записей. Я использую следующий код, который на диктовках, который использует синтаксис 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)
Как объяснили другие, встроенный 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
имеет смысл.
для вложенных словарей не используйтеdict(srcData) or srcData.copy() or {**srcData}
потому что, если вы измените второй уровень и более, он также изменит исходный словарь
srcData = {
'first': {
'second': 'second Value'
}
}
newData = dict(srcData) # srcData.copy() or {**srcData}
newData['first']['second'] = 'new Second Value'
print(srcData)
print(newData)
# it will print
# srcData: {'first': {'second': 'new Second Value'}}
# newData:{'first': {'second': 'new Second Value'}}
# but it should be
# srcData: {'first': {'second': 'second Value'}}
# newData:{'first': {'second': 'new Second Value'}}
другой вариант для глубокого копирования используетjson
трюк, как JavascriptJSON.parse(JSON.stringify(obj))
import json
srcData = {'first': {'second': 'second Value'}}
newData = json.loads(json.dumps(srcData))
newData['first']['second'] = 'new Second Value'
print(srcData)
print(newData)
# srcData: {'first': {'second': 'second Value'}}
# newData: {'first': {'second': 'new Second Value'}}
Поскольку python работает со ссылкой, поэтому, когда вы сделали dict2 = dict1, вы передаете ссылку на dict2, это было то же самое, что и dict1. Таким образом, когда вы вносите изменения в dict1 или dict2, вы меняете ссылку, и оба дикта изменяются. Извините, если я что-то ошибаюсь на английском.
Хорошие объяснения, я хочу добавить простейшее правило, на которое вы можете ссылаться, думая о переменных Python, которым вы назначаете равные =
, Если тип данных является неизменяемым, вам не нужно беспокоиться о неожиданном поведении, с которым вы столкнулись. Если тип данных изменчив, вы должны убедиться, что сделали его копию, чтобы предотвратить непредвиденное поведение, с которым вы столкнулись.
Неизменяемые типы данных: строка (кортеж символов), кортеж
Изменяемые типы данных: список, массив, словарь
Я столкнулся со странным поведением при попытке глубоко скопировать свойство словаря класса без присвоения его переменной
new = copy.deepcopy(my_class.a)
не работает т.е. изменение new
изменяет my_class.a
но если вы это сделаете old = my_class.a
а потом new = copy.deepcopy(old)
он работает отлично, т.е. модифицирует new
не влияет my_class.a
Я не уверен, почему это происходит, но надеюсь, что это поможет сэкономить несколько часов!:)
Если вашdict
набирается какMapping
, ты не можешь.copy()
это, но ты можешь
dict2 = dict1 | {}
Это немного загадочно, и я не могу говорить о производительности по сравнению сcopy.copy(dict1)
, но очень лаконично.
Копирование с использованием цикла 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}
Другой более чистый способ - использовать json. см. ниже код
>>> a = [{"name":"Onkar","Address": {"state":"MH","country":"India","innerAddress":{"city":"Pune"}}}]
>>> b = json.dumps(a)
>>> b = json.loads(b)
>>> id(a)
2334461105416
>>> id(b)
2334461105224
>>> a[0]["Address"]["innerAddress"]["city"]="Nagpur"
>>> a
[{'name': 'Onkar', 'Address': {'state': 'MH', 'country': 'India', 'innerAddress': {'city': 'Nagpur'}}}]
>>> b
[{'name': 'Onkar', 'Address': {'state': 'MH', 'country': 'India', 'innerAddress': {'city': 'Pune'}}}]
>>> id(a[0]["Address"]["innerAddress"])
2334460618376
>>> id(b[0]["Address"]["innerAddress"])
2334424569880
Чтобы создать другой словарь, выполните json.dumps(), а затем json.loads () для того же объекта словаря. У вас будет отдельный объект dict.
Вы можете использовать напрямую:
dict2 = eval(repr(dict1))
где объект dict2 является независимой копией dict1, поэтому вы можете изменять dict2, не затрагивая dict1.
Это работает для любого типа объекта.