Pickle требует, чтобы атрибут __class__
экземпляров мог быть загружен через импорт.
Экземпляры выборки сохраняют только данные экземпляра, а атрибуты __qualname__
и __module__
класса используются для последующего повторного создания экземпляра путем повторного импорта класса и создания нового экземпляра для класса.
Pickle подтверждает, что класс может быть импортирован первым. Пара __module__
и __qualname__
используется, чтобы найти правильный модуль и затем получить доступ к объекту, названному __qualname__
в этом модуле, и если объект __class__
и объект, найденный в модуле, не совпадают, ошибка, которую вы видите, поднята.
Здесь foo.__class__
указывает на объект класса с __qualname__
, установленным на 'Foo'
и __module__
, установленным на '__main__'
, но sys.modules['__main__'].Foo
не указывает на класс, вместо этого он указывает на функцию , wrapper
вложенная функция, которую возвратил ваш декоратор.
Есть два возможных решения:
Не возвращайте функцию, возвращайте исходный класс и, возможно, инструментируйте объект класса, чтобы выполнить работу, которую выполняет обертка. Если вы используете аргументы для конструктора класса, добавьте или оберните метод __new__
или __init__
в декорированном классе.
Примите во внимание, что удаление обычно вызывает __new__
в классе, чтобы создать новый пустой экземпляр, прежде чем восстанавливать состояние экземпляра (если только не было настроено настраивание ).
Сохранить класс в новом месте. Измените атрибуты __qualname__
и, возможно, __module__
класса, чтобы он указывал на местоположение, где исходный класс может быть найден с помощью pickle. При откреплении правильный тип экземпляра будет создан снова, точно так же, как исходный вызов Foo()
.
Другой вариант - настроить травление для производимого класса. Вместо этого вы можете указать классы new __reduce_ex__
и new __reduce__
, которые указывают на функцию-оболочку или пользовательскую функцию сокращения. Это может стать сложным, так как класс уже может иметь настраиваемое засоление, и object.__reduce_ex__
предоставляет значение по умолчанию, и возвращаемое значение может отличаться в зависимости от версии рассола.
Если вы не хотите изменять класс, вы также можете использовать функцию copyreg.pickle()
, чтобы зарегистрировать пользовательский обработчик __reduce__
для класса.
В любом случае, возвращаемое значение редуктора должно по-прежнему избегать ссылки на класс и вместо этого должно ссылаться на новый конструктор по имени, с которым он может быть импортирован. Это может быть проблематично, если вы используете декоратор напрямую с new_name = decorator()(classobj)
. Сам Пикл также не будет иметь дело с такими ситуациями (так как classobj.__name__
не будет соответствовать newname)
.