variable library example dict deepcopy python pickle python-2.x deep-copy

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í:

  1. Si una clase define __[gs]etstate__ , se les llama a una deepcopy en deepcopy de sus instancias. Esto me sorprendió al principio, porque pensé que son específicos para pickle , 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__ ?)
  2. Una implementación alternativa ingenua de deepcopy sería pickle.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.