lenguaje - python tutorial
¿Cuál es una manera rápida de realizar una copia en profundidad de los datos de un dictado o una lista de python? (3)
Cuando necesitamos copiar datos completos de un diccionario que contenga tipos de datos primitivos (para simplificar, ignoremos la presencia de tipos de datos como fecha y hora, etc.), la opción más obvia que tenemos es utilizar la deepcopy
, pero la copia profunda es más lenta que otros métodos de piratería. lograr lo mismo, es decir, utilizar serialización-unserialization por ejemplo como json-dump-json-load o msgpack-pack-msgpack-unpack. La diferencia en eficiencia se puede ver aquí:
>>> 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
Los métodos json y msgpack (o cPickle) son más rápidos que una copia profunda normal, lo que es obvio, ya que la copia profunda haría mucho más en la copia de todos los atributos del objeto.
Pregunta: ¿Existe una forma más pythonic / incorporada de lograr solo una copia de datos de un diccionario o lista, sin tener toda la sobrecarga que tiene deepcopy?
@MSeifert La respuesta sugerida no es precisa
Hasta ahora, encontré que ujson.loads (ujson.dumps (my_dict)) es la opción más rápida, que se ve extraña (cómo traducir dict a cadena y luego de string a nuevo dict es más rápido que una copia pura)
Aquí hay un ejemplo de los métodos que probé y su tiempo de ejecución para un diccionario pequeño (los resultados, por supuesto, son más claros con un diccionario más grande):
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)
¿Hay alguna otra extensión de C que pueda funcionar mejor que ujson? Es muy extraño que este sea el método más rápido para copiar grandes dict.
Creo que puedes implementar manualmente lo que necesitas anulando el object.__deepcopy__
.
Creo que una forma pitónica de hacer esto es usar su dictado personalizado extendido desde el __deepcopy__
incorporado e implementar su __deepcopy__
.
Realmente depende de tus necesidades. deepcopy
fue construida con la intención de hacer lo (más) correcto. Mantiene las referencias compartidas, no retrocede en infinitas estructuras recursivas, etc. Puede hacerlo manteniendo un diccionario de memo
en el que todas las "cosas" encontradas se insertan por referencia. Eso es lo que lo hace bastante lento para copias de datos puros. Sin embargo, casi siempre diría que la deepcopy
es la forma más pirónica de copiar datos, incluso si otros enfoques pudieran ser más rápidos.
Si tiene datos puros y una cantidad limitada de tipos dentro de ellos, podría crear su propia deepcopy
(compilación aproximadamente después de la implementación de la deepcopy
en deepcopy
en 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)
Esto solo funciona correctamente para todos los tipos inmutables de no contenedores y las instancias de list
y dict
. Usted podría agregar más despachadores si los necesita.
# 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)
También veamos cómo se realiza al copiar un diccionario grande que contiene cadenas y enteros:
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)