【问题标题】:Multiprocessing proxy: let getters return proxies themselves多处理代理:字母 getters 自己返回代理
【发布时间】:2021-02-06 20:20:25
【问题描述】:

我有一个复杂的不可拾取对象,它具有复杂且不可拾取类型的属性(通过 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__ 方法适用于所有属性,而我只希望它在访问属性a 时返回B 的代理。我怎样才能做到这一点?

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

【问题讨论】:

    标签: python proxy multiprocessing python-multiprocessing multiprocessing-manager


    【解决方案1】:

    如果没有一些技巧,我是不可能做到这一点的,因为返回值或代理的选择仅基于方法名称,而不是返回值的类型(来自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 dict 包装为执行我们需要的特定方法的hack。

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

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-06-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多