Подтвердить что ты не робот

Как я могу выбрать вложенный класс в python?

У меня есть вложенный класс:

class WidgetType(object):

    class FloatType(object):
        pass

    class TextType(object):
        pass

.. и oject, который ссылается на вложенный тип класса (а не на экземпляр), как этот

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

Попытка сериализации экземпляра класса ObjectToPickle приводит к:

PicklingError: Невозможно рассолить < класс 'Setmanager.app.site.widget_data_types.TextType' >

Есть ли способ раскрыть вложенные классы в python?

4b9b3361

Ответ 1

Модуль 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 not found as %s.%s" %
            (obj, module, name))

klass = getattr(mod, name), конечно, не будет работать в случае вложенного класса. Чтобы продемонстрировать, что происходит, попробуйте добавить эти строки перед трассировкой экземпляра:

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

Этот код добавляет TextType как атрибут модуля. Травление должно работать нормально. Я не советую вам использовать этот хак, хотя.

Ответ 2

Я знаю, что это старый вопрос очень, но я никогда явно не видел удовлетворительного решения этого вопроса, кроме очевидного и, скорее всего, правильного ответа на повторную структуру вашего кода.

К сожалению, делать это не всегда удобно, и в этом случае в качестве крайней меры, можно распиливать экземпляры классов, которые определены внутри другого класса.

Документация python для функции __reduce__ указывает, что вы можете вернуть

Вызываемый объект, который будет вызываться для создания начальной версии объекта. Следующий элемент кортежа предоставит аргументы для этого вызываемого.

Следовательно, все, что вам нужно, это объект, который может возвращать экземпляр соответствующего класса. Этот класс должен сам быть picklable (следовательно, должен жить на уровне __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()

Все, что осталось, - это вернуть соответствующие аргументы в метод __reduce__ для FloatType:

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__, ))

Результат представляет собой класс, который является вложенным, но экземпляры могут быть маринованными (необходима дальнейшая работа для сброса/загрузки информации __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

Мое последнее замечание об этом - запомнить, что сказали другие ответы:

Если вы в состоянии сделать это, подумайте о повторной факторизации кода избегайте вложенных классов в первую очередь.

Ответ 3

Если вы используете 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

Ответ 4

В Sage (www.sagemath.org), у нас есть много примеров этой проблемы маринования. То, как мы решили систематически решить это, - это поставить внешний класс внутри определенного метакласса, цель которого - реализовать и скрыть хак. Обратите внимание, что это автоматически распространяется через вложенные классы, если существует несколько уровней вложенности.

Ответ 5

Ответ Надии довольно полный - это практически не то, что вы хотите делать; вы уверены, что не можете использовать наследование в WidgetTypes вместо вложенных классов?

Единственная причина использования вложенных классов - инкапсулировать классы, тесно связанные друг с другом, ваш конкретный пример выглядит как непосредственный кандидат наследования для меня - нет никакой пользы для гнездования классов WidgetType; поместите их в модуль и наследуйте от базы WidgetType.

Ответ 6

Рассеивание работает только с классами, определенными в области модуля (верхний уровень). В этом случае похоже, что вы можете определить вложенные классы в области модуля, а затем установить их как свойства в WidgetType, предполагая, что есть причина не просто ссылаться на TextType и FloatType в свой код. Или импортируйте модуль, в котором они находятся, и используйте widget_type.TextType и widget_type.FloatType.