Call methods again in same order
Question:
For a class with an arbitrary number of methods:
class Test:
def __init__(self, foo):
self.foo = foo
def test1(self, arg1=1, arg2=2):
# do something
print(f"test1, arg1 = {arg1}, arg2 = {arg2}")
def test2(self, arg3=3, arg4=4):
# do somehting else
print(f"test2, arg3 = {arg3}, arg4 = {arg4}")
I would like to keep track of what methods that has been called, in what order, and the arguments. I would like a method that can then call the methods in the same order, with the same arguments.
My first intution was to create a decorator that sets a dictionary with function name and function, and a list with the order of the function names:
def set_stuff(func):
def inner(self, *args, **kwargs):
func_name = func.__name__
self.called.append({f"{func_name}": func})
func(self, *args, **kwargs)
return inner
class Test:
def __init__(self, foo):
self.foo = foo
self.called = list()
@set_stuff
def test1(self, arg1=1, arg2=2):
# do something
print(f"test1, arg1 = {arg1}, arg2 = {arg2}")
@set_stuff
def test2(self, arg3=3, arg4=4):
# do somehting else
print(f"test2, arg3 = {arg3}, arg4 = {arg4}")
def call_in_order(self):
for func_order in self.called:
func = list(func_order.values())[0]
func(self)
Which results in:
test = Test('yo')
test.test2()
test2, arg3 = 3, arg4 = 4
test.test1()
test1, arg1 = 1, arg2 = 2
test.call_in_order()
test2, arg3 = 3, arg4 = 4
test1, arg1 = 1, arg2 = 2
This is what i want, however it has two problems:
- It only works for the default argument input. If
test.test1(arg1='foo', arg2='bar')
is called, test.call_in_order()
will still call the method with the default arguments (arg1=1, arg2=2)
.
- It should be able to call the same method multiple times. This is (obviously) not possible when using dictionaries.
Anyone with an elegant solution?
Update: as @MattDMo pointed out, it makes most sense to use a list of dictionaries. This has now been done in the example and it solves the second problem.
Answers:
How about also keeping track of the arguments?
def set_stuff(func):
def inner(self, *args, **kwargs):
func_name = func.__name__
self.called.update({f"{func_name}": func})
self.call_order.append(func_name)
if args:
self.args.append(args)
else:
self.args.append(())
if kwargs:
self.kwargs.append(kwargs)
else:
self.kwargs.append({})
func(self, *args, **kwargs)
return inner
class Test:
def __init__(self, foo):
self.foo = foo
self.called = dict()
self.call_order = list()
self.args = list()
self.kwargs = list()
@set_stuff
def test1(self, arg1=1, arg2=2):
# do something
print(f"test1, arg1 = {arg1}, arg2 = {arg2}")
@set_stuff
def test2(self, arg3=3, arg4=4):
# do somehting else
print(f"test2, arg3 = {arg3}, arg4 = {arg4}")
def call_in_order(self):
for func_name, args, kwargs in zip(self.call_order, self.args, self.kwargs):
func = self.called[f'{func_name}']
func(self, *args, **kwargs)
test = Test('yo')
test.test2()
test2, arg3 = 3, arg4 = 4
test.test1()
test1, arg1 = 1, arg2 = 2
test.test1(arg2=17,arg1=2)
test1, arg1 = 2, arg2 = 17
test.test1(4,5)
test1, arg1 = 4, arg2 = 5
test.call_in_order()
test2, arg3 = 3, arg4 = 4
test1, arg1 = 1, arg2 = 2
test1, arg1 = 2, arg2 = 17
test1, arg1 = 4, arg2 = 5
What about using a simple list and exec()
? Of course, you need to know what you are doing.
Here is a minimal implementation of the concept:
class Test:
def __init__(self):
self.calls = []
def test1(self, arg1=1, arg2=2):
# do something
s = f"test1(arg1={arg1}, arg2={arg2})"
print('called:', s)
self.calls.append(s)
def test2(self, arg1=3, arg2=4):
# do something
s = f"test2(arg1={arg1}, arg2={arg2})"
print('called:', s)
self.calls.append(s)
def recall(self):
_calls = self.calls[:] #copy the original call stack
for call in _calls:
exec(f"self.{call}")
self.calls = _calls[:] #recover the original call stack
T = Test()
T.test1(2,3)
T.test2(4,5)
print('---')
T.recall()
Output:
called: test1(arg1=2, arg2=3)
called: test2(arg1=4, arg2=5)
---
called: test1(arg1=2, arg2=3)
called: test2(arg1=4, arg2=5)
For a class with an arbitrary number of methods:
class Test:
def __init__(self, foo):
self.foo = foo
def test1(self, arg1=1, arg2=2):
# do something
print(f"test1, arg1 = {arg1}, arg2 = {arg2}")
def test2(self, arg3=3, arg4=4):
# do somehting else
print(f"test2, arg3 = {arg3}, arg4 = {arg4}")
I would like to keep track of what methods that has been called, in what order, and the arguments. I would like a method that can then call the methods in the same order, with the same arguments.
My first intution was to create a decorator that sets a dictionary with function name and function, and a list with the order of the function names:
def set_stuff(func):
def inner(self, *args, **kwargs):
func_name = func.__name__
self.called.append({f"{func_name}": func})
func(self, *args, **kwargs)
return inner
class Test:
def __init__(self, foo):
self.foo = foo
self.called = list()
@set_stuff
def test1(self, arg1=1, arg2=2):
# do something
print(f"test1, arg1 = {arg1}, arg2 = {arg2}")
@set_stuff
def test2(self, arg3=3, arg4=4):
# do somehting else
print(f"test2, arg3 = {arg3}, arg4 = {arg4}")
def call_in_order(self):
for func_order in self.called:
func = list(func_order.values())[0]
func(self)
Which results in:
test = Test('yo')
test.test2()
test2, arg3 = 3, arg4 = 4
test.test1()
test1, arg1 = 1, arg2 = 2
test.call_in_order()
test2, arg3 = 3, arg4 = 4
test1, arg1 = 1, arg2 = 2
This is what i want, however it has two problems:
- It only works for the default argument input. If
test.test1(arg1='foo', arg2='bar')
is called,test.call_in_order()
will still call the method with the default arguments(arg1=1, arg2=2)
. - It should be able to call the same method multiple times. This is (obviously) not possible when using dictionaries.
Anyone with an elegant solution?
Update: as @MattDMo pointed out, it makes most sense to use a list of dictionaries. This has now been done in the example and it solves the second problem.
How about also keeping track of the arguments?
def set_stuff(func):
def inner(self, *args, **kwargs):
func_name = func.__name__
self.called.update({f"{func_name}": func})
self.call_order.append(func_name)
if args:
self.args.append(args)
else:
self.args.append(())
if kwargs:
self.kwargs.append(kwargs)
else:
self.kwargs.append({})
func(self, *args, **kwargs)
return inner
class Test:
def __init__(self, foo):
self.foo = foo
self.called = dict()
self.call_order = list()
self.args = list()
self.kwargs = list()
@set_stuff
def test1(self, arg1=1, arg2=2):
# do something
print(f"test1, arg1 = {arg1}, arg2 = {arg2}")
@set_stuff
def test2(self, arg3=3, arg4=4):
# do somehting else
print(f"test2, arg3 = {arg3}, arg4 = {arg4}")
def call_in_order(self):
for func_name, args, kwargs in zip(self.call_order, self.args, self.kwargs):
func = self.called[f'{func_name}']
func(self, *args, **kwargs)
test = Test('yo')
test.test2()
test2, arg3 = 3, arg4 = 4
test.test1()
test1, arg1 = 1, arg2 = 2
test.test1(arg2=17,arg1=2)
test1, arg1 = 2, arg2 = 17
test.test1(4,5)
test1, arg1 = 4, arg2 = 5
test.call_in_order()
test2, arg3 = 3, arg4 = 4
test1, arg1 = 1, arg2 = 2
test1, arg1 = 2, arg2 = 17
test1, arg1 = 4, arg2 = 5
What about using a simple list and exec()
? Of course, you need to know what you are doing.
Here is a minimal implementation of the concept:
class Test:
def __init__(self):
self.calls = []
def test1(self, arg1=1, arg2=2):
# do something
s = f"test1(arg1={arg1}, arg2={arg2})"
print('called:', s)
self.calls.append(s)
def test2(self, arg1=3, arg2=4):
# do something
s = f"test2(arg1={arg1}, arg2={arg2})"
print('called:', s)
self.calls.append(s)
def recall(self):
_calls = self.calls[:] #copy the original call stack
for call in _calls:
exec(f"self.{call}")
self.calls = _calls[:] #recover the original call stack
T = Test()
T.test1(2,3)
T.test2(4,5)
print('---')
T.recall()
Output:
called: test1(arg1=2, arg2=3)
called: test2(arg1=4, arg2=5)
---
called: test1(arg1=2, arg2=3)
called: test2(arg1=4, arg2=5)