Как я могу выбрать вложенный класс в 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, и он смог рассолить и распаковать вложенный класс.