如何解决多处理代理:让获取者自己返回代理
我有一个复杂的不可拾取对象,该对象的属性(通过getter和setter定义)也具有复杂且不可拾取的类型。我想为该对象创建一个多处理代理,以并行执行一些任务。
问题:虽然我已成功使getter方法可用于代理对象,但我无法使getter返回无法拾取的返回对象的代理。
我的设置类似于以下内容:
from multiprocessing.managers import BaseManager,NamespaceProxy
class A():
@property
def a(self):
return B()
@property
def b(self):
return 2
# unpickable class
class B():
def __init__(self,*args):
self.f = lambda: 1
class ProxyBase(NamespaceProxy):
_exposed_ = ('__getattribute__','__setattr__','__delattr__')
class AProxy(ProxyBase): pass
class BProxy(ProxyBase): pass
class MyManager(BaseManager):pass
MyManager.register('A',A,AProxy)
if __name__ == '__main__':
with MyManager() as manager:
myA = manager.A()
print(myA.b) # works great
print(myA.a) # raises error,because the object B is not pickable
我知道在管理器中注册方法时可以指定方法的结果类型。也就是说,我可以做
MyManager.register('A',AProxy,method_to_typeid={'__getattribute__':'B'})
MyManager.register('B',B,BProxy)
if __name__ == '__main__':
with MyManager() as manager:
myA = manager.A()
print(myA.a) # works great!
print(myA.b) # returns the same as myA.a ?!
我很清楚,我的解决方案无效,因为__getattr__
方法适用于所有属性,而我只希望它在属性B
为a
时返回*args
的代理访问。我该如何实现?
作为附带的问题:如果我从__init__
的{{1}}方法中删除了B
自变量,则会收到一个错误,即调用了错误数量的自变量。为什么?我该如何解决?
解决方法
我不可能没有技巧,因为返回值或代理的选择仅基于方法名称而不是返回值的类型(来自Server.serve_client
):
try:
res = function(*args,**kwds)
except Exception as e:
msg = ('#ERROR',e)
else:
typeid = gettypeid and gettypeid.get(methodname,None)
if typeid:
rident,rexposed = self.create(conn,typeid,res)
token = Token(typeid,self.address,rident)
msg = ('#PROXY',(rexposed,token))
else:
msg = ('#RETURN',res)
还请记住,在调用方法时,将__getattribute__
暴露在不可挑选类的代理中基本上会破坏代理功能。
但是,如果您愿意破解它而只需要属性访问,这是一个可行的解决方案(请注意,调用myA.a.f()
仍然无法正常工作,lambda是一个属性,并且没有代理,只有方法可以,但这是另一个问题)。
import os
from multiprocessing.managers import BaseManager,NamespaceProxy,Server
class A():
@property
def a(self):
return B()
@property
def b(self):
return 2
# unpickable class
class B():
def __init__(self,*args):
self.f = lambda: 1
self.pid = os.getpid()
class HackedObj:
def __init__(self,obj,gettypeid):
self.obj = obj
self.gettypeid = gettypeid
def __getattribute__(self,attr):
if attr == '__getattribute__':
return object.__getattribute__(self,attr)
obj = object.__getattribute__(self,'obj')
result = object.__getattribute__(obj,attr)
if isinstance(result,B):
gettypeid = object.__getattribute__(self,'gettypeid')
# This tells the server that the return value of this method is
# B,for which we've registered a proxy.
gettypeid['__getattribute__'] = 'B'
return result
class HackedDict:
def __init__(self,data):
self.data = data
def __setitem__(self,key,value):
self.data[key] = value
def __getitem__(self,key):
obj,exposed,gettypeid = self.data[key]
if isinstance(obj,A):
gettypeid = gettypeid.copy() if gettypeid else {}
# Now we need getattr to update gettypeid based on the result
# luckily BaseManager queries the typeid info after the function
# has been invoked
obj = HackedObj(obj,gettypeid)
return (obj,gettypeid)
class HackedServer(Server):
def __init__(self,registry,address,authkey,serializer):
super().__init__(registry,serializer)
self.id_to_obj = HackedDict(self.id_to_obj)
class MyManager(BaseManager):
_Server = HackedServer
class ProxyBase(NamespaceProxy):
_exposed_ = ('__getattribute__','__setattr__','__delattr__')
class AProxy(ProxyBase): pass
class BProxy(ProxyBase): pass
MyManager.register('A',callable=A,proxytype=AProxy)
MyManager.register('B',callable=B,proxytype=BProxy)
if __name__ == '__main__':
print("This process: ",os.getpid())
with MyManager() as manager:
myB = manager.B()
print("Proxy process,using B directly: ",myB.pid)
myA = manager.A()
print('myA.b',myA.b)
print("Proxy process,via A: ",myA.a.pid)
该解决方案的关键是替换我们管理器中的_Server
,然后将id_to_obj
字典包装为对我们所需的特定方法进行破解的字典。
hack包括为该方法填充gettypeid
dict,但是只有在对它进行评估之后,并且我们知道返回类型是我们需要代理的类型。而且我们很幸运,按照求值的顺序,在调用方法后可以访问gettypeid
。
幸运的是,gettypeid
在serve_client
方法中用作本地变量,因此我们可以返回它的副本并对其进行修改,而不会引入任何并发问题。
虽然这是一个有趣的练习,但我不得不反对这种解决方案,如果您要处理的是无法修改的外部代码,则应该简单地创建自己的具有显式方法的包装器类,而不要使用{{ 1}}访问者,而是代理您自己的类,并使用@property
。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。