Why local trace function shows line event for a function call? - python

I was trying to understand how functions can be traced by following the code mentioned in this article.
import sys
def trace_lines(frame, event, arg):
if event != 'line':
return
co = frame.f_code
func_name = co.co_name
line_no = frame.f_lineno
filename = co.co_filename
print(f"{func_name} line {line_no}")
def trace_calls(frame, event, arg):
if event != 'call':
return
co = frame.f_code
func_name = co.co_name
if func_name == 'write':
# Ignore write() calls from print statements
return
line_no = frame.f_lineno
filename = co.co_filename
print(f"Call to {func_name} on line {line_no} of {filename}")
if func_name in TRACE_INTO:
# Trace into this function
return trace_lines
return
def c(input):
print('input =', input)
print('Leaving c()')
def b(arg):
val = arg * 5
c(val)
print('Leaving b()')
def a():
b(2)
print('Leaving a()')
TRACE_INTO = ['b']
sys.settrace(trace_calls)
a()
Upon running this program, this is the output that we get:
$ python3 test.py
Call to a on line 37 of /Users/xyz/personal/projects/test.py
Call to b on line 32 of /Users/xyz/personal/projects/test.py
b line 33
b line 34
Call to c on line 28 of /Users/xyz/personal/projects/test.py
input = 10
Leaving c()
b line 35
Leaving b()
Leaving a()
Looking at the program, the first two lines of the output are straightforward. The function a() is called which calls function b(). For b(), a local trace function named trace_lines() is used.
Since the first line in the definition of function b() is an expression, it is treated as a line event and therefore, the b line 33 is printed.
But the second line in function b() is a function call to c() and not an expression. So I was expecting it to be a call event. Since the local trace function for function b() is trace_lines(), it should have returned because of the failure of the first condition if event != 'line'. But here, for a function call to c(), the output first shows a line event to trace_lines() and then a call event to trace_calls().
b line 34
Call to c on line 28 of /Users/xyz/personal/projects/test.py
If this is how it works for every function call, Every encounter of a function call should have looked like this-
Step 1: Pass it as a line event
Step 2: Once it is identified as a function, pass a call event to global trace function.
Here is my question-
Why a line event is generated for the function call to c() before the actual call event to global trace function? Also It would be great if somebody can break this output down with explanation.

Related

Get function signature and parameters from within current function in Python

