Оформление функции Pickling Cython приводит к PicklingError - PullRequest
0 голосов
/ 14 декабря 2018

У меня есть следующий код:

def decorator(func):

    @functools.wraps(func)
    def other_func():
        print('other func')

    return other_func

@decorator
def func():
    pass

Если я пытаюсь засолить func, все работает.Однако, если я скомпилирую модуль как расширение Cython, он потерпит неудачу.Вот ошибка:

>>>> pickle.dumps(module.func)

PicklingError: Can't pickle <cyfunction decorator.<locals>.other_func at 0x102a45a58>: attribute lookup other_func on module failed

То же самое происходит, если я использую dill вместо pickle.

Знаете ли вы, как это исправить?

1 Ответ

0 голосов
/ 14 декабря 2018

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

Проблема возникает из-за того, что функции Cython представляются как встроенные функции в земле Python (например, map, all и т. Д.).Эти функции не могут изменять свои атрибуты имени.Тем не менее, Cython пытается сделать свои функции более похожими на чистые функции Python, и поэтому предоставляет возможность изменять некоторые их атрибуты.Однако функции Cython также реализуют __reduce__, который настраивает, как объекты сериализуются с помощью pickle.Похоже, что эта функция действительно думает, что имя объекта функции можно изменить, и поэтому игнорирует эти значения и использует имя внутренней структуры PyCFunction, которая упаковывается ( github blob ).

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

Настройка Pickle

Вы можете использовать функцию persistent_idPickler и Unpickler для переопределения пользовательской реализации, предоставленной Cython.Ниже описано, как настроить травление для определенных типов / объектов.Это делается с помощью чистой функции Python, но вы можете легко изменить ее для работы с функциями Cython.

import pickle
from importlib import import_module
from io import BytesIO

# example using pure python
class NoPickle:
    def __init__(self, name):
        # emulating a function set of attributes needed to pickle
        self.__module__ = __name__
        self.__qualname__ = name

    def __reduce__(self):
        # cannot pickle this object
        raise Exception


my_object = NoPickle('my_object')

# pickle.dumps(obj) # error!

# use persistent_id/load to help dump/load cython functions

class CustomPickler(pickle.Pickler):
    def persistent_id(self, obj):
        if isinstance(obj, NoPickle):
            # replace with NoPickle with type(module.func) to get the correct type
            # alternatively you might want to include a simple cython function 
            # in the same module to make it easier to get the write type.
            return "CythonFunc" , obj.__module__, obj.__qualname__
        else:
            # else return None to pickle the object as normal
            return None

class CustomUnpickler(pickle.Unpickler):
    def persistent_load(self, pid):
        if pid[0] == "CythonFunc":
            _, mod_name, func_name = pid
            return getattr(import_module(mod_name), func_name)
        else:
            raise pickle.UnpicklingError('unsupported pid')

bytes_ = BytesIO()
CustomPickler(bytes_).dump(my_object)

bytes_.seek(0)
obj = CustomUnpickler(bytes_).load()

assert obj is my_object
...