library - python copy dict
RelaciĆ³n entre salmuera y copia profunda. (2)
¿Cuál es exactamente la relación entre pickle
y copy.deepcopy
? ¿Qué mecanismos comparten y cómo?
Está claro que las dos son operaciones estrechamente relacionadas y comparten algunos de los mecanismos / protocolos, pero no puedo comprender los detalles.
Algunas cosas (confusas) que descubrí:
- Si una clase define
__[gs]etstate__
, se les llama a unadeepcopy
endeepcopy
de sus instancias. Esto me sorprendió al principio, porque pensé que son específicos parapickle
, pero luego descubrí que las Clases pueden usar las mismas interfaces para controlar las copias que usan para controlar el pickling . Sin embargo, no hay documentación de cómo se usa__[gs]etstate__
cuando se realiza una copia profunda (cómo se usa el valor devuelto de__getstate__
, ¿qué se pasa a__setstate__
?) - Una implementación alternativa ingenua de
deepcopy
seríapickle.loads(pickle.dumps(obj))
. Sin embargo, esto no puede ser equivalente a deepcopy''ing, porque si una clase define una operación__deepcopy__
, no se invocará usando esta implementación de deepcopy basada en el decapado. (También me topé con una afirmación de que la copia profunda es más general que el encurtido, y hay muchos tipos que se pueden copiar en profundidad, pero no se pueden recoger).
(1) indica un punto en común, mientras que (2) indica una diferencia entre pickle
y deepcopy
.
Además de eso, encontré estas dos afirmaciones contradictorias:
copy_reg : los copy_reg pickle, cPickle y copy usan esas funciones cuando decapan / copian esos objetos
y
El módulo de copia no utiliza el módulo de registro copy_reg
Esto, por un lado, es otra indicación de una relación / coincidencia entre pickle
y deepcopy
, y por otro lado, contribuye a mi confusión ...
[Mi experiencia es con python2.7, pero también apreciaría cualquier sugerencia con respecto a las diferencias en pickle / deepcopy entre python2 y python3]
No debe confundirse con (1) y (2). En general, Python trata de incluir alternativas sensatas para los métodos que faltan. (Por ejemplo, es suficiente definir __getitem__
para tener una clase iterable, pero puede ser más eficiente implementar también __iter__
. Similar para operaciones como __add__
, con la opción __iadd__
etc.)
__deepcopy__
es el método más especializado que deepcopy()
, pero si no existe, recurrir al protocolo pickle es algo sensato. Realmente no llama a dumps()
/ loads()
, porque no se basa en la representación intermedia para ser una cadena, sino que indirectamente hará uso de __getstate__
y __setstate__
(a través de __reduce__
), como ha observado.
Actualmente, la documentación aún establece
... El módulo de copia no utiliza el módulo de registro copy_reg.
pero eso parece ser un error que se ha solucionado mientras tanto (posiblemente, la rama 2.7 no ha recibido suficiente atención aquí).
También tenga en cuenta que esto está bastante integrado en Python (al menos en la actualidad); la propia clase de object
implementa __reduce__
(y su variante _ex versionada), que se refiere a copy_reg.__newobj__
para crear nuevas instancias de la clase derivada de objeto dada.
Ok, tuve que leer el código fuente para este, pero parece que es una respuesta bastante simple. svn.python.org/projects/python/trunk/Lib/copy.py
copy
busca algunos de los tipos incorporados que sabe a qué se parecen los constructores (registrados en el diccionario _copy_dispatch
, y cuando no sabe cómo copiar el tipo básico, importa copy_reg.dispatch_table
... que es el lugar donde pickle
registra los métodos que conoce para producir nuevas copias de objetos. Esencialmente, es un diccionario del tipo de objeto y la "función para producir un nuevo objeto": esta "función para producir un nuevo objeto" es prácticamente lo que se escribe cuando escribe un __reduce__
o __reduce_ex__
para un objeto (y si uno de ellos falta o necesita ayuda, difiere a los __setstate__
, __getstate__
, etc.).
Así que eso es copy
. Básicamente ... (con algunas cláusulas adicionales ...)
def copy(x):
"""Shallow copy operation on arbitrary Python objects.
See the module''s __doc__ string for more info.
"""
cls = type(x)
copier = _copy_dispatch.get(cls)
if copier:
return copier(x)
copier = getattr(cls, "__copy__", None)
if copier:
return copier(x)
reductor = dispatch_table.get(cls)
if reductor:
rv = reductor(x)
else:
reductor = getattr(x, "__reduce_ex__", None)
if reductor:
rv = reductor(2)
else:
reductor = getattr(x, "__reduce__", None)
if reductor:
rv = reductor()
else:
raise Error("un(shallow)copyable object of type %s" % cls)
deepcopy
hace lo mismo que el anterior, pero además inspecciona cada objeto y se asegura de que haya una copia para cada nuevo objeto y no una referencia de puntero. deepcopy
construye su propia tabla _deepcopy_dispatch
(un dict) donde registra funciones que garantizan que los nuevos objetos producidos no tengan referencias de puntero a los originales (posiblemente generados con las funciones __reduce__
registradas en copy_reg.dispatch_table
)
Por lo tanto, escribir un método __reduce__
(o similar) y registrarlo con copy_reg
, debería permitir que copy
y deepcopy
hagan lo suyo.