Finding Out Which Functions Are Called Within a Given Function

Finding out which functions are called within a given function

There must be better ways out there, but here's my attempt:

listFunctions <- function(function.name, recursive = FALSE, 
checked.functions = NULL){

# Get the function's code:
function.code <- deparse(get(function.name))

# break code up into sections preceding left brackets:
left.brackets <- c(unlist(strsplit(function.code,
split="[[:space:]]*\\(")))

called.functions <- unique(c(unlist(sapply(left.brackets,
function (x) {

# Split up according to anything that can't be in a function name.
# split = not alphanumeric, not '_', and not '.'
words <- c(unlist(strsplit(x, split="[^[:alnum:]_.]")))

last.word <- tail(words, 1)
last.word.is.function <- tryCatch(is.function(get(last.word)),
error=function(e) return(FALSE))
return(last.word[last.word.is.function])
}))))

if (recursive){

# checked.functions: We need to keep track of which functions
# we've checked to avoid infinite loops.
functs.to.check <- called.functions[!(called.functions %in%
checked.functions)]

called.functions <- unique(c(called.functions,
do.call(c, lapply(functs.to.check, function(x) {
listFunctions(x, recursive = T,
checked.functions = c(checked.functions,
called.functions))
}))))
}
return(called.functions)
}

And the results:

> listFunctions("listFunctions", recursive = FALSE)
[1] "function" "deparse" "get" "c"
[5] "unlist" "strsplit" "unique" "sapply"
[9] "tail" "tryCatch" "is.function" "return"
[13] "if" "do.call" "lapply" "listFunctions"

> system.time(all.functions <- listFunctions("listFunctions", recursive = TRUE))
user system elapsed
92.31 0.08 93.49

> length(all.functions)
[1] 518

As you can see, the recursive version returns a lot of functions. The problem with this is it returns every function called in the process, which obviously adds up as you go. In any case, I hope you can use this (or modify it) to suit your needs.

get the lists of functions used/called within a function in python

This seems to do the work:

import dis
def list_func_calls(fn):
funcs = []
bytecode = dis.Bytecode(fn)
instrs = list(reversed([instr for instr in bytecode]))
for (ix, instr) in enumerate(instrs):
if instr.opname=="CALL_FUNCTION":
load_func_instr = instrs[ix + instr.arg + 1]
funcs.append(load_func_instr.argval)

return ["%d. %s" % (ix, funcname) for (ix, funcname) in enumerate(reversed(funcs), 1)]

Example:

>>> list_func_calls(calculate)
['1. add', '2. subtract']

What's happening here is:

  1. we make a Bytecode object of the function
  2. we reverse the list of instructions, since the function name will
    follow the function call
  3. we step through the list, and for each CALL_FUNCTION instruction,

  4. we use the instructions arg parameter to tell us how many
    arguments we're getting

  5. we look one past that to find the instruction that loads the function
    we're calling

  6. we add that function's name (instr.argval) to a list which we then
    reverse, enumerate, and return in the requested format

Note that since Python 3.6, there are three CALL_FUNCTION instructions, so you'll have to check the documentation to extend this example to be fully functional with current python

How to get the list of in-built functions used within a function

You could use all.vars() to get all the variable names (including functions) that appear inside the body of Fun1, then compare that with some prepared list of functions. You mention in-built functions, so I will compare it with the base package object names.

## full list of variable names inside the function body
(vars <- all.vars(body(Fun1)[-1], functions = TRUE))
# [1] "sum" "x" "y" "mean" "c" "print"

## compare it with the base package object names
intersect(vars, ls(baseenv()))
# [1] "sum" "mean" "c" "print"

I removed the first element of the function body because presumably you don't care about {, which would have been matched against the base package list.

Another possibility, albeit a bit less reliable, would be to compare the formal arguments of Fun1 to all the variable names in the function. Like I said, likely less reliable though because if you make assignments inside the function you will end up with incorrect results.

setdiff(vars, names(formals(Fun1)))
# [1] "sum" "mean" "c" "print"

These are fun though, and you can fiddle around with them.

Determine function name from within that function (without using traceback)

Python doesn't have a feature to access the function or its name within the function itself. It has been proposed but rejected. If you don't want to play with the stack yourself, you should either use "bar" or bar.__name__ depending on context.

The given rejection notice is:

This PEP is rejected. It is not clear how it should be implemented or what the precise semantics should be in edge cases, and there aren't enough important use cases given. response has been lukewarm at best.

Find Out If a Function has been Called

If it's OK for the function to know its own name, you can use a function attribute:

def example():
example.has_been_called = True
pass
example.has_been_called = False


example()

#Actual Code!:
if example.has_been_called:
print("foo bar")

You could also use a decorator to set the attribute:

import functools

def trackcalls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
wrapper.has_been_called = True
return func(*args, **kwargs)
wrapper.has_been_called = False
return wrapper

@trackcalls
def example():
pass


example()

#Actual Code!:
if example.has_been_called:
print("foo bar")

List all functions inside a given function

There are two possible meanings to "all functions inside a given function".

  1. If you mean all functions called from a given function, use codetools::findGlobals(fn, merge = FALSE)$functions. This will list everything referenced from within fn that wasn't defined there.

  2. If you mean all functions defined within a given function, that's a little harder. I think there are other codetools functions that would let you put this together, but I don't know the details.

how to get function's name from within the function (or kind of self reference to the function)?

Perhaps you should decorate each function that you're calling with an onlyonce decorator? That would be more pythonic. A proof of concept follows.

called = set()

def onlyonce(fn):
def decorated(*largs, **kargs):
if fn not in called:
called.add(fn)
print "Calling"
fn(*largs, **kargs)
else:
print "Already called"
return decorated



@onlyonce
def test_function():
print "I am getting called now"


test_function()
test_function()
test_function()
test_function()

Also, functions are "immutable" and can be stored as dictionary keys. You don't have to rely on the names. This might be an advantage (or a disadvantage) depending on your use.



Related Topics



Leave a reply



Submit