Pickle и украшенные классы (PicklingError: не тот же объект) - PullRequest
0 голосов
/ 05 сентября 2018

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

import pickle


def decorate(message):
    def call_decorator(func):
        def wrapper(*args, **kwargs):
            print(message)
            return func(*args, **kwargs)

        return wrapper

    return call_decorator


@decorate('hi')
class Foo:
    pass


foo = Foo()
dump = pickle.dumps(foo) # Fails already here.
foo = pickle.loads(dump)

Однако, используя его, pickle вызывает следующее исключение:

_pickle.PicklingError: Can't pickle <class '__main__.Foo'>: it's not the same object as __main__.Foo

Что я могу сделать, чтобы это исправить?

1 Ответ

0 голосов
/ 05 сентября 2018

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).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...