Как я могу выбрать вложенный класс в Python? - PullRequest
37 голосов
/ 22 декабря 2009

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

class WidgetType(object):

    class FloatType(object):
        pass

    class TextType(object):
        pass

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

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

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

PicklingError: Can't pickle <класс 'Setmanager.app.site.widget_data_types.TextType'>

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

Ответы [ 6 ]

29 голосов
/ 22 декабря 2009

Модуль 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 в качестве атрибута модуля. Травление должно работать нормально. Я не советую вам использовать этот хак.

28 голосов
/ 15 июля 2012

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

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

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

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

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

5 голосов
/ 25 января 2014

Если вы используете 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 голосов
/ 06 марта 2010

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

2 голосов
/ 22 декабря 2009

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

1 голос
/ 23 декабря 2009

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

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

...