ChatGPT解决这个技术问题 Extra ChatGPT

如何复制字典并仅编辑副本

我设置了 dict2 = dict1。当我编辑 dict2 时,原来的 dict1 也发生了变化。为什么?

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = dict1
>>> dict2["key2"] = "WHY?!"
>>> dict1
{'key2': 'WHY?!', 'key1': 'value1'}
PythonTutor 非常适合可视化 Python 引用。 Here's this code at the last step。您可以看到 dict1dict2 指向同一个字典。
以防万一 PythonTutor 出现故障,这里是最后的 a screenshot 数据结构。

M
Mike Graham

Python 从不 隐式复制对象。当您设置 dict2 = dict1 时,您正在使它们引用相同的确切 dict 对象,因此当您对其进行变异时,对它的所有引用都会继续引用处于当前状态的对象。

如果你想复制字典(这很少见),你必须明确地这样做

dict2 = dict(dict1)

或者

dict2 = dict1.copy()

说“dict2 和 dict1 指向同一个字典”可能会更好,你不是在更改 dict1 或 dict2 而是它们指向的内容。
另请注意, dict.copy() 很浅,如果其中有嵌套列表/等,则将对两者都应用更改。 IIRC。 Deepcopy 将避免这种情况。
python 从不隐式复制对象并不完全正确。原始数据类型,例如 int、float 和 bool,也被视为对象(只需执行 dir(1) 即可看到),但它们是隐式复制的。
@danielkullmann,我认为您可能会根据您处理其他语言的工作方式对 Python 产生误解。在 Python 中,a) 没有“原始数据类型”的概念。 intfloatbool 实例是真正的 Python 对象,并且 b) 这些类型的对象在您传递它们时不会被隐式复制,肯定不是在语义 Python 级别,甚至不是作为实现细节CPython。
诸如“深拷贝被认为有害”之类的未经证实的言论是无益的。在其他条件相同的情况下,浅拷贝复杂数据结构明显比深拷贝相同结构更可能产生意想不到的边缘情况问题。修改原始对象的副本不是副本;这是一个错误。因此,大多数用例绝对应该调用 copy.deepcopy() 而不是 dict()dict.copy()。与此答案不同,Imranconcise answer 处于理智的右侧。
I
Imran

当您分配 dict2 = dict1 时,您不会复制 dict1,它导致 dict2 只是 dict1 的另一个名称。

要复制字典等可变类型,请使用 copy 模块的 copy / deepcopy

import copy

dict2 = copy.deepcopy(dict1)

对于我曾经使用过的任何字典,深拷贝是我所需要的......我只是因为一个错误而损失了几个小时,这是因为我没有获得嵌套字典的完整副本并且我对嵌套条目的更改影响了原始.
同样在这里。 deepcopy() 可以解决问题。通过向原始事件的“副本”添加时间戳,将我的嵌套字典弄乱了。谢谢!
这实际上应该被标记为正确答案;这个答案是通用的,它也适用于字典词典。
这应该是公认的答案。 当前 accepted answer 的评论部分中嵌入的未经证实的“深拷贝被认为是有害的”言论公然在复制嵌套字典(例如此处记录的字典)时引发同步问题并且应该受到这样的挑战。
谢谢, deepcopy() 是我需要的! copy() 仍然保留原始的引用似乎有点奇怪,但是嘿嘿。
a
alukach

虽然 dict.copy()dict(dict1) 生成一个副本,但它们只是 浅层 副本。如果您想要 deep 副本,则需要 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}}

关于浅拷贝和深拷贝,来自 Python copy module docs

浅拷贝和深拷贝之间的区别仅与复合对象(包含其他对象的对象,如列表或类实例)有关:浅拷贝构造一个新的复合对象,然后(尽可能)将引用插入到对象中在原文中找到。深拷贝构造一个新的复合对象,然后递归地将在原始对象中找到的对象的副本插入其中。


