我正在使用multiprocessing
包中的功能来创建同步的共享对象。我的对象既具有property
属性,又具有上下文管理器(即具有__enter__
和__exit__
方法)。
我遇到了一个好奇,我无法同时使两者同时工作,至少使用我在python 2和3中在网上找到的食谱都是如此。
假设此简单类已注册到管理器中:
class Obj(object):
@property
def a(self): return 1
def __enter__(self): return self
def __exit__(self, *args, **kw): pass
通常都不会起作用,因为我们需要的东西没有暴露:
from multiprocessing.managers import BaseManager, NamespaceProxy
BaseManager.register('Obj', Obj)
m = BaseManager(); m.start();
o = m.Obj()
o.a # AttributeError: 'AutoProxy[Obj]' object has no attribute 'a'
with o: pass # AttributeError: __exit__
我在SO上找到的使用自定义代理而不是AutoProxy
的解决方案适用于该属性,但不适用于上下文管理器(无论__enter__
和__exit__
是否以这种方式公开还是不行):
class MyProxy(NamespaceProxy):
_exposed_ = ['__getattribute__', '__setattr__', '__delattr__', 'a', '__enter__', '__exit__']
BaseManager.register('Obj', Obj, MyProxy)
m = BaseManager(); m.start();
o = m.Obj()
o.a # outputs 1
with o: pass # AttributeError: __exit__
在注册时,我可以使用exposed
关键字来使上下文管理器独自工作:
BaseManager.register('Obj', Obj, exposed=['__enter__', '__exit__'])
m = BaseManager(); m.start();
o = m.Obj()
with o: pass # works
但是,如果我还为该属性添加内容,则会出现最大递归错误:
BaseManager.register('Obj', Obj, exposed=['__enter__', '__exit__', '__getattribute__', '__setattr__', '__delattr__', 'a'])
m = BaseManager(); m.start();
o = m.Obj() # RuntimeError: maximum recursion depth exceeded
如果我遗漏了__getattribute__
和朋友,我会看到a
是一个绑定方法,它尝试调用属性值而不是方法本身,因此也不起作用。
我尝试过以我无法想到的所有方式混合搭配。有没有办法做到这一点,或者这可能是库中的错误?
答案 0 :(得分:1)
事实是,这些管理器的实现方式集中于控制然后以属性的形式控制对共享数据的访问。他们在处理其他Python功能(例如属性或依赖对象状态的“ dunder”方法,例如__enter__
和__exit__
)方面的工作效果不佳。
通过代理对象的子类化,当然可以为每个所需功能找到特定的解决方法,直到使每个功能都可以工作-但这样做的结果永远不会在所有极端情况下都是防弹的,所有Python类功能的价值都大大降低了。
因此,我认为在这种情况下,您要做的最好是创建一个仅包含数据的类!一个只使用普通属性的属性-没有属性,没有描述符,没有属性访问自定义-只是一个普通的数据类,其实例将保存您需要共享的 data 。实际上,您甚至可能不需要这样的类,因为管理器模块提供了Synced字典类型-只需使用
然后创建第二类,在其中构建所需的智能。第二个类将具有getter,setter和属性,并且可以实现上下文协议,并且您喜欢的任何dunder方法和都可以拥有数据类的关联实例。在这种情况下,方法和属性中的所有智能都可以利用数据。实际上,您可能只使用multiprocessing.managers.SyncManager.dict
同步字典来保存数据。
然后,如果您管理此关联的数据类,它将以一种简单明了的方式工作,并且在每个过程中,您将构建包装它的“智能类”。
您的代码段未提供有关如何将objetcs从一个进程传递到另一个进程的示例-我希望您知道通过调用BaseManager.Obj()可以得到新的,本地的类实例-您无论管理者如何,都必须获得一个队列来跨进程共享您的对象。
下面的概念证明展示了我的意思的一个例子。
import time
from multiprocessing import Process, Pool
from multiprocessing.managers import SyncManager
class MySpecialClass:
def __init__(self, data):
self.data = data
@property
def a(self):
return self.data["a"]
def __enter__(self):
return self
def __exit__(self, ext_type, exc_value, traceback):
pass
def worker(data):
obj = MySpecialClass(data)
for i in range(10):
time.sleep(1)
obj.data[i] = i ** 2
def main():
m = SyncManager()
m.start()
data = m.dict()
server_obj = MySpecialClass(data)
p = Process(target=worker, args=(data,))
p.start()
for i in range(22):
print(server_obj.data)
time.sleep(.5)
p.join()
main()
请记住,由于某些资源,如果需要跨进程协调上下文块,则可以像上面的数据字典一样轻松地传递manager.Lock()对象-即使是字典中的值-然后就可以在对象的__enter__
方法中使用它了。