问题是您已经将一个为函数设计的装饰器应用于一个类。结果不是一个类,而是一个封装了对该类的调用的函数。这会导致许多问题(例如,正如 Aran-Fey 在 cmets 中指出的那样,您不能 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,但我相信存在大量示例。
但是你可以通过仅仅记忆构造函数而不是记忆类来构建一个享元类:
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.'
你确实仍然想考虑这个类应该如何使用酸洗。特别是,您是否希望 unpickling 通过缓存?默认情况下,它不会:
>>> 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,但使用记忆构造函数会比完全通用的@memoclass 容易得多。)