什么是深度复制python dict或列表中的数据的快速pythonic方法?

Dhr*_*hak 25 python

当我们需要从包含原始数据类型的字典中复制完整数据时(为简单起见,让我们忽略数据类型的存在,比如datetime等),我们最明显的选择是使用deepcopy,但深度复制比其他一些实现的hackish方法要慢相同的,例如使用序列化 - 非序列化,例如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
Run Code Online (Sandbox Code Playgroud)

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

问题:是否有一种更加pythonic /内置的方式来实现字典或列表的数据副本,而没有深度复制所具有的所有开销?

MSe*_*ert 30

这真的取决于你的需求.deepcopy建立的目的是做(最)正确的事情.它保留了共享引用,它不会递归到无限递归结构等等......它可以通过保存一个memo字典来实现这一点,其中所有遇到的"事物"都通过引用插入.这就是使纯数据副本变得非常慢的原因.但是我几乎总是说这deepcopy复制数据的最pythonic方式,即使其他方法可能更快.

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

_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)
Run Code Online (Sandbox Code Playgroud)

这仅适用于所有不可变的非容器类型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)
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)


Sra*_*raw 5

我认为您可以通过重写来手动实现您需要的内容object.__deepcopy__

执行此操作的一种 Pythonic 方法是dict从内置创建自定义扩展dict并实现自定义__deepcopy__.