2011-01-10 15 views
13

Mam system, który zwykle przechowuje typy kiszonych klas.Podlewanie podklasy o parametrach sparametryzowanych dynamicznie

Chcę móc zapisywać dynamicznie sparametryzowane klasy w ten sam sposób, ale nie mogę, ponieważ otrzymuję błąd PicklingError podczas próby pobrania klasy, która nie została znaleziona globalnie (nie zdefiniowano w prostym kodzie).

Mój problem można modelować jak w poniższym przykładzie kodu:

class Base(object): 
def m(self): 
    return self.__class__.PARAM 

def make_parameterized(param_value): 
class AutoSubClass(Base): 
    PARAM = param_value 
return AutoSubClass 

cls = make_parameterized(input("param value?")) 

Kiedy próbuję marynowane klasę, pojawia się następujący błąd:

# pickle.PicklingError: Can't pickle <class '__main__.AutoSubClass'>: it's not found as __main__.AutoSubClass 
import pickle 
print pickle.dumps(cls) 

szukam jakiegoś sposobu zadeklaruj Base jako ParameterizableBaseClass, który powinien zdefiniować potrzebne parametry (PARAM w powyższym przykładzie). Podklasa z parametrami dynamicznymi (cls) powinna być następnie dostępna do wybrania przez zapisanie typu "ParameterizableBaseClass" i różnych wartości parametrów (dynamiczny param_value powyżej).

Jestem pewien, że w wielu przypadkach można tego całkowicie uniknąć ... I mogę tego uniknąć również w moim kodzie, jeśli naprawdę (naprawdę) muszę. W pewnym momencie grałem z __metaclass__, copyreg, a nawet __builtin__.issubclass (nie pytam), ale nie mogłem go złamać.

Czuję, że nie byłbym wierny duchowi Pythona, gdybym nie pytał: w jaki sposób można to osiągnąć w stosunkowo czysty sposób?

+1

W stosunkowo czysty sposób? Nie sądzę, że jest to możliwe dla jakiejkolwiek sensownej definicji "czystej". Dlaczego próbujesz pobierać obiekty, których klasy są generowane w czasie wykonywania? Jaki jest faktyczny przypadek użycia? Jak zamierzasz rozpruć coś, czego klasa nie istnieje? –

+1

Ja też nie rozumiem tego przypadku. Dlaczego nie można po prostu zwrócić instancji klasy z tym parametrem jako atrybutu po inicjalizacji. Kolejny komentarz, należy także unikać magii Pythona, tj. 'Self .__ class __. PARAM'. Myślę, że to komplikujesz. – milkypostman

+2

Może się zdarzyć, że jest to nadmierna komplikacja (w takim przypadku pytanie może nadal trwać, nawet bez względu na moją sprawę). Mój przykład wygląda na naprawdę niemądry - to tylko przykład. Mój prawdziwy system klasyfikuje typy klas wraz z parametrami tworzenia i przekazuje je między procesami w scenariuszu przetwarzania równoległego. Oprócz ~ 20 różnych używanych klas, z konkretnym kodem w każdym z nich, mam kilka klas "po prostu wybierz twój parametr". Chcę móc je podklasować dynamicznie, bez konieczności zmiany (i potencjalnie przeładowania) kodu i umożliwienia ich wybrania. HTH. – Yonatan

Odpowiedz

4

Tak, to jest możliwe -

Ilekroć chcesz zwyczaj zalewy i Unpickle zachowań dla swoich obiektów, po prostu trzeba ustawić „__getstate__” i „__setstate__” metod na samej klasie.

W tym przypadku jest nieco trudniej: Potrzebna jest, jak zauważyliście, istnienie klasy w globalnym obszarze nazw, która jest klasą aktualnie trawionego obiektu: musi to być ta sama klasa, z Takie samo imię. Ok - umowa jest taka, że ​​klasa gthis istniejąca w przestrzeni globalnej może zostać utworzona w czasie pikle.

W czasie Unpickle klasa, o tej samej nazwie, musi istnieć - ale nie musi to być ten sam obiekt - po prostu zachowuj się tak jak ona - a jako __setstate__ jest wywoływana w procesie Unpickling, może odtworzyć sparametryzować klasę obiektu orignal i ustawić własną klasę na taką, ustawiając atrybut atrybutu obiektu na __class__.

Ustawienie atrybutu obiektu może być niepożądane, ale OO działa w Pythonie i jest oficjalnie udokumentowane, działa nawet w różnych implementacjach. (Testowałem ten fragment zarówno w Pythonie 2.6 i pypy)

class Base(object): 
    def m(self): 
     return self.__class__.PARAM 
    def __getstate__(self): 
     global AutoSub 
     AutoSub = self.__class__ 
     return (self.__dict__,self.__class__.PARAM) 
    def __setstate__(self, state): 
     self.__class__ = make_parameterized(state[1]) 
     self.__dict__.update(state[0]) 

def make_parameterized(param_value): 
    class AutoSub(Base): 
     PARAM = param_value 
    return AutoSub 

