Python多进程中类property跨进程回传丢失的解决
问题本质原因
- 多进程Queue传递对象的过程,是对实例做全量序列化拷贝,仅会携带实例自身存在
__dict__里的实例属性,不会携带运行时动态修改的类定义信息。而property本质是绑定在类上的描述符对象,根本不会存储在实例的属性字典里,序列化时自然不会跟着实例一起跨进程传输。 - 你在worker进程里执行
setattr(b.__class__, 'prop', property(...)),修改的是子进程独立内存空间里的B类副本,这个改动完全不会同步到主进程的B类定义上。实例传到主进程后,绑定的还是主进程那份没有加prop属性的原生B类,自然找不到对应属性。 - Windows默认使用spawn模式启动子进程,子进程是从主进程重新加载模块生成的独立运行实例,本身就不共享主进程的类内存空间,子进程里动态修改的类属性根本跨不过进程边界。
可行的实现方案
方案1:将property直接定义在主进程的类声明中
从源头避免运行时动态修改类属性的操作,类定义阶段就把property写好,保证主进程和子进程里的类定义完全一致,实例跨进程传递后属性自然可访问:
from multiprocessing import Process, Queue class B: def __init__(self): self.attr = 'hello' # 类定义阶段直接声明property,不依赖运行时动态修改 @property def prop(self): return self.attr def worker(queue): b = B() assert b.prop queue.put(b) if __name__ == '__main__': queue = Queue() p = Process(target=worker, args=(queue,)) p.start() res = queue.get() p.join() assert hasattr(res, 'prop') print(res.prop) # 正常输出hello
方案2:将动态属性逻辑绑定到实例自身存储
如果必须在子进程运行时才确定property的实现逻辑,不要把property存在类上,而是把可序列化的计算逻辑存在实例自己的属性字典里,主进程收到实例后可以正常触发计算逻辑:
from multiprocessing import Process, Queue # 顶层定义的可调用对象可以被pickle序列化跨进程传递 def calc_attr(b): return b.attr class B: def __init__(self): self.attr = 'hello' # 实例层存储动态扩展的属性逻辑,这部分内容会跟着实例一起被序列化跨进程传递 self._dynamic_props = {} def __getattribute__(self, name): dynamic_props = object.__getattribute__(self, '_dynamic_props') if name in dynamic_props: # 调用存的可调用对象,传入实例自身完成值的计算 return dynamic_props[name](self) return object.__getattribute__(self, name) def worker(queue): b = B() # 把可序列化的顶层函数存到实例自己的属性字典里,不修改类定义 b._dynamic_props['prop'] = calc_attr assert b.prop queue.put(b) if __name__ == '__main__': queue = Queue() p = Process(target=worker, args=(queue,)) p.start() res = queue.get() p.join() assert hasattr(res, 'prop') print(res.prop) # 正常输出hello
关键避坑提示
- 不要试图依赖跨进程传递动态修改的类属性,Python多进程的序列化机制本身就不会携带类的运行时修改内容,类属于模块级全局对象,不会随着单个实例的传输跨进程同步。
- 如果确实需要主进程和子进程共享同一份修改后的类定义,不要用普通Process+Queue的模式,可以改用带共享内存的进程通信方案;如果没有CPU密集型运算需求,直接用线程代替进程——线程是共享同一份进程内存空间的,类的动态修改会全局可见。
- Windows环境下写多进程代码必须加
if __name__ == '__main__':保护主模块,否则spawn模式会触发无限递归创建子进程的问题。 - 跨进程传递的可调用对象必须是支持pickle序列化的顶层函数,不能使用lambda、闭包等无法被pickle处理的对象,否则会在序列化环节抛出异常。
内容的提问来源于stack exchange,提问作者Kreol
相关产品推荐
相关产品推荐

