在Python 2.6中,Pickle不考虑我注册的自定义类型缩减函数。

4

给定以下代码:

import pickle
import copy_reg


class A(type):
    pass


class B(object):
    __metaclass__ = A


def _reduce_a(a):
    print('Hey there')
    return a.__name__


copy_reg.pickle(A, _reduce_a)

pickle.dumps(B)

在尝试pickle A实例的B时,Python 2.6中注册的_reduce_a函数从未被调用,但在2.7中它被调用。

这是一个已知的bug吗?


1
这是在Python 2.6还是2.7上?标签显示2.6,您的问题显示2.7。 - Bharat
我已经更新了我的问题。_reduce_a函数在2.7中被调用,但在2.6中没有被调用。 - Simon Charette
2个回答

3
Python 2.7.3新增了一个功能,允许对动态创建的类进行pickle。请参见issue 7689

当前情况下,即使用户通过copy_reg请求不同的pickling机制,通常的pickle机制也无法用于pickle动态创建的类。通过简单地转置两个代码块,附加的补丁使这种自定义成为可能。

copy_reg模块存在于处理对象的自定义pickle方案,这正是需要的。然而,检查元类实例的代码来自#494904,它在查看copy_reg分发表之前执行此操作。这些补丁只需重新排列pickle.py和cPickle.c中的这些测试,以便可以为元类实例注册自定义的pickler。

并从2.7.3 changelog得知:
问题#7689:允许在其元类注册到copy_reg时对动态创建的类进行pickle处理。 Nicolas M. Thiéry和Craig Citro提供了补丁。
Python 2.6唯一的解决方法是将pickle.py模块移植到该版本。您可以尝试将2.7.3 pickle.py模块与2.6捆绑在一起,作为纯Python实现,它应该可以正常工作。
或者,使用monkey-patch方法修改pickle.Pickler.save方法:
from copy_reg import dispatch_table
from types import TypeType, StringType, TupleType
from pickle import Pickler, PicklingError


def pickler_save(self, obj):
    # Check for persistent id (defined by a subclass)
    pid = self.persistent_id(obj)
    if pid:
        self.save_pers(pid)
        return

    # Check the memo
    x = self.memo.get(id(obj))
    if x:
        self.write(self.get(x[0]))
        return

    # Check the type dispatch table
    t = type(obj)
    f = self.dispatch.get(t)
    if f:
        f(self, obj) # Call unbound method with explicit self
        return

    # Check copy_reg.dispatch_table
    reduce = dispatch_table.get(t)
    if reduce:
        rv = reduce(obj)
    else:
        # Check for a class with a custom metaclass; treat as regular class
        try:
            issc = issubclass(t, TypeType)
        except TypeError: # t is not a class (old Boost; see SF #502085)
            issc = 0
        if issc:
            self.save_global(obj)
            return

        # Check for a __reduce_ex__ method, fall back to __reduce__
        reduce = getattr(obj, "__reduce_ex__", None)
        if reduce:
            rv = reduce(self.proto)
        else:
            reduce = getattr(obj, "__reduce__", None)
            if reduce:
                rv = reduce()
            else:
                raise PicklingError("Can't pickle %r object: %r" %
                                    (t.__name__, obj))

    # Check for string returned by reduce(), meaning "save as global"
    if type(rv) is StringType:
        self.save_global(obj, rv)
        return

    # Assert that reduce() returned a tuple
    if type(rv) is not TupleType:
        raise PicklingError("%s must return string or tuple" % reduce)

    # Assert that it returned an appropriately sized tuple
    l = len(rv)
    if not (2 <= l <= 5):
        raise PicklingError("Tuple returned by %s must have "
                            "two to five elements" % reduce)

    # Save the reduce() output and finally memoize the object
    self.save_reduce(obj=obj, *rv)


Pickler.save = pickler_save

使用上述猴子补丁,你的示例能够在Python 2.6.8上运行:

>>> pickle.dumps(B)
Hey there
'c__main__\nB\np0\n.'

0

我认为这是一个2.6版本的bug/限制。如果您调用copy.copy(B),您可以看到函数已经被调用,但是当您调用pickle.dumps(B)时却没有。


你知道暴露pickle/unpickle函数的其他方法吗?我尝试在A上声明__reduce__方法,但它从未被调用。 - Simon Charette

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接