web-dev-qa-db-ja.com

Pythonでネストされたクラスをpickle化するにはどうすればよいですか?

ネストされたクラスがあります:

 class WidgetType(object):
 
 class FloatType(object):
 pass 
 
 class TextType(object):
        パス

..そしてこのようなネストされたクラスタイプ(そのインスタンスではない)を参照するオブジェクト

 class ObjectToPickle(object):
 def __init __(self):
 self.type = WidgetType.TextType 

ObjectToPickleクラスのインスタンスをシリアル化しようとすると、次のようになります。

PicklingError:<class'setmanager.app.site.widget_data_types.TextType '>をpickleできません

Pythonでネストされたクラスをpickle化する方法はありますか?

38
prinzdezibel

Pickleモジュールは、モジュールからTextTypeクラスを取得しようとしています。ただし、クラスはネストされているため、機能しません。 jasonjsの提案は機能します。エラーメッセージの原因となるpickle.pyの行は次のとおりです。

_    try:
        __import__(module)
        mod = sys.modules[module]
        klass = getattr(mod, name)
    except (ImportError, KeyError, AttributeError):
        raise PicklingError(
            "Can't pickle %r: it's not found as %s.%s" %
            (obj, module, name))
_

もちろん、klass = getattr(mod, name)はネストされたクラスの場合には機能しません。何が起こっているかを示すために、インスタンスをpickle化する前にこれらの行を追加してみてください。

_import sys
setattr(sys.modules[__name__], 'TextType', WidgetType.TextType)
_

このコードは、TextTypeを属性としてモジュールに追加します。酸洗いはうまくいくはずです。ただし、このハックを使用することはお勧めしません。

29
Nadia Alramli

これがveryの古い質問であることは知っていますが、コードを再構築するための明白でおそらく正しい答え以外に、この質問に対する満足のいく解決策を明示的に見たことがありません。

残念ながら、そのようなことを行うことが常に実用的であるとは限りません。その場合、最後の手段として、別のクラス内で定義されているクラスのインスタンスをpickle化することが可能ですis

python __reduce__関数 のドキュメントはあなたが返すことができると述べています

オブジェクトの初期バージョンを作成するために呼び出される呼び出し可能オブジェクト。タプルの次の要素は、この呼び出し可能オブジェクトの引数を提供します。

したがって、必要なのは、適切なクラスのインスタンスを返すことができるオブジェクトだけです。このクラス必須それ自体が選択可能であり(したがって、__main__レベルで存在する必要があります)、次のように単純にすることができます。

class _NestedClassGetter(object):
    """
    When called with the containing class as the first argument, 
    and the name of the nested class as the second argument,
    returns an instance of the nested class.
    """
    def __call__(self, containing_class, class_name):
        nested_class = getattr(containing_class, class_name)
        # return an instance of a nested_class. Some more intelligence could be
        # applied for class construction if necessary.
        return nested_class()

したがって、残っているのは、FloatTypeの__reduce__メソッドで適切な引数を返すことだけです。

class WidgetType(object):

    class FloatType(object):
        def __reduce__(self):
            # return a class which can return this class when called with the 
            # appropriate Tuple of arguments
            return (_NestedClassGetter(), (WidgetType, self.__class__.__name__, ))

結果はネストされたクラスですが、インスタンスをpickle化できます(__state__情報をダンプ/ロードするにはさらに作業が必要ですが、これは__reduce__ドキュメントに従って比較的簡単です)。

これと同じ手法(コードを少し変更するだけ)は、深くネストされたクラスに適用できます。

完全に機能する例:

import pickle


class ParentClass(object):

    class NestedClass(object):
        def __init__(self, var1):
            self.var1 = var1

        def __reduce__(self):
            state = self.__dict__.copy()
            return (_NestedClassGetter(), 
                    (ParentClass, self.__class__.__name__, ), 
                    state,
                    )


class _NestedClassGetter(object):
    """
    When called with the containing class as the first argument, 
    and the name of the nested class as the second argument,
    returns an instance of the nested class.
    """
    def __call__(self, containing_class, class_name):
        nested_class = getattr(containing_class, class_name)

        # make an instance of a simple object (this one will do), for which we can change the
        # __class__ later on.
        nested_instance = _NestedClassGetter()

        # set the class of the instance, the __init__ will never be called on the class
        # but the original state will be set later on by pickle.
        nested_instance.__class__ = nested_class
        return nested_instance



if __name__ == '__main__':

    orig = ParentClass.NestedClass(var1=['hello', 'world'])

    pickle.dump(orig, open('simple.pickle', 'w'))

    pickled = pickle.load(open('simple.pickle', 'r'))

    print type(pickled)
    print pickled.var1

これに関する私の最後の注意は、他の答えが言ったことを覚えておくことです:

そうする立場にある場合は、そもそもネストされたクラスを回避するためにコードをリファクタリングすることを検討してください。

28
pelson

Sage( www.sagemath.org )には、この酸洗いの問題の多くの事例があります。それを体系的に解決することにした方法は、ハックを実装して非表示にすることを目的とする特定のメタクラス内にouterクラスを配置することです。複数のレベルのネストがある場合、これはネストされたクラスを介して自動的に伝播することに注意してください。

5
Hivert

dillの代わりにpickleを使用すると、機能します。

>>> import dill
>>> 
>>> class WidgetType(object):
...   class FloatType(object):
...     pass
...   class TextType(object):
...     pass
... 
>>> class ObjectToPickle(object):
...   def __init__(self):
...     self.type = WidgetType.TextType
... 
>>> x = ObjectToPickle()
>>> 
>>> _x = dill.dumps(x)
>>> x_ = dill.loads(_x)
>>> x_
<__main__.ObjectToPickle object at 0x10b20a250>
>>> x_.type
<class '__main__.TextType'>

ここでディルを取得します: https://github.com/uqfoundation/dill

5
Mike McKerns

Pickleは、モジュールスコープ(トップレベル)で定義されたクラスでのみ機能します。この場合、コードでTextTypeFloatTypeを参照しない理由があると仮定すると、モジュールスコープでネストされたクラスを定義し、それらをWidgetTypeのプロパティとして設定できるようです。または、それらが含まれているモジュールをインポートして、widget_type.TextTypeおよびwidget_type.FloatTypeを使用します。

2
Jason S

ナディアの答えはかなり完全です-それは実際にはあなたがやりたいことではありません。ネストされたクラスの代わりにWidgetTypesで継承を使用できないのですか?

ネストされたクラスを使用する唯一の理由は、密接に連携するクラスをカプセル化することです。特定の例は、私には直接の継承候補のように見えます。WidgetTypeクラスを一緒にネストするメリットはありません。それらをモジュールに入れ、代わりにベースWidgetTypeから継承します。

1
kibitzer