I've started writing a small Python utility to cache functions. The available caching tools (lru_cache
, Beaker) do not detect changes of sub-functions.
For this, I need a Call Graph. There exists an excellent tool in pycallgraph by Gerald Kaszuba. However, so far I've only got it to output function-name strings. What I need are either function-objects or function-code-hashes.
What I mean with these two terms: Let def foo(x): return x
, then foo
is the function-object, and hash(foo.__code__.co_code)
is the function-code-hash.
You can see what I have here. But below is a minimal example. The problem I have in this example, is that I can't go from a function name (the string) to the function definition again. I'm trying with eval(func)
.
So, I guess there are two ways of solving this:
pycallgraph.output
, or some otherway to get what I want directly from Pycallgraph.function.__name__
string.import unittest
from pycallgraph import PyCallGraph
from pycallgraph.output import GraphvizOutput
class Callgraph:
def __init__(self, output_file='callgraph.png'):
self.graphviz = GraphvizOutput()
self.graphviz.output_file = output_file
def execute(self, function, *args, **kwargs):
with PyCallGraph(output=self.graphviz):
ret = function(*args, **kwargs)
self.graph = dict()
for node in self.graphviz.processor.nodes():
if node.name != '__main__':
f = eval(node.name)
self.graph[node.name] = hash(f.__code__.co_code)
return ret
def unchanged(self):
'''Checks each function in the callgraph whether it has changed.
Returns True if all the function have their original code-hash. False otherwise.
'''
for func, codehash in self.graph.iteritems():
f = eval(func)
if hash(f.__code__.co_code) != codehash:
return False
return True
def func_inner(x):
return x
def func_outer(x):
return 2*func_inner(x)
class CallgraphTest(unittest.TestCase):
def testChanges(self):
cg = Callgraph()
y = cg.execute(func_outer, 3)
self.assertEqual(6, y)
self.assertTrue(cg.unchanged())
# Change one of the functions
def func_inner(x):
return 3+x
self.assertFalse(cg.unchanged())
# Change back!
def func_inner(x):
return x
self.assertTrue(cg.unchanged())
if __name__ == '__main__':
unittest.main()
I've solved this by patching tracer.py
with the appropriate hashes.
# Work out the current function or method
func_name = code.co_name
+ func_hash = hash(code.co_code)
I am calculating the value just where the function name is saved. Later on, you'd obviously also need to save that value. I am doing that with a dictionary where the func_name
is the key and the hash is the value. In the function where nodes are created I am then assigning this to a new field in stat_group
.