You need to define a proxy class for your TestClass
that supports attributes. This should inherit from NamespaceProxy
to support attributes and you will have to explicitly expose the methods as follows:
class TestClassProxy(NamespaceProxy):
_exposed_ = ('__getattribute__', '__setattr__', '__delattr__', 'add_to_a')
def add_to_a(self, b):
callmethod = object.__getattribute__(self, '_callmethod')
return callmethod('add_to_a', args=(b,))
I would also suggest renaming TestSubClass
to TestContainedClass
. And although it doesn't matter in your code as it currently stands, I would move the register
statement to be within the if __name__ == '__main__':
block so that if this code is ever modified to create processes, the register
statement is not needlessly executed by the newly created processes if you happen to be on a platform that uses spawn
to create these processes (e.g. Windows). It's not a big deal one way or another.
Putting this all together:
from multiprocessing.managers import BaseManager, NamespaceProxy
class TestContainedClass:
def __init__(self):
self.a = 1
def set_a(self, a):
self.a = a
class TestClass:
def __init__(self):
self.subclass = TestContainedClass()
def add_to_a(self, b):
return b + self.subclass.a
class MyManager(BaseManager): pass
class TestClassProxy(NamespaceProxy):
_exposed_ = ('__getattribute__', '__setattr__', '__delattr__', 'add_to_a')
def add_to_a(self, b):
callmethod = object.__getattribute__(self, '_callmethod')
return callmethod('add_to_a', args=(b,))
if __name__ == '__main__':
MyManager.register('TestClass', TestClass, TestClassProxy)
with MyManager() as manager:
# Creates classes
t = TestClass()
mt : TestClass = manager.TestClass()
# Runs the Exposed add_to_a method
print (t.add_to_a(2))
print (mt.add_to_a(2))
# Runs the subclass method for the regular class
t.subclass.set_a(2)
print (t.add_to_a(2))
# Runs the subclass method for the proxy class (Fails)
mt.subclass.set_a(2)
print (mt.add_to_a(2))
Prints:
3
3
4
3
But notice the last result is 3
and not the expected 4
. Nested objects does not seem to work well with the Manager
. See Sharing a complex object between processes?.
To get this to work, both classes must be represented by proxies:
from multiprocessing.managers import BaseManager, NamespaceProxy
class MyManager(BaseManager): pass
class TestContainedClass:
def __init__(self):
self.a = 1
def set_a(self, a):
self.a = a
class TestContainedClassProxy(NamespaceProxy):
_exposed_ = ('__getattribute__', '__setattr__', '__delattr__', 'set_a')
def set_a(self, a):
callmethod = object.__getattribute__(self, '_callmethod')
return callmethod('set_a', args=(a,))
class TestClass:
def __init__(self, subclass):
self.subclass = subclass
def add_to_a(self, b):
return b + self.subclass.a
class TestClassProxy(NamespaceProxy):
_exposed_ = ('__getattribute__', '__setattr__', '__delattr__', 'add_to_a')
def add_to_a(self, b):
callmethod = object.__getattribute__(self, '_callmethod')
return callmethod('add_to_a', args=(b,))
if __name__ == '__main__':
MyManager.register('TestContainedClass', TestContainedClass, TestContainedClassProxy)
MyManager.register('TestClass', TestClass, TestClassProxy)
with MyManager() as manager:
# Creates classes
testContainedClass = manager.TestContainedClass()
mt : TestClass = manager.TestClass(testContainedClass)
# Runs the Exposed add_to_a method
print (mt.add_to_a(2))
# Runs the subclass method for the proxy class
mt.subclass.set_a(2)
print (mt.add_to_a(2))
Prints:
3
4
It would, of course, be simpler, just to refactor your original code:
from multiprocessing.managers import BaseManager, NamespaceProxy
class TestClass:
def __init__(self, a):
self.a = a
def add_to_a(self, b):
return b + self.a
class MyManager(BaseManager): pass
class TestClassProxy(NamespaceProxy):
_exposed_ = ('__getattribute__', '__setattr__', '__delattr__', 'add_to_a')
def add_to_a(self, b):
callmethod = object.__getattribute__(self, '_callmethod')
return callmethod('add_to_a', args=(b,))
if __name__ == '__main__':
MyManager.register('TestClass', TestClass, TestClassProxy)
with MyManager() as manager:
# Creates classes
t = TestClass(1)
mt : TestClass = manager.TestClass(1)
# Runs the Exposed add_to_a method
print (t.add_to_a(2))
print (mt.add_to_a(2))
# Runs the subclass method for the regular class
t.a = 2
print (t.add_to_a(2))
# Runs the subclass method for the proxy class
mt.a = 2
print (mt.add_to_a(2))
Prints:
3
3
4
4
Or, if you are going to access attribute a
using method set_a
, then you can use the default proxy:
from multiprocessing.managers import BaseManager, NamespaceProxy
class TestClass:
def __init__(self, a):
self.set_a(a)
def set_a(self, a):
self.a = a
def add_to_a(self, b):
return b + self.a
class MyManager(BaseManager): pass
if __name__ == '__main__':
MyManager.register('TestClass', TestClass)
with MyManager() as manager:
# Creates classes
t = TestClass(1)
mt : TestClass = manager.TestClass(1)
# Runs the Exposed add_to_a method
print (t.add_to_a(2))
print (mt.add_to_a(2))
# Runs the subclass method for the regular class
t.set_a(2)
print (t.add_to_a(2))
# Runs the subclass method for the proxy class
mt.set_a(2)
print (mt.add_to_a(2))