【问题标题】:Is deepcopy of an ordered dictionary expected to preserve its order?有序字典的深拷贝是否有望保留其顺序?
【发布时间】:2018-12-31 11:49:27
【问题描述】:

我做了一个小程序并测试它确实保持顺序。但是,我仍然想确保 deepcopy 能够做到这一点。

import copy
import collections

a_dict = collections.OrderedDict()
a_dict['m'] = 10
a_dict['u'] = 15
a_dict['c'] = 5
a_dict['h'] = 25
a_dict['a'] = 55
a_dict['s'] = 30

print(a_dict)

other_dict = copy.deepcopy(a_dict)

other_dict['g'] = 75
other_dict['r'] = 35

print(other_dict)

这个程序的输出是

OrderedDict([('m', 10), ('u', 15), ('c', 5), ('h', 25), ('a', 55), ('s', 30)])
OrderedDict([('m', 10), ('u', 15), ('c', 5), ('h', 25), ('a', 55), ('s', 30), ('g', 75), ('r', 35)])

【问题讨论】:

  • 您必须查看 c 代码以查看 deepcopy 是否能够复制定义ordereddicts 顺序的链表。我猜这是可行的。

标签: python deep-copy ordereddictionary


【解决方案1】:

通过copy.deepcopy 正确实现的复制应该产生一个与原始对象相等的对象(假设完全定义了相等性)。虽然没有,但没有明确的书面保证关于 OrderedDictcopy.deepcopy,具体来说,如果 OrderedDict 的顺序在副本中更改,它将不等于原始 OrderedDict,这将违反副本的期望- 大体上的平等。

不能真正给出确切的保证,因为键或值的__deepcopy__ 方法可能会做一些真正可怕的事情(例如修改源OrderedDict),但除了病理情况,你可以依赖@ 987654330@ 保留订单。

【讨论】:

  • 看起来OrderedDictcopy模块中没有特殊的大小写,这意味着它最终会酸洗和解酸来执行深层复制; the implementation of __reduce__ 返回 items 迭代器作为其返回 tuple 的第五项,根据 __reduce__ 文档,means it will be iterated in that order with the first element used as the key, and the second as the value,因此实现有意保留顺序。
  • 我不敢相信您将答案中最酷的部分放在评论中:)
  • @GregSchmit:它是 Python,所以实现细节不是官方的;规范是文档,而不是 CPython 参考解释器;实现细节可能会因版本和解释器而异。没有什么特别需要选择的特定实现(尽管它是一个简单的实现);理论上,其他一些 Python 解释器可能会选择不同的解释器。
【解决方案2】:

在 CPython 中,似乎保留了顺序。我通过检查deepcopy 的实现得出了这个结论。在这种情况下,它将在您的 OrderedDict 对象上找到 __reduce_ex____reduce__ 方法以用于酸洗:

(https://github.com/python/cpython/blob/master/Lib/copy.py#L159-L161)

def deepcopy(x, memo=None, _nil=[]):
...
                    reductor = getattr(x, "__reduce_ex__", None)
                    if reductor is not None:
                        rv = reductor(4)
                    else:
                        reductor = getattr(x, "__reduce__", None)
                        if reductor:
                            rv = reductor()

那些返回 odict_iterator 用于构造的对象,因此将保留顺序:

>>> a = {}
>>> b = collections.OrderedDict()
>>> a['a'] = 1
>>> b['a'] = 1
>>> a.__reduce_ex__(4)
(<function __newobj__ at 0x10471a158>, (<class 'dict'>,), None, None, <dict_itemiterator object at 0x104b5d958>)
>>> a.__reduce__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/local/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/copyreg.py", line 65, in _reduce_ex
    raise TypeError("can't pickle %s objects" % base.__name__)
TypeError: can't pickle dict objects
>>> b.__reduce_ex__(4)
(<class 'collections.OrderedDict'>, (), None, None, <odict_iterator object at 0x104c02d58>)
>>> b.__reduce__()
(<class 'collections.OrderedDict'>, (), None, None, <odict_iterator object at 0x104c5c780>)
>>> 

【讨论】:

    【解决方案3】:

    一个对象的深拷贝应该返回一个相同类型的对象。所以作为副本返回的有序字典应该保持有序?

    EDIT - 这不是完全正确的答案。请看下面 Greg 的评论。

    【讨论】:

    • 我不认为 OP 怀疑他们会收到另一个 OrderedDict,他们只是担心新版本可能有不同的顺序。
    • 好的。如果返回的对象是一个OrderedDict,不应该是有序的吗?如果返回的对象是 OrderedDict 并且它不是有序的,那么它违反了数据结构的定义。这就是我想要表达的观点。
    • @mod0,那么我认为您不了解 OrderedDicts 的工作原理。它们保持添加对象的顺序,所以如果你有两个以不同的顺序,你不一定知道哪个是“正确的”。问题是询问是否保留了相同的顺序,这不是您暗示的给定顺序,因为在复制期间,解释器可以以不同的顺序复制,因此返回不同的 OrderedDict。
    • @GregSchmit 你是对的。我很抱歉假设密钥已排序。我将编辑我的答案以反映这一点。
    • @mod0 它发生了;不用担心!
    【解决方案4】:

    由于有些人可能会来这里寻找与 dict(标准 dict,而不是 OrderedDict)相关的答案,我认为这会很有用:在 Python 3.6 或更高版本中,为 dict 保留了顺序,因此对 dict 进行了深拷贝将免费订购。 (如果您担心实现与规范,请使用 python 3.7,因为它现在在 3.7 语言规范中)

    请查看相关问题:Can I get JSON to load into an OrderedDict? 另请参阅有关使用 OrderedDict 保留 oder 的评论:https://gandenberger.org/2018/03/10/ordered-dicts-vs-ordereddict/

    【讨论】:

      猜你喜欢
      • 2010-10-24
      • 2014-10-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-04-26
      • 2020-11-06
      • 1970-01-01
      • 2010-09-16
      相关资源
      最近更新 更多