Welcome, guest | Sign In | My Account | Store | Cart

Given an object, this tool throws up a gtk tree widget that maps all the references found. It dynamically builds the tree, which means it can handle large amounts of data and circular references.

Python, 108 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
#!/usr/bin/env python

import pygtk
pygtk.require('2.0')
import gtk

class Browser:
    def make_row( self, piter, name, value ):
        info = repr(value)
        if not hasattr(value, "__dict__"):
            if len(info) > 80:
                # it's a big list, or dict etc. 
                info = info[:80] + "..."
        _piter = self.treestore.append( piter, [ name, type(value).__name__, info ] )
        return _piter

    def make_instance( self, value, piter ):
        if hasattr( value, "__dict__" ):
            for _name, _value in value.__dict__.items():
                _piter = self.make_row( piter, "."+_name, _value )
                _path = self.treestore.get_path( _piter )
                self.otank[ _path ] = (_name, _value)

    def make_mapping( self, value, piter ):
        keys = []
        if hasattr( value, "keys" ):
            keys = value.keys()
        elif hasattr( value, "__len__"):
            keys = range( len(value) )
        for key in keys:
            _name = "[%s]"%str(key)
            _piter = self.make_row( piter, _name, value[key] )
            _path = self.treestore.get_path( _piter )
            self.otank[ _path ] = (_name, value[key])

    def make(self, name=None, value=None, path=None, depth=1):
        if path is None:
            # make root node
            piter = self.make_row( None, name, value )
            path = self.treestore.get_path( piter )
            self.otank[ path ] = (name, value)
        else:
            name, value = self.otank[ path ]

        piter = self.treestore.get_iter( path )
        if not self.treestore.iter_has_child( piter ):
            self.make_mapping( value, piter )
            self.make_instance( value, piter )

        if depth:
            for i in range( self.treestore.iter_n_children( piter ) ):
                self.make( path = path+(i,), depth = depth - 1 )

    def row_expanded( self, treeview, piter, path ):
        self.make( path = path )

    def delete_event(self, widget, event, data=None):
        gtk.main_quit()
        return gtk.FALSE

    def __init__(self, name, value):
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_title("Browser")
        self.window.set_size_request(512, 320)
        self.window.connect("delete_event", self.delete_event)

        # we will store the name, the type name, and the repr 
        columns = [str,str,str]
        self.treestore = gtk.TreeStore(*columns)

        # the otank tells us what object we put at each node in the tree
        self.otank = {} # map path -> (name,value)
        self.make( name, value )

        self.treeview = gtk.TreeView(self.treestore)
        self.treeview.connect("row-expanded", self.row_expanded )

        self.tvcolumns = [ gtk.TreeViewColumn() for _type in columns ]
        i = 0
        for tvcolumn in self.tvcolumns:
            self.treeview.append_column(tvcolumn)
            cell = gtk.CellRendererText()
            tvcolumn.pack_start(cell, True)
            tvcolumn.add_attribute(cell, 'text', i)
            i = i + 1

        self.window.add(self.treeview)
        self.window.show_all()

def dump( name, value ):
    browser = Browser( name, value )
    gtk.main()

def test():
    class Nil:
        pass
    a = Nil()
    b = Nil()
    c = Nil()
    d = Nil()
    a.b=b
    b.c=c
    c.d=d
    d.a=a # circular chain
    dump( "a", a )

if __name__ == "__main__":
    test()

This script is useful for debugging the suspect object that has just caused an exception by calling the dump( ) routine from the except clause of your script. It is also good for checking through old pickled data, or other complex object stores.

The make* routines can be adjusted to taste: for example to search through dir(value) to obtain information about modules and classes.

This is inspired by a browse.py script supplied with the pygtk distribution, that alas, has not been updated to pygtk-2.x. See also the TreeView example in the excellent pygtk tutorial by John Finlay.

2 comments

John Pye 15 years, 5 months ago  # | flag

This code is useful but is broken when an object contains Python 'sets', eg

x = set([3,4,5])

Sets are different because they have a __len__ attribute, but are not indexable.

It seems likely that this highlights a more general failing of the code. If I manage to work out a better implementation, I'll post it here.