I have a series of parent-children classes A(), B(A),... possibly in different modules. A user will import one of these classes and define its own child class Z(X) deriving either from X = A, B,.... Then he will initialize his instance with an initialize method defined in the parent class X, or in his overridden initialize in Z. I want the initialize function to save its arguments each time it is called and I have prepared in A() a save_last_arginfo() function that could be called automatically by a decorator of initialize.
I give an example that works with save_last_arginfo being explicitly called from Z.initialize(). How to replace this call by the decorator defined in A? I am not sure
import inspect
import functools
class A():
lastargsinfo = None
#@decorator
def initialize(self, *args, **kwargs):
pass
def save_last_arginfo(self):
args1, varargs1, varkw1, defaults1, = inspect.getfullargspec(self.initialize)[0:4]
frame = inspect.getouterframes(inspect.currentframe())[1][0] # will this work when using the decorator
args2, varargs2, keywords2, locals2 = inspect.getargvalues(frame)
self.lastarginfo = [args1, defaults1, locals2] # I'll do a dictionary with args and kwargs later
def decorator(self): # is this syntax correct?
@functools.wraps(self.func)
def wrapper(*args, **kwargs):
self.save_last_arginfo()
return self.func(*args, **kwargs)
return wrapper
class B(A):
#@decorator
def initialize(self, a, b=1):
pass
class Z(B):
#@decorator # how to correct this syntax? A.decorator? what to do with self?
def initialize(self, a, b=1, c=2, **kwargs):
self.save_last_arginfo() # how to remove this line and use the decorator instead
return (a+b)*c
myZ = Z()
myZ.initialize(0,b=3, c=8, d=12)
print(myZ.lastarginfo)
# returns [['self', 'a', 'b', 'c'], (1, 2), {'self': <Z object at 0x000001BBBF135340>, 'a': 0, 'b': 3, 'c': 8, 'kwargs': {'d': 12}}]
I try to reformulate Daniil answers, keeping it simple for me, and closer to my goals, in order to check whether I have understood and to continue the discussion.
import inspect
import functools
class A:
def __init__(self):
self.last_call_info = []
def save_last_call(func):
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
result = func(self, *args, **kwargs)
self.last_call_info = [inspect.signature(func), args, kwargs]
return result
return wrapper
@save_last_call
def initialize(self, *args, **kwargs):
pass
@classmethod
def __init_subclass__(cls, **kwargs):
setattr(cls, "initialize", cls.save_last_call(cls.initialize))
class B(A):
def initialize(self, a, x=5, y=8):
# Do something here
pass
class Z(B):
def initialize(self, a, b, x=5, y=8):
# Do something here
pass
myZ = Z()
myZ.initialize(1,2,x=4)
print(myZ.last_call_info)
outputs: [<Signature (self, a, b, x=5, y=8)>, (1, 2), {'x': 4}]
Note that in the wrapper, I have to call func BEFORE storing its arguments to avoid a bug occuring when the last programmer who overrides initialize in Z choose to reuse X.initialise (by calling super for instance), so that save_last_call is called twice for two different initialize with different arguments. Example:
class Z(B):
def initialize(self, a, b, x=5, y=9):
super().initialize(a, x=x ,y=y)
self.b = b
myZ = Z()
myZ.initialize(1,2,x=4)
print(myZ.last_call_info )
outputs now the correct result [<Signature (self, a, b, x=5, y=9)>, (1, 2), {'x': 4}]