【问题标题】:What is a fast pythonic way to deepcopy just data from a python dict or list ?什么是从 python dict 或 list 中深度复制数据的快速 pythonic 方法?
【发布时间】:2017-08-24 09:37:35
【问题描述】:

当我们需要从包含原始数据类型的字典中复制完整数据时(为简单起见,让我们忽略 datetime 等数据类型的存在),我们最明显的选择是使用 deepcopy,但 deepcopy 比实现相同目的的其他一些骇人听闻的方法,即使用序列化-反序列化,例如 json-dump-json-load 或 msgpack-pack-msgpack-unpack。效率上的差异可以在这里看到:

>>> import timeit
>>> setup = '''
... import msgpack
... import json
... from copy import deepcopy
... data = {'name':'John Doe','ranks':{'sports':13,'edu':34,'arts':45},'grade':5}
... '''
>>> print(timeit.timeit('deepcopy(data)', setup=setup))
12.0860249996
>>> print(timeit.timeit('json.loads(json.dumps(data))', setup=setup))
9.07182312012
>>> print(timeit.timeit('msgpack.unpackb(msgpack.packb(data))', setup=setup))
1.42743492126

json 和 msgpack(或 cPickle)方法比普通的 deepcopy 更快,这很明显,因为 deepcopy 在复制对象的所有属性方面也会做得更多。

问题:有没有一种更 Pythonic/内置的方式来实现字典或列表的数据副本,而不需要 deepcopy 的所有开销?

【问题讨论】:

  • 在小数据集上衡量性能并据此得出结论很少有用。如果您有更多嵌套或更大的数据结构,deepcopy 仍然慢得多?
  • @MSeifert 我同意您的反馈,但我的目的不是将 deepcopy 与任何方法进行比较,我的主要问题是如果我的兴趣只是数据副本,如何减少 deepcopy 的所有开销。
  • 值得注意的是,json 的往返序列化并不总是等同于 copy.deepcopy。例如,deepcopy 将保留对同一对象的多个引用,如果它们嵌套在容器中。考虑D = {1: 2}; L = [D, D]。如果您使用 deepcopy 复制它,新列表仍将包含对单个 dict 的两个引用(D 的副本)。使用json,您将获得两个独立的字典。使用json 还会将字典中的整数键转换为字符串。我对msgpack不熟悉,所以不知道有没有和json一样的限制。

标签: python


【解决方案1】:

这真的取决于您的需求。 deepcopy 旨在做(最)正确的事情。它保留共享引用,它不会递归成无限递归结构等等......它可以通过保留一个memo 字典来做到这一点,其中所有遇到的“事物”都通过引用插入。这就是纯数据副本速度非常慢的原因。然而,我几乎总是说deepcopy最 Pythonic 的数据复制方式,即使其他方法可能更快。

如果您有纯数据和有限数量的类型,您可以构建自己的deepcopy(在实现deepcopy in CPython 之后构建大致):

_dispatcher = {}

def _copy_list(l, dispatch):
    ret = l.copy()
    for idx, item in enumerate(ret):
        cp = dispatch.get(type(item))
        if cp is not None:
            ret[idx] = cp(item, dispatch)
    return ret

def _copy_dict(d, dispatch):
    ret = d.copy()
    for key, value in ret.items():
        cp = dispatch.get(type(value))
        if cp is not None:
            ret[key] = cp(value, dispatch)

    return ret

_dispatcher[list] = _copy_list
_dispatcher[dict] = _copy_dict

def deepcopy(sth):
    cp = _dispatcher.get(type(sth))
    if cp is None:
        return sth
    else:
        return cp(sth, _dispatcher)

这只适用于所有不可变的非容器类型和listdict 实例。如果需要,您可以添加更多调度程序。

# Timings done on Python 3.5.3 - Windows - on a really slow laptop :-/

import copy
import msgpack
import json

import string

data = {'name':'John Doe','ranks':{'sports':13,'edu':34,'arts':45},'grade':5}

%timeit deepcopy(data)
# 11.9 µs ± 280 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit copy.deepcopy(data)
# 64.3 µs ± 1.15 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit json.loads(json.dumps(data))
# 65.9 µs ± 2.53 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit msgpack.unpackb(msgpack.packb(data))
# 56.5 µs ± 2.53 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

让我们也看看它在复制包含字符串和整数的大字典时的表现:

data = {''.join([a,b,c]): 1 for a in string.ascii_letters for b in string.ascii_letters for c in string.ascii_letters}

%timeit deepcopy(data)
# 194 ms ± 5.37 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
%timeit copy.deepcopy(data)
# 1.02 s ± 46.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit json.loads(json.dumps(data))
# 398 ms ± 20.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
%timeit msgpack.unpackb(msgpack.packb(data))
# 238 ms ± 8.81 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

【讨论】:

    【解决方案2】:

    我认为您可以通过覆盖 object.__deepcopy__ 来手动实现您所需要的。

    实现此目的的 Pythonic 方法是创建自定义 dict 从内置 dict 扩展并实现自定义 __deepcopy__

    【讨论】:

      【解决方案3】:

      @MSeifert 建议的答案不准确

      到目前为止,我发现 ujson.loads(ujson.dumps(my_dict)) 是最快的选项,但看起来很奇怪(如何将 dict 转换为字符串,然后从字符串转换为新 dict 比纯副本更快)

      这是我尝试过的方法的一个例子,以及它们在小字典中的运行时间(当然大字典的结果更清楚):

      x = {'a':1,'b':2,'c':3,'d':4, 'e':{'a':1,'b':2}}
      
      #this function only handle dict of dicts very similar to the suggested solution
      def fast_copy(d):
          output = d.copy()
          for key, value in output.items():
              output[key] = fast_copy(value) if isinstance(value, dict) else value        
          return output
      
      
      
      from copy import deepcopy
      import ujson
      
      
      %timeit deepcopy(x)
      13.5 µs ± 146 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
      
      %timeit fast_copy(x)
      2.57 µs ± 31.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
      
      %timeit ujson.loads(ujson.dumps(x))
      1.67 µs ± 14.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
      

      还有其他 C 扩展可能比 ujson 更好用吗? 很奇怪,这是复制大字典的最快方法。

      【讨论】:

        【解决方案4】:

        根据自己的数据结构编写自己的复制函数总是最快的。

        你的例子

        data = {
            'name': 'John Doe',
            'ranks': {
                'sports': 13,
                'edu': 34,
                'arts': 45
                },
            'grade': 5
            }
        

        是一个dict,仅由strs 或dicts 组成。 Hence:

        def copy(obj):
        
            out = obj.copy() # Shallow copy
        
            for k, v in obj.items():
        
                if isinstance(obj[k], dict):
        
                    out[k] = obj[k].copy()
        
            return obj
        
        %timeit deepcopy(data)
        5.26 µs ± 88.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
        %timeit json.loads(json.dumps(data))
        5.11 µs ± 117 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
        %timeit msgpack.unpackb(msgpack.packb(data))
        2.44 µs ± 76.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
        %timeit ujson.loads(ujson.dumps(data))
        1.63 µs ± 25.2 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
        
        %timeit copy(data)
        548 ns ± 5.77 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
        

        【讨论】:

          猜你喜欢
          • 2018-02-11
          • 2021-10-06
          • 1970-01-01
          • 2011-03-10
          • 2011-02-04
          • 2017-12-25
          • 2019-07-18
          • 2016-05-13
          • 1970-01-01
          相关资源
          最近更新 更多