I have a very deeply nested class structure spanning multiple files and multiple levels of definitions. You can imagine this as a class describing some hardware system where each hardware component is represented by a class.
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 A is instantiated once at the beginning and after that, any of its nested members can be called.
I'd like to create a virtual class (eg: class VirtualA
) that replaces the instantiation of Class A at the beginning and allows me to use the same code as before without modification.
The scope of VirtualA
is to virtualize the hardware instruments at different nested levels and provide some virtual data.
Requirements are:
example:
# example of class VirtualA definition
class VirtualA():
def capture_a(self):
return "Some virtual data"
# TODO: add some minimal code (without replicating the whole nested class) to make the rest of the calls automatically pass
# example of usage of class A
a = A()
a.setup_a()
a.attr_b.setup_b()
a.attr_b.attr_c = 1
print(a.capture_a()) # -> "Some real captured data"
# example of usage of class VirtualA
a = VirtualA()
a.setup_a()
a.attr_b.setup_b()
a.attr_b.attr_c = 1
print(a.capture_a()) # -> "Some virtual data"
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.
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())