Python 多处理代理:让getter自己返回代理

Python 多处理代理:让getter自己返回代理,python,proxy,multiprocessing,python-multiprocessing,multiprocessing-manager,Python,Proxy,Multiprocessing,Python Multiprocessing,Multiprocessing Manager,我有一个复杂的不可点击对象,它的属性(通过getter和setter定义)也是复杂的不可点击类型。我想为对象创建一个多处理代理,以并行执行一些任务 问题:虽然我成功地使getter方法可用于代理对象,但我无法使getter为不可点击的返回对象返回代理 我的设置类似于以下内容: from multiprocessing.managers import BaseManager, NamespaceProxy class A(): @property def a(self):

我有一个复杂的不可点击对象,它的属性(通过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 ?!

我很清楚,我的解决方案不起作用,因为
\uu getattr\uu
方法适用于所有属性,而我只希望它在访问属性
a
时返回
B
的代理。我怎样才能做到这一点


作为一个附带问题:如果我从
B
\uuuu init\uuuu
方法中删除
*args
参数,我会得到一个错误,即调用该参数的参数数量错误。为什么?如何解决这个问题?

我不知道,如果没有一些技巧,这是不可能的,因为返回值或代理的选择仅基于方法名称,而不是返回值的类型(来自
服务器。服务\u客户端
):

还请记住,在不可访问类的代理中公开
\uuuu getattribute\uuuu
基本上会在调用方法时破坏代理功能

但是,如果您愿意破解它,并且只需要访问属性,那么这里有一个可行的解决方案(注意调用
myA.a.f()
仍然不起作用,lambda是一个属性,没有代理,只有方法可以,但这是一个不同的问题)

解决方案的关键是在我们的管理器中替换
\u服务器
,然后将
id\u包装到\u obj
dict中,使用一个为我们需要的特定方法执行hack的服务器

hack包括为方法填充
gettypeid
dict,但只有在对其进行评估并且我们知道返回类型是我们需要代理的返回类型之后。幸运的是,按照求值顺序,
gettypeid
在方法被调用后被访问

同样幸运的是,
gettypeid
service\u client
方法中被用作本地,因此我们可以返回它的副本并对其进行修改,并且不会引入任何并发问题

虽然这是一个有趣的练习,但我必须说,我确实建议不要使用这种解决方案,如果您处理的是无法修改的外部代码,那么您只需创建自己的包装类,该类具有显式方法,而不是
@property
访问器,代理您自己的类,并使用
方法\u到\u typeid

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)
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, 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 = 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)