简体   繁体   中英

Non-graphical output from pycallgraph

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 .

What I have

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:

  1. Proper pycallgraph.output , or some otherway to get what I want directly from Pycallgraph.
  2. Dynamically loading the function from the 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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM