How can I determine programmatically if one function calls another function? I cannot modify either function.
Here's what I want (source_calls_target):
>>> def check():
>>> pass
>>> def test_check():
>>> check()
>>> def source_calls_target(source, target):
>>> # if source() calls target() somewhere, return True
>>> ???
>>> source_calls_target(test_check, check)
True
>>> source_calls_target(check, test_check)
False
Ideally, I do not want to actually call target().
Ideally, I want to check if a call to target() appears within the definition for source. It may or may not actually call it depending on conditional statements.
If you can guarantee having access to the source code, you can use ast.parse:
import ast
call_names = [c.func.id for c in ast.walk(ast.parse(inspect.getsource(source)))
if isinstance(c, ast.Call)]
return 'target' in call_names
Note that calls are always by name, so it's difficult (and potentially impossible) to tell whether a call is to a particular function or another of the same name.
In the absence of source code, the only way is via disassembly:
import dis
def ops(code):
i, n = 0, len(code)
while i < n:
op = ord(code[i])
i += 1
if op == dis.EXTENDED_ARG:
ext = ord(code[i]) + ord(code[i+1])*256
op = ord(code[i + 2])
i += 3
else:
ext = 0
if op >= dis.HAVE_ARGUMENT:
arg = ord(code[i]) + ord(code[i+1])*256 + ext*65536
i += 2
yield op, arg
else:
yield op, None
source_ops = list(ops(source.func_code.co_code))
The problem is that it's in practice impossible to tell whether a function is calling another function or just loading a reference to it; if the other function is passed to map or reduce etc. then it will be called but passed to another function it might not be. Practically the sensible thing is to assume that if the function is in source.func_code.co_names then it might be called:
'target' in source.func_code.co_names
Here's a simple example using sys.settrace(). It does require that the source function be called to work. It is also not guaranteed to work, since in some rare instances, two different functions may share the same code object.
import sys
def check():
pass
def test_check():
check()
def source_calls_target(source, target):
orig_trace = sys.gettrace()
try:
tracer = Tracer(target)
sys.settrace(tracer.trace)
source()
return tracer.called
finally:
sys.settrace(orig_trace)
class Tracer:
def __init__(self, target):
self.target = target
self.called = False
def trace(self, frame, event, arg):
if frame.f_code == self.target.func_code:
self.called = True
print source_calls_target(test_check, check)
print source_calls_target(check, test_check)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With