多处理代理:让获取者自己返回代理

问题描述

我有一个复杂的不可拾取对象,该对象的属性(通过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__方法适用于所有属性,而我只希望它在属性Ba时返回*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

幸运的是,gettypeidserve_client方法中用作本地变量,因此我们可以返回它的副本并对其进行修改,而不会引入任何并发问题。

虽然这是一个有趣的练习,但我不得不反对这种解决方案,如果您要处理的是无法修改的外部代码,则应该简单地创建自己的具有显式方法的包装器类,而不要使用{{ 1}}访问者,而是代理您自己的类,并使用@property