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

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

Класс WidgetType(объект):

    Класс FloatType(объект):
        проходить

    Класс TextType(объект):
        проходить

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

Класс ObjectToPickle(объект):
     def __init__(self):
         self.type = WidgetType.TextType

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

PicklingError: Невозможно засолить

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

7 ответов

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

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

Документация 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()

Поэтому все, что осталось, - это вернуть соответствующие аргументы в __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

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

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

Модуль pickle пытается получить класс TextType из модуля. Но поскольку класс вложен, он не работает. предложение Джейсона будет работать. Вот строки в 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) конечно, не будет работать в случае вложенного класса. Чтобы продемонстрировать, что происходит, попробуйте добавить эти строки перед выбором экземпляра:

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

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

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

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

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

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

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

Кажется, это отлично работает в более новых версиях Python. Я попробовал это в v3.8, и он смог рассолить и распаковать вложенный класс.

Другие вопросы по тегам