2012-08-22 13 views
7

Quiero implementar el soporte de decapado para los objetos que pertenecen a mi biblioteca de extensiones. Hay una instancia global del servicio de clase inicializado al inicio. Todos estos objetos se producen como resultado de algunas invocaciones al método de Servicio y pertenecen esencialmente a él. El servicio sabe cómo serializarlos en búferes binarios y cómo deserializar los búferes en objetos.Python's __reduce __/copy_reg desapilador semántico y statefull

Parecía que Pythons __ reduction__ debería servir para mi propósito: implementar el soporte de decapado. Empecé a implementar uno y me di cuenta de que hay un problema con el unpickler (se espera que el primer elemento de una tupla sea devuelto por __ reduction__). Esta función no activa necesita una instancia de un Servicio para poder convertir el búfer de entrada en un Objeto. Aquí hay un poco de pseudo código para ilustrar el problema:

class Service(object): 
    ... 
    def pickleObject(self,obj): 
     # do serialization here and return buffer 
     ... 

    def unpickleObject(self,buffer): 
     # do deserialization here and return new Object 
     ... 

class Object(object): 
    ... 
    def __reduce__(self): 
     return self.service().unpickleObject, (self.service().pickleObject(self),) 

Tenga en cuenta el primer elemento en una tupla. A Python Pickler no le gusta: dice que es un método de instancia y no se puede escabechar. Obviamente Pickler está tratando de almacenar la rutina en la salida y quiere la instancia de servicio junto con el nombre de la función, pero esto no es lo que quiero que suceda. No quiero (y realmente no puedo: el servicio no es seleccionable) almacenar el servicio junto con todos los objetos. Quiero que se cree una instancia de servicio antes de que se invoque pickle.load y, de alguna manera, esa instancia se use durante la eliminación.

Aquí donde vine por el módulo copy_reg. Nuevamente apareció porque debería resolver mis problemas. Este módulo permite registrar las rutinas pickler y unpickler por tipo dinámicamente y se supone que se utilizarán más adelante para los objetos de este tipo. Por lo que añade este registro para la construcción de servicios:

class Service(object): 
    ... 
    def __init__(self): 
     ... 
     import copy_reg 
     copy_reg(mymodule.Object, self.pickleObject, self.unpickleObject) 

self.unpickleObject es ahora un servicio de toma de método vinculado como primer parámetro y el tampón como el segundo. self.pickleObject también es un método consolidado que toma el servicio y el objeto a pickle. copy_reg requiere que la rutina pickleObject siga la semántica del reductor y devuelva una tupla similar a la anterior. Y aquí el problema surgió de nuevo: ¿qué debería devolver como primer elemento de la tupla?

class Service(object): 
    ... 
    def pickleObject(self,obj): 
     ... 
     return self.unpickleObject, (self.serialize(obj),) 

En esta forma pickle se queja nuevamente de que no puede saltear el método de instancia. Intenté Ninguno, tampoco me gusta. Pongo allí alguna función ficticia. Esto funciona, lo que significa que la fase de serialización pasó bien, pero durante el desatornillamiento llama a esta función ficticia en lugar de destripador, me registré para el tipo mymodule.Objeto en el constructor del Servicio.

Así que ahora estoy en la pérdida. Perdón por una explicación larga: no sabía cómo hacer esta pregunta en pocas líneas. Puedo resumir mis preguntas de esta manera:

  1. ¿Por qué copy_reg semántica requiere que regrese la rutina deshecha de pickleObject, si esperaba registrar una independientemente?
  2. ¿Hay alguna razón para preferir la interfaz copy_reg.constructor para registrar la rutina destrabajador?
  3. ¿Cómo hago pickle para usar el descolector que registré en lugar de uno dentro de la secuencia?
  4. ¿Qué debo devolver como primer elemento en una tupla como valor de resultado de pickleObject? ¿Hay un valor "correcto"?
  5. ¿Me enfoco en todo esto correctamente? ¿Hay alguna solución diferente/más simple?

Gracias por su tiempo.

Gennadiy

Respuesta

3

En primer lugar, el módulo copy_reg es poco probable que ayudará mucho aquí: es ante todo una manera de añadir __reduce__ características iguales a las clases que no tienen ese método en lugar de ofrecer ninguna habilidad especial (por ejemplo, si desea extraer objetos de una biblioteca que no lo admite de forma nativa).

El llamable devuelto por __reduce__ debe ser localizable en el entorno donde el objeto se va a desenfundar, por lo que un método de instancia no es realmente apropiado. Como se mencionó en la Pickle documentation:

En el entorno desestibado este objeto debe ser o bien una clase, un exigible registrado como un “constructor seguro” (ver abajo), o debe tener un atributo __safe_for_unpickling__ con una verdadera valor.

Así que si ha definido una función (no el método) de la siguiente manera:

def _unpickle_service_object(buffer): 
    # Grab the global service object, however that is accomplished 
    service = get_global_service_object() 
    return service.unpickleObject(buffer) 

_unpickle_service_object.__safe_for_unpickling__ = True 

Ahora puede utilizar esta función _unpickle_service_object en el valor de retorno de sus __reduce__ métodos para que sus objetos vinculados al nuevo entorno de Objeto global Service cuando no está desplazado.

+0

No respondió la pregunta anterior Q1. –

+0

Terminé haciendo algo similar. Aunque llamé a la instancia del servicio global, esto no es exactamente correcto. De hecho, solo hay una instancia utilizada en la aplicación de producción, pero nada dice que sea global. Y, de hecho, en mis pruebas unitarias sigo creando una nueva y otra vez. Implementé un módulo pickle.py en mi paquete con el registro de def de rutina (svc) y el servicio variable a nivel de módulo global que se está configurando en el registro. También esta rutina registra las funciones de decapado/descortezado usando copy_reg. Esta rutina se invoca desde el constructor del Servicio. Sin embargo, hay un par de problemas: –

+0

1. Esto solo funciona para una única instancia de servicio. En otras palabras, no podemos tener 2 servicios diferentes simultáneamente y ambos tienen objetos elegibles 2. Esta referencia global es fuente de posibles pérdidas de memoria. ¿Cómo y cuándo debo limpiarlo? –

Cuestiones relacionadas