python - Cant Pickle 记忆类实例
问题描述
这是我正在使用的代码
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 的调用。
使用 func==1.10 运行 python 3.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
,但我确信存在大量示例。
但是您可以通过只记住构造函数而不是记住类来构建享元类:
class mystery:
@funcy.memoize
def __new__(cls, num):
return super().__new__(cls)
def __init__(self, num):
self.num = num
…尽管在这种情况下您可能希望将初始化移动到构造函数中。否则,调用mystery(1)
and thenmystery(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
。)
推荐阅读
- mysql - 从 MySQL 5.7.26 迁移到 MySQL 8.0.23 时,MySQL 空间查询不起作用
- javascript - Vanilla JS 秒表 - 向数据库提交时间
- angular - 带标签的离子角路由
- rust - 特征对象的 Rust 向量的类型推断不正确
- java - 获取微调器的项目值而不是微调器字符串值
- python - 如何使用 Linux Red Hat 6.10 版连接到 MS SQL DB,将日期视图中的记录导出到 .csv 文件中并将其放置在 Linux 文件系统中
- php - 输入复选框中的数组
- sql - 如何在 Big Query 中进行分组
- javascript - TypeError 不是 Schema 方法的函数
- celery - 基于密钥执行任务