多处理代理:让 getters 返回代理本身

我有一个复杂的不可选取对象,它的属性(通过 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', 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__返回代理。我怎样才能做到这一点?Ba


作为一个附带问题:如果我*args从 __init__的方法中删除参数B,我会收到一个错误,表明它被调用时参数数量错误。为什么?我该如何解决这个问题?


德玛西亚99
浏览 69回答 1
1回答

DIEA

如果没有一些技巧,这是不可能的,因为返回值或代理的选择是仅基于方法名称而不是返回值的类型(来自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 osfrom multiprocessing.managers import BaseManager, NamespaceProxy, Serverclass A():    @property    def a(self):        return B()    @property    def b(self):        return 2# unpickable classclass 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 resultclass 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, exposed, gettypeid)class HackedServer(Server):    def __init__(self, registry, address, authkey, serializer):        super().__init__(registry, address, authkey, serializer)        self.id_to_obj = HackedDict(self.id_to_obj)class MyManager(BaseManager):    _Server = HackedServerclass ProxyBase(NamespaceProxy):    _exposed_ = ('__getattribute__', '__setattr__', '__delattr__')class AProxy(ProxyBase): passclass BProxy(ProxyBase): passMyManager.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该方法的字典,但只有在它被评估并且我们知道返回类型是我们需要代理的类型之后。幸运的是,我们的评估顺序gettypeid是在调用该方法之后访问的。幸运的是,gettypeid它在方法中用作本地变量serve_client,因此我们可以返回它的副本并对其进行修改,并且不会引入任何并发问题。虽然这是一个有趣的练习,但我不得不说我真的建议不要使用此解决方案,如果您正在处理无法修改的外部代码,您应该简单地创建自己的包装类,该包装类具有显式方法而不是访问@property器,代理您自己的类代替,并使用method_to_typeid.
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python