【问题标题】:Subclass object of pathlib.Path gets custom attributes lost after pickle.loadpathlib.Path 的子类对象在 pickle.load 后获取自定义属性丢失
【发布时间】:2021-03-18 06:43:33
【问题描述】:
from pathlib import Path
import pickle
class P(type(Path())):
    def __init__(self, *args):
        super().__init__()
        self.a = ''
p = P()
p.a = 'x'
with open('xx', 'wb') as wf:
    pickle.dump(p, wf)
p1 = pickle.load(open('xx', 'rb'))
print(p1.a)            # here p1.a is ''

我正在创建pathlib.Path 的子类,并希望为其添加一些自定义属性。 问题是自定义属性在被pickle重新加载后会丢失。 如何解决这个问题。

我尝试过的其他解决方案:

  • 使用__slots__,同样的问题。
  • 使用组合而不是继承,然后通过实现__getattr__ 调度类似路径的方法。但是,在这种情况下,self.path 未在 pickle.load 中初始化,从而导致__getattr__ 的无休止调用。
class File():
    def __init__(self, *args):
        self.path = Path(*args)
    def __getattr__(self, item):
        return getattr(self.path, item)
p = File('aaa')
p.exists()  # no error
with open('xx', 'wb') as wf:
    pickle.dump(p, wf)
p1 = pickle.load(open('xx', 'rb')) 
# RecursionError: maximum recursion depth exceeded. 
# This is due to call of self.path, in that moment, path is not in self.__dict__

【问题讨论】:

  • WHOOPs nvm,我搞错了

标签: python python-3.x inheritance serialization pickle


【解决方案1】:

一种方法是使用copyreg 模块将pickle 支持函数与您的类的实例相关联,如下所示。请注意,我还必须修改 P 类处理参数的方式——它不再忽略它们。

import copyreg
from pathlib import Path
import pickle


class P(type(Path())):
    def __init__(self, *args):
        super().__init__()
        self.a = args[0] if args else ''


def pickle_P(p):
    print("pickling a P instance...")
    return P, (p.a,)

copyreg.pickle(P, pickle_P)

p = P()
p.a = 'x'
q = P('y')

with open('xx', 'wb') as outp:
    pickle.dump(p, outp)
    pickle.dump(q, outp)

with open('xx', 'rb') as inp:
    p1 = pickle.load(inp)
    q1 = pickle.load(inp)

print('p1.a = {!r}'.format(p1.a))
print('q1.a = {!r}'.format(q1.a))

输出:

pickling a P instance...
pickling a P instance...
p1.a = 'x'
q1.a = 'y'

【讨论】:

  • 作文一呢?如果该错误也可以通过这种方式解决。我查看了copyreg的文档,但是没有关于如何编写支持函数的描述。
  • 正如我所说,这是一种方法。一方面,辅助函数可以以不同的方式实现,另外还有其他方法可以做这样的事情,根本不涉及使用copyreg 模块。我不完全确定你所说的作曲是什么意思。如果这是一个重要方面,请在您的问题中提及并添加(更好的)描述。
  • 谢谢,我指的是第二个代码示例中的情况。
  • 我认为组合方法的问题在于知道(或以某种方式确定)已将哪些属性添加到实例中,以便可以保存它们并稍后由pickle 恢复。我并不是说我认为这是不可能的,但我仍然不清楚你正在尝试完成什么,更不用说为什么必须以某种方式完成。
  • 我正在尝试创建一个与pathlib.Path具有相同接口的类。
【解决方案2】:

关于继承一的问题。

作为@martineau 回答的注释的另一个解决方案。

如果我是正确的,问题是由pathlib.PosixPath 中的__reduce__ 方法引起的。似乎泡菜行为将由这种方法确定。并且@martineau 的使用copyreg.pickle(P, pickle_P) 的解决方案也与此方法有关:pickle_P__reduce__ 具有相同的返回模式。

这是关于 __reduce__ 的返回值的文档:

返回元组时,它的长度必须在 2 到 6 个之间。 可选项目可以省略,也可以提供 None 作为它们的 价值。每个项目的语义是按顺序排列的:

  • 将被调用以创建对象的初始版本的可调用对象。

  • 可调用对象的参数元组。如果可调用对象不接受任何参数,则必须给出一个空元组。

  • (可选)对象的状态,如前所述,它将传递给对象的 __setstate__() 方法。如果对象没有这样的方法,那么值必须是一个字典,它将被添加 到对象的__dict__ 属性。

  • ...

第二项解释了@martineau 的解决方案是如何工作的:第二个返回值将被传递到__init__

这是PosixPath.__reduce__的源代码

    def __reduce__(self):
        # Using the parts tuple helps share interned path parts
        # when pickling related paths.
        # self._parts is arguments passed to Path
        return (self.__class__, tuple(self._parts))

根据第三个返回值的描述,解决方法是:

from pathlib import Path
import pickle
class P(type(Path())):
    def __init__(self, *args):
        super().__init__()
        self.a = ''
    def __reduce__(self):
        return self.__class__, tuple(self._parts), self.__dict__

p = P()
p.a = 'x'
with open('xx', 'wb') as wf:
    pickle.dump(p, wf)
p1 = pickle.load(open('xx', 'rb'))
print(p1.a)            # here p1.a is 'x'

本方案的不足之处:

  • P 的实例将包含 __dict__ 属性(Path 使用 __slots__)。
  • 名为_hash 的属性将被忽略。

关于作文一的问题

pickle 的文档中的注释可以解释错误的原因。

注意在unpickling时,一些方法如__getattr__(), 可以在实例上调用__getattribute__()__setattr__()。如果这些方法依赖于某些内部不变量为真,则 type 应该实现__new__() 来建立这样的不变量,如 __init__() 在 unpickling 实例时不会被调用。

为确保在调用__getattr__ 时退出path 属性,一种解决方案是将属性分配移到__new__ 方法中(在__init__ 之前)。

class File():
    def __new__(cls, *args):
        obj = super().__new__(cls)
        obj.path = Path(*args)
        return obj
    def __getattr__(self, item):
        return getattr(self.path, item)
p = File('aaa')
p.exists()  # no error
with open('xx', 'wb') as wf:
    pickle.dump(p, wf)
p1 = pickle.load(open('xx', 'rb')) # no error

【讨论】:

    猜你喜欢
    • 2021-06-12
    • 2018-04-20
    • 2012-06-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多