In Python, determine if a function calls another function

佐手、 提交于 2019-12-11 12:46:53

问题


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.


回答1:


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



回答2:


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)



回答3:


Probably very ugly way but it works:

import profile

def source_calls_target(source, target):
    pro = profile.Profile()
    pro.run(source.__name__ + '()')
    pro.create_stats()
    return target.__name__ in [ele[2] for ele in pro.stats]



回答4:


I had been coming up with something that ended up looking very similar to @Luke's good answer. Mine is just a simple function, but it checks if the immediate caller of the target function was the source function:

import sys
from functools import partial

def trace_calls(call_list, frame, event, arg):
    if event != 'call':
        return
    call_list.append(frame.f_code)

def test_call(caller, called):
    traces = []
    old = sys.gettrace()
    sys.settrace(partial(trace_calls, traces))
    try:
        caller()
    finally:
        sys.settrace(old)

    try:
        idx = traces.index(called.func_code)
    except ValueError:
        return False

    if idx and traces[idx-1] == caller.func_code:
        return True

    return False

And testing...

def a():
    b()

def b():
    pass

def c():
    a()


test_call(c,a) #True
test_call(a,b) #True
test_call(c,b) #False



回答5:


This post http://stefaanlippens.net/python_inspect will help you



来源:https://stackoverflow.com/questions/12013399/in-python-determine-if-a-function-calls-another-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!