I'm trying to get all function parameters and their values from within the current function in Python and almost have it working except for one thing: I don't know how to call it from inside the function without explicitly giving it the name of the object.
For example: the code below works for the case of the function being a class method ("my_func_in_class", but I don't know what to pass into the inspect.signature() call for the "my_func" function that is not part of a class without mentioning the function by name.
This isn't critical to solving my problem at hand but I'd still like to know how to make it work
import inspect
import sys
def my_func(a, b, c=None):
this_func = inspect.currentframe()
func_name = inspect.getframeinfo(this_func).function
print(f"Function Name {func_name}")
(sig, local_vars) = inspect.signature(my_func), locals()
args = {}
for a in sig.parameters.keys():
args[a] = local_vars[a]
return args
class MyClass(object):
def __init__(self):
pass
def my_func_in_class(self, a, b, c=None):
this_func = inspect.currentframe()
func_name = inspect.getframeinfo(this_func).function
print(f"Function Name {func_name}")
(sig, local_vars) = inspect.signature(getattr(self, func_name)), locals()
args = {}
for a in sig.parameters.keys():
args[a] = local_vars[a]
return args
if __name__ == "__main__":
class1 = MyClass()
args1 = my_func(1, 2)
args2 = class1.my_func_in_class(10, 20, c=30)
print(f"args1:")
for (k, v) in args1.items():
print(f"{k}: {v}")
print(f"\nargs2")
for (k, v) in args2.items():
print(f"{k}: {v}")
python ./get_func_args.py
Function Name my_func
Function Name my_func_in_class
args1:
a: 1
b: 2
c: None
args2
a: 10
b: 20
c: 30
But:
def my_func(a, b, c=None):
this_func = inspect.currentframe()
func_name = inspect.getframeinfo(this_func).function
print(f"Function Name {func_name}")
(sig, local_vars) = inspect.signature(this_func), locals()
returns the error:
Function Name my_func
Traceback (most recent call last):
File "./get_func_args.py", line 41, in <module>
args1 = my_func(1, 2)
File "./get_func_args.py", line 12, in my_func
(sig, local_vars) = inspect.signature(this_func), locals()
File "/home/mlissa2/cmm/python/miniconda3/lib/python3.6/inspect.py", line 3065, in signature
return Signature.from_callable(obj, follow_wrapped=follow_wrapped)
File "/home/mlissa2/cmm/python/miniconda3/lib/python3.6/inspect.py", line 2815, in from_callable
follow_wrapper_chains=follow_wrapped)
File "/home/mlissa2/cmm/python/miniconda3/lib/python3.6/inspect.py", line 2193, in _signature_from_callable
raise TypeError('{!r} is not a callable object'.format(obj))
TypeError: <frame object at 0x7ff7dcc92048> is not a callable object
So I'm very close but don't have the final step in place.
If your only concern is to get arguments information from the signature of the function along with their values, then you don't need to use inspect.signature(). You can use the following approach.
def my_func(a, b, c=None):
# get arg-values without reference to callable
cframe = inspect.currentframe()
args_info = inspect.getargvalues(cframe)
# Collecting args-values of my_func in a dictionary
this_func_argvals = {arg: args_info.locals.get(arg) for arg in args_info.args}
return this_func_argvals
That's it.
However, if you must insist on getting the signature too for whatnot than use the following approach.
The reason you were able to get my_func_in_class reference in your class method was because your class provided access to its namespace, so getting attribute from that namespace was easy. In order to get reference of my_func we need to access namespace of the immediate outer stack of my_func. From there we can get my_func reference. Here's how to do that:
def some_fun(a, b, c=None):
# Get Stacks. We only need FrameInfo for current frame and immediate outer frame
stacks = inspect.stack()
# Get my_func's name (str) from current frame
this_func_name = stacks[0].function
# Get immediate outer frame
outer_frame = stacks[1].frame
# From outer frame's locals, get reference to my_fun using its name as key
this_func_reference = outer_frame.f_locals.get(this_func_name)
# Voila!
sigs = inspect.signature(this_func_reference)

Why local variables go missing in Python exec while running bytecode?

