被装饰的类方法pickle时报错对象不匹配如何解决?
报错原因
你的普通装饰器直接返回了新的wrapper函数替换了原类方法,默认不会保留原函数的名称、路径等元信息。pickle序列化函数时会校验函数的模块路径与类绑定的对象是否一致,此时类上的Test.test已经是包装后的新对象,和你尝试序列化的原函数对象标识不匹配,因此抛出该错误。
另外你给出的示例代码还有一个语法问题:类方法test没有声明self参数,运行时会额外报参数不匹配的错误,后面的修复代码已经同步修正。
解决方法
方案1:使用functools.wraps保留原函数元信息(最常用)
仅需给装饰器的包装函数加@wraps(f)注解,即可自动拷贝原函数的所有关键元信息,让pickle能正确识别函数身份,改动最小,适配90%以上的普通装饰器场景:
import pickle import os from functools import wraps def dump(data, fpath, protocol=2): fdir = os.path.dirname(fpath) if not os.path.exists(fdir): os.makedirs(fdir) with open(fpath, 'wb') as f: pickle.dump(data, f) def D(f): @wraps(f) # 新增这一行即可 def wrapper(self): dump(f, '/tmp/a.dump') # 要保留原方法执行的话加一句 f(self) return wrapper class Test(): @D def test(self): # 补全self参数 print('test') aaa = Test() aaa.test()
方案2:自定义序列化规则(适配复杂装饰器)
如果你的装饰器带参数、或者包装逻辑非常复杂,wraps无法完全覆盖需求,可以用copyreg模块给方法类型自定义序列化逻辑,告诉pickle怎么处理被装饰的方法:
import copyreg import types # 注册方法类型的序列化规则 def pickle_method(method): return getattr, (method.__self__.__class__, method.__name__) copyreg.pickle(types.MethodType, pickle_method)
注册后直接运行原有代码即可正常序列化。
方案3:多进程场景专属优化
如果你的实际场景只是用multiprocessing调用被装饰函数,不需要手动做pickle操作,还有两个更简便的方案:
- 更换多进程启动模式为
spawn:启动进程前加一行multiprocessing.set_start_method('spawn'),spawn模式会在子进程重新导入整个模块,不会出现fork模式带来的对象标识不匹配问题 - 换用
pathos.multiprocessing库代替标准库的multiprocessing,该库用dill做序列化,原生支持被装饰的函数、lambda等各种不可序列化对象,不需要修改原有业务代码
内容的提问来源于stack exchange,提问作者amtom
相关产品推荐
相关产品推荐

