【问题标题】:Pickling Cython decorated function results in PicklingErrorPickling Cython 修饰函数导致 PicklingError
【发布时间】:2018-12-14 09:09:52
【问题描述】:

我有以下代码:

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,也会发生同样的情况。

你知道怎么解决吗?

【问题讨论】:

  • 您是否尝试为def other_func(): 指定return
  • 是的,其实是一样的。为什么你认为它会有所帮助?
  • 我现在无法对此进行测试,但是:这具体是 Cython 问题吗?这段代码可以与纯 Python 一起使用吗? (我不认为应该可以腌制其他函数中定义的函数,但我认为 dill 应该可以工作。不过我可能是错的......)
  • 是的,它像纯 Python 一样工作。如果不添加@functools.wraps(func),就无法腌制,但它可以工作。 dill 没有这个也可以工作,但在 Cython 案例中失败。

标签: python cython decorator pickle dill


【解决方案1】:

我不认为你可以在这里做任何事情。它看起来像 Cython 中的一个可能的错误。但是,Cython 做我不知道的事情可能有充分的理由。

问题出现是因为 Cython 函数在 Python 领域中作为内置函数公开(例如mapall 等)。这些函数的名称属性不能更改。但是,Cython 试图使其函数更像纯 Python 函数,因此提供了修改它们的几个属性的能力。然而,Cython 函数也实现了__reduce__,它自定义了对象如何被pickle 序列化。看起来这个函数确实认为函数对象的名称可以更改,因此忽略这些值并使用正在包装的内部 PyCFunction 结构的名称 (github blob)。

您能做的最好的事情就是提交错误报告。您可能能够创建一个瘦包装器,以便使您的函数能够被序列化,但这会在调用函数时增加开销。

定制泡菜

您可以使用 PicklerUnpicklerpersistent_id 功能覆盖 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

【讨论】:

  • 我明白了,非常感谢!我将填写错误报告。你知道或有关于可能要编写的包装器的参考资料吗?
  • 我添加了一个代码示例来展示如何自定义酸洗。我在这台机器上没有 cython,所以我还没有完全测试它。但我看不出它有任何不工作的理由。
猜你喜欢
  • 2012-04-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-31
  • 1970-01-01
  • 2019-11-07
  • 1970-01-01
相关资源
最近更新 更多