I've built a function called foo to alter a function's code at bytecode level and execute it before returning to regular function execution flow.
import sys
from types import CodeType
def foo():
frame = sys._getframe(1) # get main's frame
main_code: CodeType = do_something(frame.f_code) # modify function code
# copy globals & locals
main_globals: dict = frame.f_globals.copy()
main_locals: dict = frame.f_locals.copy()
# execute altered bytecode before returning to regular code
exec(main_code, main_globals, main_locals)
return
def main():
bar: list = []
# run altered code
foo()
# return to regular code
bar.append(0)
return bar
if __name__ == '__main__':
main()
Though, there is a problem with the evaluation of the local variable during exec:
Traceback (most recent call last):
File "C:\Users\Pedro\main.py", line 31, in <module>
main()
File "C:\Users\Pedro\main.py", line 23, in main
foo()
File "C:\Users\Pedro\main.py", line 15, in foo
exec(main_code, main_globals, main_locals)
File "C:\Users\Pedro\main.py", line 26, in main
bar.append(0)
UnboundLocalError: local variable 'bar' referenced before assignment
If I print main_locals before the call to exec it shows exactly the same contents as if it was done before calling foo. I wonder if it has to do with any of the frame.f_code.co_* arguments passed to the CodeType constructor. They are pretty much the same, except for the actual bytecode frame.f_code.co_code, to which I made a few operations.
I need help to understand why the evaluation of the code under these globals and locals fail to reference main's local variables.
Note: I'm pretty sure that the changes made to main's bytecode prevent the process from going into unwanted recursion.
Edit: As asked in the comments, the basic behaviour of do_something can be resumed to remove all of main's code before call to foo. Some additional steps would involve applying changes to local variables i.e. bar.
import copy
import dis
## dump opcodes into global scope
globals().update(dis.opmap)
NULL = 0
def do_something(f_code) -> CodeType:
bytecode = f_code.co_code
f_consts = copy.deepcopy(f_code.co_consts)
for i in range(0, len(bytecode), 2):
cmd, arg = bytecode[i], bytecode[i+1]
# watch for the first occurence of calling 'foo'
if cmd == LOAD_GLOBAL and f_code.co_names[arg] == 'foo':
break # use 'i' variable later
else:
raise NameError('foo is not defined.')
f_bytelist = list(bytecode)
f_bytelist[i:i+4] = [
NOP, NULL, ## LOAD
LOAD_CONST, len(f_consts) ## CALL
# Constant 'None' will be added to 'f_consts'
]
f_bytelist[-2:] = [NOP, NULL] # 'main' function RETURN
# This piece of code removes all code before
# calling 'foo' (except for JUMP_ABSOLUTE) so
# it can be usend inside while loops.
null_code = [True] * i
j = i + 2
while j < len(f_bytelist):
if j >= i:
cmd, arg = f_bytelist[j], f_bytelist[j+1]
if cmd == JUMP_ABSOLUTE and arg < i and null_code[arg]:
j = arg
else:
j += 2
else:
null_code[j] = False
j += 2
else:
for j in range(0, i, 2):
if null_code[j]:
f_bytelist[j:j+2] = [NOP, NULL] # skip instruction
else:
continue
f_bytecode = bytes(f_bytelist)
f_consts = f_consts + (None,) ## Add constant to return
return CodeType(
f_code.co_argcount,
f_code.co_kwonlyargcount,
f_code.co_posonlyargcount, # Remove this if Python < 3.8
f_code.co_nlocals,
f_code.co_stacksize,
f_code.co_flags,
f_bytecode,
f_consts,
f_code.co_names,
f_code.co_varnames,
f_code.co_filename,
f_code.co_name,
f_code.co_firstlineno,
f_code.co_lnotab,
f_code.co_freevars,
f_code.co_cellvars
)

In Python, Is there a C++ equivalent to declaring a function and defining it after it's used?

I'm sorry if this has already been answered. I didn't really know how to search for this particular question.
In C++ you can define a variable at the top to later define. For example:
int printOne();
int main()
{
cout << printOne() << endl;
return 0;
}
int printOne
{
return 1;
}
I'm pretty new to Python though, so I was wondering if this was a possibility for Python as well.
you don't have to. Python evaluates everything at run-time:
def a():
print(b())
def b():
return 12
a()
so when a is called b is already defined.
note: that doesn't work because when a() is called b isn't defined yet:
def a():
print(b())
a()
def b():
return 12
Traceback (most recent call last):
File "<string>", line 420, in run_nodebug
File "<module1>", line 4, in <module>
File "<module1>", line 2, in a
NameError: name 'b' is not defined
There generally isn't a need. The function only needs to be defined by the time you call the function. For example this would work just fine even though the definition of printOne was after main.
def main():
print(printOne())
def printOne():
return 1
main()
I found this useful when I want to define all my parameters at the top, but their computation depends on not yet declared code:
# Parameter section ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
param_a = 2
# Like to set param_b here for everyone to see, but it depends on big complex functions:
param_b = lambda: compute_param(param_a)
param_c = 'compute_param(param_a)'
# Function section ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def fn(b = param_b, c = param_c):
''' Function using the global parameters defined at the top
'''
if callable(b):
b = b()
c = eval(str(c))
print(b + c)
def compute_param(x):
return 3*x
# MAin code section ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if __name__ == '__main__':
fn() #default params
fn(10,10)

Python functions recursively calling each other using dictionary storing their name

