Adopted Solution
The solution that I adopted eventually is to create a Wrapper class to be inherited by my Child classes. This class overrides the __getattr__ method and inspects the code to decide whether the attribute that is attempted to be accessed is a function or a property that is being accessed.
If the attribute is a property that is being accessed (if a . is found after the attribute name), then I use setattr to create that property and assign it to an instance of the same Wrapper class, in a recursive way.
If the attribute was a function call (if a ( was found in the code) then I return a wrapper function for which I can specify a return value.
class Wrapper(object):
output = None
def __init__(self, output=None):
self.output = output
def __getattr__(self, name):
code = str(inspect.stack()[1].code_context)
is_function = True if code.find(name + '(') != -1 else False
is_accessed = True if code.find(name + '.') != -1 else False
if is_accessed:
setattr(self, name, Wrapper(self.output))
return getattr(self, name)
if is_function:
return self.wrapper(self.output)
else:
return self.output
@staticmethod
def wrapper(output):
def wrapper2(*args, **kwargs):
return output
return wrapper2
def __deepcopy__(self, memo):
print(self.__class__, self.__dict__)
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
for k, v in self.__dict__.items():
setattr(result, k, deepcopy(v, memo))
return result
Note that the Wrapper class can be initialized with the default return value output. I'm also overriding the __deepcopy__ method to enable the deepcopy of the child classes and for this, the output property needs to be a class property (I don't fully understand why this works).
The way that I use this class is as follows:
class VirtualB(Wrapper):
def __init__(self):
self.output = "B"
class VirtualC(Wrapper):
def __init__(self):
self.output = "C"
class VirtualA(VirtualB):
def __init__(self):
self.output = "A"
self.B = VirtualB()
self.C = VirtualC()
def capture_a(self):
return "Some virtual data"
a = VirtualA()
print(a.capture_a())
print(a.fun()) # returns A
print(a.par) # returns A
print(a.par.fun()) # returns A
print(a.par.par.fun()) # returns A
print(a.B.fun()) # returns B
print(a.B.par) # returns B
print(a.B.par.fun()) # returns B
print(a.B.par.par.fun()) # returns B
print(a.C.fun()) # returns C
print(a.C.par) # returns C
print(a.C.par.fun()) # returns C
print(a.C.par.par.fun()) # returns C
d = deepcopy(a)
Where as you can see the only function I got to override is capture_a while the rest of the calls are being recursively handled by the Wrapper class.
Original Solution - non optimal for my usecase
A solution that is not optimal requires the instantiation of VirtualB as well (and all the potential additional classes that are instantiated). This is non optimal because it requires to create a Virtual class for every class in the tree.
In this solution I use __getattr__ to redirect all the calls to methods and a double wrapper to choose the return value of the function.
class B():
def __init__(self):
self.attr_c = 0
def setup_b(self):
return "Setup of Class B might fail"
class A():
def __init__(self):
self.attr_b = B()
def setup_a(self):
return "Setup of Class A might fail"
def capture_a(self):
return "Some real captured data"
class VirtualB():
def __getattr__(self, name):
return self.wrapper("Setup of VirtualB can't fail")
def wrapper(self,output):
def wrapper2(*args, **kwargs):
return output
return wrapper2
class VirtualA():
def __init__(self):
self.attr_b = VirtualB()
def capture_a(self):
return "Some virtual data"
def __getattr__(self, name):
return self.wrapper("Setup of VirtualA can't fail")
def wrapper(self,output):
def wrapper2(*args, **kwargs):
return output
return wrapper2
# Usage of A()
a = A()
print(a.setup_a())
print(a.attr_b.setup_b())
a.attr_b.attr_c = 1
print(a.attr_b.attr_c)
print(a.capture_a())
print()
# Usage of VirtualA()
# a = A()
a = VirtualA()
print(a.setup_a())
print(a.attr_b.setup_b())
a.attr_b.attr_c = 1
print(a.attr_b.attr_c)
print(a.capture_a())