这应该是正确的答案,因为它不会显式循环字典并且可以用于其他主要结构。
澄清一下:w=copy.deepcopy(x) 是关键线。
dict2 = dict1dict2 = copy.deepcopy(dict1) 和有什么不一样?
@TheTank, y=x 使两个名称(引用)引用同一个对象,即“y 是 x”是 True。通过 x 对对象所做的任何更改都等效于通过 y 进行的相同更改。然而 u, v, w 是对新的不同对象的引用,这些对象在实例化期间具有从 x 复制的值。至于 u,v(shallow copy) 和 w(deepcopy) 的区别,请查看docs.python.org/2/library/copy.html
V
Vkreddy

深度和简单的记忆方法:

每当您执行 dict2 = dict1 时,dict2 指的是 dict1。 dict1 和 dict2 都指向内存中的相同位置。这只是在 python 中使用可变对象时的正常情况。当你在 python 中使用可变对象时,你必须小心,因为它很难调试。

而不是使用 dict2 = dict1,您应该使用 python 复制模块中的 copy(shallow copy) 和 deepcopy 方法将 dict2 与 dict1 分开。

正确的方法是:

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = dict1.copy()
>>> dict2
{'key1': 'value1', 'key2': 'value2'}
>>> dict2["key2"] = "WHY?"
>>> dict2
{'key1': 'value1', 'key2': 'WHY?'}
>>> dict1
{'key1': 'value1', 'key2': 'value2'}
>>> id(dict1)
140641178056312
>>> id(dict2)
140641176198960
>>> 

如您所见, dict1 和 dict2 的 id 不同,这意味着两者都指向/引用内存中的不同位置。

此解决方案适用于具有不可变值的字典,这对于具有可变值的字典不是正确的解决方案。

例如:

>>> import copy
>>> dict1 = {"key1" : "value1", "key2": {"mutable": True}}
>>> dict2 = dict1.copy()
>>> dict2
{'key1': 'value1', 'key2': {'mutable': True}}
>>> dict2["key2"]["mutable"] = False
>>> dict2
{'key1': 'value1', 'key2': {'mutable': False}}
>>> dict1
{'key1': 'value1', 'key2': {'mutable': False}}
>>> id(dict1)
140641197660704
>>> id(dict2)
140641196407832
>>> id(dict1["key2"])
140641176198960
>>> id(dict2["key2"])
140641176198960

您可以看到,即使我们为 dict1 应用了副本,但 mutable 的值在 dict2 和 dict1 上都更改为 false,即使我们只在 dict2 上更改它。这是因为我们更改了 dict1 的可变 dict 部分的值。当我们在 dict 上应用一个副本时,它只会做一个浅拷贝,这意味着它将所有不可变值复制到一个新的 dict 中并且不复制可变值但它会引用它们。

最终的解决方案是对 dict1 进行 deepycopy 以完全创建一个新的 dict,其中包含复制的所有值,包括可变值。

>>>import copy
>>> dict1 = {"key1" : "value1", "key2": {"mutable": True}}
>>> dict2 = copy.deepcopy(dict1)
>>> dict2
{'key1': 'value1', 'key2': {'mutable': True}}
>>> id(dict1)
140641196228824
>>> id(dict2)
140641197662072
>>> id(dict1["key2"])
140641178056312
>>> id(dict2["key2"])
140641197662000
>>> dict2["key2"]["mutable"] = False
>>> dict2
{'key1': 'value1', 'key2': {'mutable': False}}
>>> dict1
{'key1': 'value1', 'key2': {'mutable': True}}

如您所见,id 不同,这意味着 dict2 完全是一个新的 dict,其中包含 dict1 中的所有值。

如果您想更改任何可变值而不影响原始字典,则需要使用 Deepcopy。如果没有,您可以使用浅拷贝。 Deepcopy 很慢,因为它递归地复制原始字典中的任何嵌套值,并且还需要额外的内存。


P
PabTorre