I have following scenario:
there are multiple function each accepting certain arguments
they call each other based on arguments recursively/iteratively till certain conditions that can be inferred from arguments are met
I can do if-elif in those functions, but since that will cause a lot of if-elif inside all of these functions, I thought I should use dictionary storing reference to these functions against their name as a key and then hash into this dictionary (using argument contents) to obtain and call the function to be called.
The issue is that I am not able to decide where to define that dictionary, before all functions (as all functions will be using this dictionary) or after all functions (as dictionary will use all these functions).
Below I tried to imitate the scenario. I used random function do decide upon which function to call instead of inferring it from the arguments. Also I have used recurCount to decide when to stop recursive calls.
import random
# funcDict = {"fun1": fun1,
# "fun2": fun2,
# "fun3": fun3,
# "fun4": fun4}
#Traceback (most recent call last):
# File "C:\...\temp.py", line 107, in <module>
# funcDict = {"fun1": fun1,
#NameError: name 'fun1' is not defined
funcList = ["fun1","fun2","fun3","fun4"]
recurCount = 5
def fun1():
global recurCount
print("fun1")
if(recurCount == 0):
return
else:
recurCount= recurCount-1
funcDict[random.choice(funcList)]() #recursive call
def fun2():
global recurCount
print("fun2")
if(recurCount == 0):
return
else:
recurCount= recurCount-1
funcDict[random.choice(funcList)]() #recursive call
def fun3():
global recurCount
print("fun3")
if(recurCount == 0):
return
else:
recurCount= recurCount-1
funcDict[random.choice(funcList)]() #recursive call
def fun4():
global recurCount
print("fun4")
if(recurCount == 0):
return
else:
recurCount= recurCount-1
funcDict[random.choice(funcList)]() #recursive call
fun1()
# funcDict = {"fun1": fun1,
# "fun2": fun2,
# "fun3": fun3,
# "fun4": fun4}
#Traceback (most recent call last):
# File "C:\...\temp.py", line 152, in <module>
# fun1()
# File "C:\...\temp.py", line 123, in fun1
# funcDict[random.choice(funcList)]()
#NameError: name 'funcDict' is not defined
The dictionary requires that the functions are already defined, while the first call to any of the functions requires that the dictionary is already defined. Therefore, you should define the dictionary after all the function definitions and before making the first call to any of the functions:
def fun1():
...
def fun2():
...
def fun3():
...
def fun4():
...
funcDict = {"fun1": fun1,
"fun2": fun2,
"fun3": fun3,
"fun4": fun4}
fun1()

Passing function to a class

