什么是一种快速的pythonic方法来从python目录或列表中深度复制数据?

什么是一种快速的pythonic方法来从python目录或列表中深度复制数据?,python,Python,当我们需要从包含基本数据类型的字典中复制完整数据时(为了简单起见,让我们忽略datetime等数据类型的存在),最明显的选择是使用deepcopy,但deepcopy比其他一些实现相同功能的黑客方法要慢,即使用序列化非序列化,例如json dump json load或msgpack pack msgpack unpack。从这里可以看出效率的差异: >>> import timeit >>> setup = ''' ... import msgpack ..

当我们需要从包含基本数据类型的字典中复制完整数据时(为了简单起见,让我们忽略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在复制对象的所有属性方面也会做得更多


问题:是否有一种更具python风格的/内置的方法来实现字典或列表的数据拷贝,而不需要像deepcopy那样的所有开销?

我认为您可以通过覆盖
对象来手动实现所需的功能


一种类似Python的方法是创建自定义的
dict
扩展自内置的
dict
,并实现自定义的
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
deepcopy
是为了做(最)正确的事情而构建的。它保持共享引用,不会递归到无限递归结构中,等等。。。它可以通过保存一个
备忘录
字典来做到这一点,其中所有遇到的“东西”都是通过引用插入的。这使得纯数据拷贝的速度非常慢。然而,我几乎总是说,
deepcopy
复制数据的最适合的方式,即使其他方法可能更快

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

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

# 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)

@mseifer建议的答案不准确

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

下面是我尝试过的方法的示例,以及它们在小字典中的运行时间(当然,使用大字典的结果更清楚):

x={'a':1,'b':2,'c':3,'d':4,'e':{'a':1,'b':2}
#此函数仅处理与建议的解决方案非常相似的dict
def快速拷贝(d):
输出=d.copy()
对于键,输出.items()中的值:
输出[键]=快速拷贝(值),如果是INSTANCE(值,dict)else值
返回输出
从复制导入deepcopy
导入ujson
%timeit深度拷贝(x)
每个回路13.5µs±146 ns(7次运行的平均值±标准偏差,每个100000个回路)
%timeit快速拷贝(x)
每个回路2.57µs±31.6 ns(7次运行的平均值±标准偏差,每个100000个回路)
%timeit ujson.load(ujson.dumps(x))
每个回路1.67µs±14.8 ns(7次运行的平均值±标准偏差,每个1000000个回路)
有没有其他C扩展比ujson更好?
非常奇怪的是,这是复制大型dict的最快方法。

在一个小数据集上测量性能并据此得出结论几乎没有用处。如果您有一个更嵌套或更大的数据结构,
deepcopy
仍然慢得多?@MSeifert我同意您的反馈,但我的目的不是将deepcopy与任何方法进行比较,我的主要问题是,如果我只对数据拷贝感兴趣,如何减少deepcopy的所有开销。相关:值得注意的是,使用
json
的往返序列化并不总是等同于
copy.deepcopy
。例如,
deepcopy
将保留对同一对象的多个引用(如果它们嵌套在一个容器中)。考虑<代码> d={ 1:2 };L=[D,D]
。如果使用
deepcopy
复制该目录,新列表仍将包含对单个目录的两个引用(一个
D
)。使用
json
,您将得到两个独立的dict。使用
json
还会将dict中的整数键转换为字符串。我不熟悉
msgpack
,因此我不知道它是否具有与
json
相同的限制。
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)