Reputation: 6779
I know python is extremely flexible allowing -almost- anything user wants. However I have never seen nor heard of such a feature, and could not find anything related online: is it possible to execute a variable that is a function step by step?
def example_function():
print("line 1")
# stuff
print("line 2")
# stuff
return(3)
def step_by_step_executor(fn):
while fn.has_next_step():
print(fn.current_step)
fn.execute_step()
return fn.return
step_by_step_executor(example_function)
# print("line 1")
# line 1
# stuff
# print("line 2")
# line 2
# stuff
# return(3)
# returns 3
I think I can implement something like this using a combination of inspect
, exec
and maybe __call__
, but I am interested to see if there is an already existing name and implementation for this.
Example use cases:
@do_y_instead_of_x
def some_function():
do stuff
do x
do more
some_function()
# does stuff
# does y
# does more
@update_progress_bar_on_loops
def some_other_function():
do stuff
for x in range...:
...
do more
some_other_function()
# does stuff
# initializes a progress bar, reports whats going on, does the loop
# does more
Upvotes: 3
Views: 356
Reputation: 106543
You can create a Python debugger pdb.Pdb
instance and pass to it a custom file-like object that implements the write
method to selectively output the code portions of the debugger output, and the readline
method to always send to the debugger the n
(short for next
) command. Since the debugger always outputs the line that returns from a function twice, the second time of which is preceded by a --Return--
line, you can use a flag to avoid outputting the redundant line of return:
import pdb
class PdbHandler:
def __init__(self):
self.returning = False
def write(self, buffer):
if buffer == '--Return--':
self.returning = True
# each line of code is prefixed with a '-> '
_, *code = buffer.split('\n-> ', 1)
if code:
if self.returning:
self.returning = False
else:
print(code[0])
def readline(self):
return 'n\n'
def flush(self):
pass
def example_function():
print("line 1")
print("line 2")
return (3)
handler = PdbHandler()
print('returns', pdb.Pdb(stdin=handler, stdout=handler).runcall(example_function))
This outputs:
print("line 1")
line 1
print("line 2")
line 2
return (3)
returns 3
Upvotes: 4
Reputation: 7210
How about yield
- it does more than people think and can be used to fundamentally represent coroutines and ordering - a feature perhaps more fundamental than it's use to build iterators. Your example:
def example_function():
print("line 1")
yield
print("line 2")
yield
print("line 3")
return 3
def step_by_step_executor(fn):
res = fn()
while True:
try:
print(next(res))
except StopIteration as e:
retval = e.value
break
return retval
print(step_by_step_executor(example_function))
Which results in
line 1
None
line 2
None
line 3
3
As the return value of the generator is given as the value of the stop iteration that is raised. If you choose to intermittently yield
values (instead of blank yield
), you will see those printed at each iteration as well.
One common place where this kind of code comes up is the contextlib
contextmanager
decorator which is used to turn a generator into a context manager though the series of enter and exit steps.
Upvotes: 2