在 python 3.5+ 上,有一种更简单的方法可以通过使用 ** 解包操作符来实现浅拷贝。由 Pep 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。

>>>id(dict1)
 178192816

>>>id(dict2)
 178192600

如果需要深拷贝,那么 copy.deepcopy() 仍然是可行的方法。


这看起来非常像 C++ 中的指针。很适合完成任务,但在可读性方面我倾向于不喜欢这种类型的运算符。
它确实有一种 c'ish 的外观......但是当将多个字典合并在一起时,语法看起来确实很流畅。
请注意,它只执行浅拷贝。
你是对的@SebastianDressler,我会做出调整。谢谢。
如果您想创建带有一些香料的副本,这很有用:dict2 = {**dict1, 'key3':'value3'}
A
Akay Nirala

在 Python 2.7 和 3 中创建 dict 副本的最佳和最简单的方法是...

要创建简单(单级)字典的副本:

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.使用python字典内置的update()方法。

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)

我相信 dict() 创建的是浅拷贝而不是深拷贝。这意味着如果您有一个嵌套的 dict,那么外部 dict 将是一个副本,但内部 dict 将是对原始内部 dict 的引用。
@shmuels 是的,这两种方法都会创建一个浅拷贝,而不是深拷贝。看,更新的答案。
D
Dashing Adam Hughes

您也可以只制作具有字典理解的新字典。这样可以避免导入副本。

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

当然在 python >= 2.7 你可以这样做:

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

但是对于向后兼容,top 方法更好。


如果您想更好地控制复制的方式和内容,这将特别有用。 +1
请注意,此方法不执行深层复制,如果您想要浅层复制而不需要控制要复制的键,d2 = dict.copy(d1) 也不需要任何导入。
@JarekPiórkowski:或者您可以像方法一样调用方法:d2 = d1.copy()
请注意,您不需要第一个示例中的理解。 dict.items 已返回可迭代的键/值对。所以你可以只使用 dict(mydict.items())(你也可以只使用 dict(mydict))。如果您想过滤条目,理解可能会很有用。
d
d4rty

除了提供的其他解决方案外,您还可以使用 ** 将字典集成到空字典中,例如,

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'}
>>>

Pointer: Difference between shallow and deep copys


这会导致浅拷贝,而不是深拷贝。
我正在尝试这个但遇到了麻烦。这仅适用于 python 3.5 及更高版本。 python.org/dev/peps/pep-0448
l
loosen

Python 中的赋值语句不复制对象,它们在目标和对象之间创建绑定。

因此,dict2 = dict1,它会导致 dict2dict1 引用的对象之间的另一个绑定。

如果要复制字典,可以使用 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']]

F
Frerich Raabe

您可以通过调用带有附加关键字参数的 dict 构造函数一次性复制和编辑新构造的副本:

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = dict(dict1, key2="WHY?!")
>>> dict1
{'key2': 'value2', 'key1': 'value1'}
>>> dict2
{'key2': 'WHY?!', 'key1': 'value1'}

唯一允许不可变添加到 dict 的 oneliner 答案
C
Craig McQueen

最初,这也让我感到困惑,因为我来自 C 背景。

在 C 中,变量是内存中具有已定义类型的位置。分配给变量会将数据复制到变量的内存位置。

但在 Python 中,变量更像是指向对象的指针。因此,将一个变量分配给另一个变量不会产生副本,它只会使该变量名指向同一个对象。


python 变量的行为更像 c++ 引用
因为 Python 中的一切都是对象! diveintopython.net/getting_to_know_python/…(是的,这个回复晚了很多年,但也许它对某人有些用处!)
我相信 Python 语言语义说没有“变量”。它们被称为“命名引用”;意味着对对象的引用是代码中的句法字符串。一个对象可以有许多对它的命名引用。不可变对象(如 int 和 float 以及 str 实例)每个进程只有一个实例。执行此操作时,内存中的 int 1 不会更改为 2 或同一内存地址处的其他值 myvalue=1 myvalue=2
P
Petrus Theron

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)

