为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

德玛西亚99 2023-12-26 17:05:23
我有一个复杂的不可选取对象,它的属性(通过 getter 和 setter 定义)也是复杂且不可选取类型的。我想为对象创建一个多处理代理来并行执行一些任务。问题:虽然我成功地使 getter 方法可用于代理对象,但我无法使 getter 返回不可选取的返回对象的代理。我的设置类似于以下内容:from multiprocessing.managers import BaseManager, NamespaceProxyclass A():    @property    def a(self):        return B()    @property    def b(self):        return 2# unpickable classclass B():    def __init__(self, *args):        self.f = lambda: 1    class ProxyBase(NamespaceProxy):    _exposed_ = ('__getattribute__', '__setattr__', '__delattr__')class AProxy(ProxyBase): passclass BProxy(ProxyBase): passclass MyManager(BaseManager):passMyManager.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,我会收到一个错误,表明它被调用时参数数量错误。为什么?我该如何解决这个问题?
查看完整描述

1 回答

?
DIEA

TA贡献1820条经验 获得超2个赞

如果没有一些技巧,这是不可能的,因为返回值或代理的选择是仅基于方法名称而不是返回值的类型(来自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, 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)


解决方案的关键是替换_Server我们管理器中的 ,然后将id_to_obj字典包装为针对我们需要的特定方法执行 hack 的字典。


该黑客方法包括填充gettypeid该方法的字典,但只有在它被评估并且我们知道返回类型是我们需要代理的类型之后。幸运的是,我们的评估顺序gettypeid是在调用该方法之后访问的。


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


虽然这是一个有趣的练习,但我不得不说我真的建议不要使用此解决方案,如果您正在处理无法修改的外部代码,您应该简单地创建自己的包装类,该包装类具有显式方法而不是访问@property器,代理您自己的类代替,并使用method_to_typeid.


查看完整回答
反对 回复 2023-12-26
  • 1 回答
  • 0 关注
  • 61 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信