I have created a class that can take a function with a set of arguments. I would like to run the passed function every time the event handler signals.
I am attaching my code below which runs when I pass a fun2 which has no arguments but not with fun1. Any suggestions that I can make to the code below work with fun1 and fun2? If I omit the return statement from fun1, I get an error that 'str' object is not callable.
>>> TimerTest.main()
function 1. this function does task1
my function from init from function1
my function in start of runTimerTraceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Program Files (x86)\IronPython 2.7\TimerTest.py", line 57, in main
File "C:\Program Files (x86)\IronPython 2.7\TimerTest.py", line 25, in runTime
r
TypeError: str is not callable
import System
from System.Timers import (Timer, ElapsedEventArgs)
class timerTest:
def __init__ (self, interval,autoreset, fun):
self.Timer = Timer()
self.Timer.Interval= interval
self.Timer.AutoReset = autoreset
self.Timer.Enabled = True
self.myfunction = fun
def runTimer(self):
print 'my function in start of runTimer', self.myfunction ()
self.Timer.Start()
def OnTimedEvent (s, e):
print "The Elapsed event was raised at " , e.SignalTime
print 'printing myfunction...', self.myfunction()
self.myfunction()
self.Timer.Elapsed += OnTimedEvent
def stopTimer(self):
self.Timer.Stop()
self.Timer.Dispose= True
def fun1(a,b):
print 'function 1. this function does task1'
return 'from function1'
def fun2():
print 'Function 2. This function does something'
print 'Test 1...2...3...'
return 'From function 2'
def main():
a = timerTest(1000, True, fun1(10,20))
a.runTimer()
b= timerTest(3000,True,fun2)
b.runTimer()
if __name__ == '__main__':
main()
I am learning Python and I apologize if my questions are basic.
To change the interval, I stop the timer using a stopTimer method I added to the timerTest class:
def stopTimer(self):
self.Timer.Stop()
I take the new user input to call the runTimer method which I have revised per Paolo Moretti's suggestions:
def runTimer(self, interval,autoreset,fun,arg1, arg2, etc.):
self.Timer.Interval= interval
self.Timer.AutoReset = autoreset
myfunction = fun
my_args = args
self.Timer.Start()
def OnTimedEvent (s, e):
print "The Elapsed event was raised at " , e.SignalTime
myfunction(*my_args)
self.Timer.Elapsed += OnTimedEvent
Whenever a command button is pressed, the following method is called:
requestTimer.runTimer((self.intervalnumericUpDown.Value* 1000),True, function, *args)
I do not understand why stopping the timer and sending the request causes the runTimer method to be executed multiple times and it seems dependent on how many times I change the interval. I have tried a couple of methods: Close and Dispose with no success.
A second question on slightly different subject.
I have been looking at other .NET classes with Timer classes. A second question is on how I would translate the following VB sample code into Python. Is "callback As TimerCallback" equivalent to myfunction(*my_args)?
Public Sub New ( _
callback As TimerCallback, _
state As Object, _
dueTime As Integer, _
period As Integer _
)
per .NET documentation:
callback
Type: System.Threading.TimerCallback
A TimerCallback delegate representing a method to be executed.
I can partially get the timer event to fire if I define a function with no arguments such as:
def fun2(stateinfo):
# function code
which works with:
self.Timer = Timer(fun2, self.autoEvent, self.dueTime,self.period)
The function call fails if I replace fun2 with a more generic function call myfunction(*my_args)
You can also use * syntax for calling a function with an arbitrary argument list:
class TimerTest:
def __init__(self, interval, autoreset, fun, *args):
# ...
self.my_function = fun
self.my_args = args
# ...
def run_timer(self):
# ...
def on_timed_event(s, e):
# ...
self.my_function(*self.my_args)
# ...
Usage:
>>> t1 = TimerTest(1000, True, fun1, 10, 20)
>>> t2 = TimerTest(1000, True, fun2)
And check out the PEP8 style guide as well. Python's preferred coding conventions are different than many other common languages.
Question 1
Every time you use the addition assignment operator (+=) you are attaching a new event handler to the event. For example this code:
timer = Timer()
def on_timed_event(s, e):
print "Hello form my event handler"
timer.Elapsed += on_timed_event
timer.Elapsed += on_timed_event
timer.Start()
will print the "Hello form my event handler"phrase twice.
For more information you can check out the MSDN documentation, in particular Subscribe to and Unsubscribe from Events .
So, you should probably move the event subscription to the __init__ method, and only start the timer in your run_timer method:
def run_timer(self):
self.Timer.Start()
You could also add a new method (or use a property) for changing the interval:
def set_interval(self, interval):
self.Timer.Interval = interval
Question 2
You are right about TimerCallback: it's a delegate representing a method to be executed.
For example, this Timer constructor:
public Timer(
TimerCallback callback
)
is expecting a void function with a single parameter of type Object.
public delegate void TimerCallback(
Object state
)
When you are invoking a function using the * syntax you are doing something completely different. It's probably easier if I'll show you an example:
def foo(a, b, *args):
print a
print b
print args
>>> foo(1, 2, 3, 4, 5)
1
2
(3, 4, 5)
>>> args = (1, 2, 3)
>>> foo(1, 2, *args)
1
2
(1, 2, 3)
Basically in the second case you are invoking a function with additional arguments unpacked from a tuple.
So If you want to pass a function with a different signature to a constructor which accepts a TimerCallback delegate you have to create a new function, like #Lasse is suggesting.
def my_func(state, a, b):
pass
You can do this either using the lambda keyword:
t1 = Timer(lambda state: my_func(state, 1, 2))
or by declaring a new function:
def time_proc(state):
my_func(state, 1, 2)
t2 = Timer(time_proc)
If the function takes no parameters, simply pass it without calling it:
b = timerTest(3000, True, fun2)
If it takes parameters, you need to convert it to a function that doesn't take parameters. What you're doing is calling it, and then you pass the result, which in this case is a string. Instead do this:
a = timerTest(1000, True, lambda: fun1(10, 20))

Categories

Resources