在Python中反序列化子类不可变类型(Path)

时间:2018-08-22 11:44:37

标签: python serialization

在处理围绕pathlib.Path对象传递的Python(3.6)程序上,我最终需要将一些额外的数据附加到路径上。我将Path子类化,并且可以正常工作。但是,在将Path子类的实例放入multiprocessing queue的put()之后,尝试从队列中获取()对象时遇到了异常(带有难以理解的错误消息...)。这是一个更简单的示例,仅使用pickle进行(反)序列化:

import pickle
from pathlib import Path

class BetterPath(type(Path())):
    def __new__(cls, path_str, extra_data):
        res = super().__new__(cls, path_str)
        res.extra_data = extra_data
        return res

subpath = BetterPath('/home/jdoe/file.txt', 'extra_data')
subpath_bytes = pickle.dumps(subpath)
subpath_obj = pickle.loads(subpath_bytes)

错误是:

Traceback (most recent call last):
File "test.py", line 12, in <module>
    subpath_obj = pickle.loads(subpath_bytes)
TypeError: __new__() takes 3 positional arguments but 5 were given

更改示例代码以继承不同的不可变类型(例如int)会导致稍微不同的错误:

Traceback (most recent call last):
File "test.py", line 12, in <module>
    subpath_obj = pickle.loads(subpath_bytes)
TypeError: __new__() missing 1 required positional argument: 'extra_data'

那么,如何修改我的代码以继承不可变类型(如Path)并成功反序列化实例对象?我尝试覆盖pickle module中提到的某些方法,例如__getnewargs_ex __(),但无效。

1 个答案:

答案 0 :(得分:2)

好吧,受用户ederag的评论激励,我花了更多时间来解决这个问题。解决方案:

import pickle
from pathlib import Path

class BetterPath(type(Path())):
    def __new__(cls, *parts, extra_data=None):
        res = super().__new__(cls, *parts)
        res.extra_data = extra_data
        return res

    def __reduce__(self):
        return type(self), self.parts, self.__dict__, None, None

betterpath = BetterPath('/home/jdoe/file.txt', extra_data='extra_data')
betterpath_bytes = pickle.dumps(betterpath)
betterpath_obj = pickle.loads(betterpath_bytes)

最重要的是,__getnewargs__() / __getnewargs_ex__()根本没有发挥作用。 (也许它们确实适用于使用__init__()的非不可变类?)重要的方法是__reduce__()。 pickle的文档中的描述已足够:

  • 第一个arg是可调用的
  • 第二个arg是将传递给可调用对象的args元组
  • 第三个参数是将传递给__setstate__()的属性的字典

新的parts变量和拆包操作是偶然的-仅在我注意到Path / PosixPath / etc类实际上将路径部分的元组作为args之后,才添加这些变量。 (在单路径str中传递是一种特殊情况。)类似地,将extra_data更改为关键字参数仅是由于parts的更改-extra_data必须是关键字,或作为第一个参数传入,并与parts参数分开处理。