class AutoSub(Base): 
    pass 


if __name__ == "__main__": 

    from pickle import dumps, loads 

    a = make_parameterized("a")() 
    b = make_parameterized("b")() 

    print a.PARAM, b.PARAM, type(a) is type(b) 
    a_p = dumps(a) 
    b_p = dumps(b) 

    del a, b 
    a = loads(a_p) 
    b = loads(b_p) 

    print a.PARAM, b.PARAM, type(a) is type(b) 
+0

Myślę, że mam tylko siebie obwiniać, że sztuczka, którą próbowałem ciągnąć, jest w najlepszym razie hackowata ... Twoja metoda wydaje się być sposobem na osiągnięcie tego, co zostało omówione. – Yonatan

+0

W tym przypadku otrzymujesz globalną definicję klasy, jeśli tylko jest to fikcyjna: 'class AutoSub (Base): pass'. Ale miły. – ThomasH

1

Klasy, które nie zostały utworzone na najwyższym poziomie modułu, nie można oszklić, as shown in the Python documentation.

Ponadto, nawet dla instancji klasy modułu najwyższego poziomu atrybuty klasy nie są przechowywane. Tak więc w twoim przykładzie PARAM i tak nie będzie przechowywany. (Wyjaśnienie w sekcji dokumentacji Pythona również powyżej)

+0

Dzięki za odpowiedź! Prawdą jest, że zwykłe używanie 'pickle.dump' nie zgadza się na wybór moich typów klas. Poszukuję mechanizmu (takiego jak '__reduce__',' __setstate__' lub podobnego w konstrukcji bazowej lub konstrukcji meta-klasy), który pozwoli mi zalać krotkę _opisującą_ moją klasę i zrekonstruować później moją klasę. – Yonatan

2

Sądzę, że jest już za późno, ale pikle to moduł, którego wolałbym uniknąć w przypadku wszystkiego złożonego, ponieważ ma problemy takie jak ten i wiele innych.

Anyways, ponieważ zalewa chce klasę w globalnym może mieć go:

import cPickle 

class Base(object): 
    def m(self): 
     return self.__class__.PARAM 

    @classmethod 
    def make_parameterized(cls,param): 
     clsname = "AutoSubClass.%s" % param 
     # create a class, assign it as a global under the same name 
     typ = globals()[clsname] = type(clsname, (cls,), dict(PARAM=param)) 
     return typ 

cls = Base.make_parameterized('asd') 

import pickle 
s = pickle.dumps(cls) 

cls = pickle.loads(s) 
print cls, cls.PARAM 
# <class '__main__.AutoSubClass.asd'> asd 

Ale tak, jesteś prawdopodobnie overcomplicating rzeczy.

+0

Myśląc o wystąpieniach takich klas, które są marnotrawione w innych modułach, prawdopodobnie zadziała, gdy spróbujesz wyłapać wystąpienia "cls", ponieważ możesz być pewien, że fabryka klas została uruchomiona. Ale nie jestem tak pewny, aby rozpakować takie instancje (w innych modułach), ponieważ w takim przypadku zadanie 'globals() [clsname]' mogło nie zostać uruchomione (pomyśl o następnym uruchomieniu programu, gdy chce przeczytać powleczone instancje z powrotem). – ThomasH

8

wiem, że to jest bardzo stare pytanie, ale myślę, że warto dzielić lepszym środkiem trawienie parametryzowane zajęcia niż ten, który jest aktualnie przyjęte rozwiązanie (dzięki czemu sparametryzowana klasa jest globalna).

Używając metody __reduce__, możemy dostarczyć wywołanie, które zwróci niezainicjowaną instancję naszej pożądanej klasy.

class Base(object): 
    def m(self): 
     return self.__class__.PARAM 

    def __reduce__(self): 
     return (_InitializeParameterized(), (self.PARAM,), self.__dict__) 


def make_parameterized(param_value): 
    class AutoSub(Base): 
     PARAM = param_value 
    return AutoSub 


class _InitializeParameterized(object): 
    """ 
    When called with the param value as the only argument, returns an 
    un-initialized instance of the parameterized class. Subsequent __setstate__ 
    will be called by pickle. 
    """ 
    def __call__(self, param_value): 
     # make a simple object which has no complex __init__ (this one will do) 
     obj = _InitializeParameterized() 
     obj.__class__ = make_parameterized(param_value) 
     return obj 

if __name__ == "__main__": 

    from pickle import dumps, loads 

    a = make_parameterized("a")() 
    b = make_parameterized("b")() 

    print a.PARAM, b.PARAM, type(a) is type(b) 
    a_p = dumps(a) 
    b_p = dumps(b) 

    del a, b 
    a = loads(a_p) 
    b = loads(b_p) 

    print a.PARAM, b.PARAM, type(a) is type(b) 

Warto przeczytaniu __reduce__ docs kilka razy, aby zobaczyć dokładnie to, co się tu dzieje.

Mam nadzieję, że ktoś uzna to za przydatne.

Powiązane problemy