Cant Pickle记忆类实例

时间:2018-08-13 23:13:43

标签: python python-3.x memoization funcy

这是我正在使用的代码

import funcy

@funcy.memoize
class mystery(object):

    def __init__(self, num):
        self.num = num

feat = mystery(1)

with open('num.pickle', 'wb') as f:
    pickle.dump(feat,f)

给我以下错误:

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

我希望1)理解为什么会发生这种情况,2)找到一种解决方案,使我可以对对象进行腌制(不删除备忘录)。理想情况下,该解决方案不会更改对pickle的调用。

使用funcy == 1.10运行python 3.6

2 个答案:

答案 0 :(得分:6)

问题是您已将为函数设计的装饰器应用于类。结果不是一个类,而是一个包装对该类的调用的函数。这会导致许多问题(例如,正如Aran-Fey在评论中指出的那样,您不能isinstance(feat, mystery),因为mystery)。

但是您关心的一个特殊问题是,您不能使无法访问的类的实例腌制。

实际上,这基本上就是错误消息告诉您的内容:

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

您的feat认为它的类型是__main__.mystery,但这根本不是类型,装饰器返回的是包装该类型的函数。


解决此问题的简单方法是找到一个类装饰器,该类装饰器可以满足您的要求。它可能叫做flyweight而不是memoize之类的东西,但是我敢肯定有很多例子。


但是您可以通过记住构造函数而不是记住该类来构建一个flyweight类:

class mystery:
    @funcy.memoize
    def __new__(cls, num):
        return super().__new__(cls)
    def __init__(self, num):
        self.num = num

…,尽管在这种情况下您可能想将初始化移动到构造函数中。否则,先调用mystery(1)然后再调用mystery(1)会返回与以前相同的对象,而且还会使用self.num = 1重新初始化该对象,这充其量是浪费的,最糟糕的是不正确的。所以:

class mystery:
    @funcy.memoize
    def __new__(cls, num):
        self = super().__new__(cls)
        self.num = num
        return self

现在:

>>> feat = mystery(1)
>>> feat
<__main__.mystery at 0x10eeb1278>
>>> mystery(2)
<__main__.mystery at 0x10eeb2c18>
>>> mystery(1)
<__main__.mystery at 0x10eeb1278>

而且,由于feat的类型现在是可以在模块全局名称mystery下访问的类,因此pickle完全没有问题:

>>> pickle.dumps(feat)
b'\x80\x03c__main__\nmystery\nq\x00)\x81q\x01}q\x02X\x03\x00\x00\x00numq\x03K\x01sb.'

要做仍然想考虑此类在腌制中的表现。特别是,您是否要取消筛选才能通过缓存?默认情况下,它不会:

>>> pickle.loads(pickle.dumps(feat)) is feat
False

正在发生的事情是,它使用默认的__reduce_ex__进行酸洗,默认情况下相当于(仅略微简化):

result = object.__new__(__main__.mystery)
result.__dict__.update({'num': 1})

如果您希望它通过缓存,最简单的解决方案是:

class mystery:
    @funcy.memoize
    def __new__(cls, num):
        self = super().__new__(cls)
        self.num = num
        return self
    def __reduce__(self):
        return (type(self), (self.num,))

如果您打算做很多事情,您可能会考虑编写自己的类装饰器:

def memoclass(cls):
    @funcy.memoize
    def __new__(cls, *args, **kwargs):
        return super(cls, cls).__new__(cls)
    cls.__new__ = __new__
    return cls

但这是

  • ...有点丑,
  • …仅适用于不需要将构造函数参数传递给基类的类,
  • …仅适用于没有__init__的类(或至少具有幂等且快速__init__且可以反复调用的类)的类,
  • …没有提供一种简便的方法来钩挂酸洗,并且
  • …没有记录或测试任何这些限制。

所以,我认为您最好还是露骨一点,而只是记住__new__方法,或者编写(或发现)一些奇特的东西以进行内省,从而使这种方式完全通用化一个类。 (或者,或者编写一个仅适用于某些受限类集的代码,例如,一个@memodataclass就像@dataclass一样,但是使用一个记忆构造​​函数比一个完全通用的{{ 1}}。)

答案 1 :(得分:0)

另一种方法是

class _mystery(object):

    def __init__(self, num):
        self.num = num

@funcy.memoize
def mystery(num):
    return _mystery(num)