w
wisty

python 中的每个变量(像 dict1str__builtins__ 之类的东西都是指向机器内部一些隐藏的柏拉图式“对象”的指针。

如果您设置 dict1 = dict2,您只需将 dict1 指向与 dict2 相同的对象(或内存位置,或任何您喜欢的类比)。现在,dict1 引用的对象与 dict2 引用的对象相同。

您可以检查:dict1 is dict2 应该是 True。此外,id(dict1) 应与 id(dict2) 相同。

您想要 dict1 = copy(dict2)dict1 = deepcopy(dict2)

copydeepcopy 之间的区别? deepcopy 将确保 dict2 的元素(您是否将其指向列表?)也是副本。

我很少使用 deepcopy - 编写需要它的代码通常是不好的做法(在我看来)。


我刚刚意识到我需要始终使用 deepcopy,这样当我复制嵌套字典并开始修改嵌套条目时,效果只发生在副本上,而不是原始的。
佚名

dict2 = dict1 不复制字典。它只是为程序员提供了第二种方式 (dict2) 来引用同一个字典。


i
imcaozi
>>> 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)

dict_2 = dict_1.copy() 更加高效和合乎逻辑。
请注意,如果您在 dict1 中有一个 dict,则使用 dict_1.copy() 您对 dict_2 中的内部 dict 所做的更改也会应用于 dict_1 中的内部 dict。在这种情况下,您应该使用 copy.deepcopy(dict_1) 代替。
x
xaxxon

下面的代码,它遵循 json 语法的 dicts 比 deepcopy 快 3 倍以上

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)

p
personal_cloud

正如其他人所解释的那样,内置 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] = ..."

请参阅此处讨论的左值修改模式:Python 2.7 - clean syntax for lvalue modification。关键的观察是 strint 在 Python 中表现为值(即使它们实际上是引擎盖下的不可变对象)。在您观察这一点的同时,请注意 strint 并没有什么神奇之处。 dict 可以以几乎相同的方式使用,我能想到 ValueDict 有意义的许多情况。


A
Anushk

在尝试深度复制类的字典属性而不将其分配给变量时,我遇到了一种特殊的行为

new = copy.deepcopy(my_class.a) 不起作用,即修改 new 修改 my_class.a

但如果您先执行 old = my_class.a,然后执行 new = copy.deepcopy(old),它会完美运行,即修改 new 不会影响 my_class.a

我不确定为什么会发生这种情况,但希望它有助于节省一些时间! :)


那么如何制作 my_class.a 的深拷贝?
不是最好的方法。好的回应如下。
J
Jesper Joachim Sørensen

使用 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}

这仅适用于简单的字典。为什么不使用专门为此目的而构建的 deepcopy
不是最好的方法。好的回应如下。
不知何故,没有一个“副本”对我有用。只有这样才有效。另一种更好的编写方法是使用字典理解;像这样:def _copy_dict(dictionary:dict): return {key: dictionary[key] for key in dictionary}
V
Viiik

你可以直接使用:

dict2 = eval(repr(dict1))

其中object dict2是dict1的独立副本,所以可以修改dict2而不影响dict1。

这适用于任何类型的对象。


此答案不正确,不应使用。例如,用户定义的类可能没有合适的 __repr__ 来由 eval 重构,对象的类也不可能在要调用的当前范围内。即使坚持使用内置类型,如果同一个对象存储在多个键下,这也会失败,因为 dict2 将有两个单独的对象。 dict1 包含自身的自引用字典将包含 Ellipsis。最好使用 dict1.copy()
不期望对象(或“值”)总是由字符串忠实地表示,在任何情况下都不是以通常的人类可读方式。
O
Onkar

另一种更清洁的方法是使用 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 对象。


这仅适用于 json-serializable 条目,并且会产生很大的开销。