Como descompactar um objeto cuja classe exista em um namespace diferente (python)?

11

Se eu tiver um script que defina uma classe:

script = """

class myClass:
    def __init__(self):
        self.name = 'apple'
        self.color = 'green'

"""

e, em seguida, exec este script em seu próprio namespace dict:

NS = {}
exec script in NS

e, em seguida, crie uma instância da classe e salve-a:

a = NS['myClass']()

import pickle

save = pickle.dumps(a)

Agora, se eu tentar descompactá-lo:

load = pickle.loads(save)

Eu recebo o erro

AttributeError: 'module' object has no attribute 'myClass'

Eu entendo que isso não funciona porque python não sabe onde encontrar myClass para reconstruir o objeto. Mas o myClass existe no dict do NS. Existe uma maneira de dizer ao pickle onde encontrar a classe para o objeto que está carregando?

    
por user1804375 09.01.2013 в 16:36
fonte

2 respostas

4

Eu descobri uma solução isso. Parece que o problema é executar o código em um dict impede o python de descobrir onde a classe está definida. A solução é criar um módulo vazio, executar o código no módulo e, em seguida, incluir o módulo em sys.modules para que o python o conheça.

script = """
class myClass:
    def __init__(self):
        self.name = 'apple'
        self.color = 'green'
"""

import imp, sys

moduleName = 'custom'

module = imp.new_module(moduleName)

exec script in module.__dict__

sys.modules[moduleName] = module

Agora é possível criar e descompactar uma instância da classe:

import pickle
a = module.myClass()
s = pickle.dumps(a)
b = pickle.loads(s)
    
por user1804375 09.01.2013 / 21:12
fonte
4

Você pode realmente ir um passo além e fazer com que o objeto se reconstitua em qualquer tipo que você queira.

import pickle
import copy_reg

class myClass(object):
    def __init__(self):
        self.apple = 'banana'

class otherclass(object):
    def __init__(self):
        self.apple = 'existential woe'

def pickle_an_object(o):
    print "pickling %s" % str(o)
    return otherclass, (o.apple,)

copy_reg.pickle(myClass, pickle_an_object)

foo = myClass()

s = pickle.dumps(foo)

del myClass
del otherclass

class otherclass(object):
    def __init__(self, appletype):
        self.apple = 'not %s' % appletype

o2 = pickle.loads(s)

print o2.apple

A idéia básica é que você coloca sua classe em uma espécie de "cavalo de tróia", onde sua reconstrução provoca uma instanciação de uma classe diferente do que era originalmente.

não importa o que o otherclass no lado de decapagem contém. Tudo o que importa é que ele exista no mesmo caminho de módulo da classe "destino" - pickle está apenas colocando uma representação de string do nome do módulo no fluxo serializado.

Então, para detalhar o que está acontecendo no código acima em detalhes:

  • Registramos um selecionador personalizado para myClass . Isso pode ser feito via copy_reg ou a função __reduce_ex__ .
  • Nosso apanhador de clientes personalizado diz "pickle this como uma instância de otherclass " (que é um fictício. Você não precisa do conteúdo "real" de otherclass no lado da decapagem, porque tudo o que entra no picles é o nome do módulo / classe).
  • Nós coletamos o objeto e "enviamos através do fio", para onde a versão real de otherclass existe.
  • No lado remoto, otherclass é instanciado com os dados da tupla retornada pela função de remoção personalizada.

O Python pode ser muito poderoso!

    
por Borealid 09.01.2013 / 17:17
fonte