summaryrefslogblamecommitdiffstats
path: root/venv/lib/python3.9/site-packages/pympler/refbrowser.py
blob: ae8966fd4716bcdd3bfd4f5bc1e7e76cf2166ba2 (plain) (tree)
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451


































































































































































































































































































































































































































































                                                                               
"""Tree-like exploration of object referrers.

This module provides a base implementation for tree-like referrers browsing.
The two non-interactive classes ConsoleBrowser and FileBrowser output a tree
to the console or a file. One graphical user interface for referrers browsing
is provided as well. Further types can be subclassed.

All types share a similar initialisation. That is, you provide a root object
and may specify further settings such as the initial depth of the tree or an
output function.
Afterwards you can print the tree which will be arranged based on your previous
settings.

The interactive browser is based on a TreeWidget implemented in IDLE. It is
available only if you have Tcl/Tk installed. If you try to instantiate the
interactive browser without having Tkinter installed, an ImportError will be
raised.

"""
import gc
import inspect
import sys

from pympler import muppy
from pympler import summary

from pympler.util.compat import tkinter


class _Node(object):
    """A node as it is used in the tree structure.

    Each node contains the object it represents and a list of children.
    Children can be other nodes or arbitrary other objects. Any object
    in a tree which is not of the type _Node is considered a leaf.

    """
    def __init__(self, o, str_func=None):
        """You have to define the object this node represents. Also you can
        define an output function which will be used to represent this node.
        If no function is defined, the default str representation is used.

        keyword arguments
        str_func -- output function

        """
        self.o = o
        self.children = []
        self.str_func = str_func

    def __str__(self):
        """Override str(self.o) if str_func is defined."""
        if self.str_func is not None:
            return self.str_func(self.o)
        else:
            return str(self.o)


class RefBrowser(object):
    """Base class to other RefBrowser implementations.

    This base class provides means to extract a tree from a given root object
    and holds information on already known objects (to avoid repetition
    if requested).

    """

    def __init__(self, rootobject, maxdepth=3, str_func=summary._repr,
                 repeat=True, stream=None):
        """You have to provide the root object used in the refbrowser.

        keyword arguments
        maxdepth -- maximum depth of the initial tree
        str_func -- function used when calling str(node)
        repeat -- should nodes appear repeatedly in the tree, or should be
                  referred to existing nodes
        stream -- output stream (used in derived classes)

        """
        self.root = rootobject
        self.maxdepth = maxdepth
        self.str_func = str_func
        self.repeat = repeat
        self.stream = stream
        # objects which should be ignored while building the tree
        # e.g. the current frame
        self.ignore = []
        # set of object ids which are already included
        self.already_included = set()
        self.ignore.append(self.already_included)

    def get_tree(self):
        """Get a tree of referrers of the root object."""
        self.ignore.append(inspect.currentframe())
        return self._get_tree(self.root, self.maxdepth)

    def _get_tree(self, root, maxdepth):
        """Workhorse of the get_tree implementation.

        This is a recursive method which is why we have a wrapper method.
        root is the current root object of the tree which should be returned.
        Note that root is not of the type _Node.
        maxdepth defines how much further down the from the root the tree
        should be build.

        """
        objects = gc.get_referrers(root)
        res = _Node(root, self.str_func)
        self.already_included.add(id(root))
        if maxdepth == 0:
            return res
        self.ignore.append(inspect.currentframe())
        self.ignore.append(objects)
        for o in objects:
            # Ignore dict of _Node and RefBrowser objects
            if isinstance(o, dict):
                if any(isinstance(ref, (_Node, RefBrowser))
                       for ref in gc.get_referrers(o)):
                    continue
            _id = id(o)
            if not self.repeat and (_id in self.already_included):
                s = self.str_func(o)
                res.children.append("%s (already included, id %s)" %
                                    (s, _id))
                continue
            if (not isinstance(o, _Node)) and (o not in self.ignore):
                res.children.append(self._get_tree(o, maxdepth - 1))
        return res


class StreamBrowser(RefBrowser):
    """RefBrowser implementation which prints the tree to the console.

    If you don't like the looks, you can change it a little bit.
    The class attributes 'hline', 'vline', 'cross', and 'space' can be
    modified to your needs.

    """
    hline = '-'
    vline = '|'
    cross = '+'
    space = ' '

    def print_tree(self, tree=None):
        """ Print referrers tree to console.

        keyword arguments
        tree -- if not None, the passed tree will be printed. Otherwise it is
        based on the rootobject.

        """
        if tree is None:
            tree = self.get_tree()
        self._print(tree, '', '')

    def _print(self, tree, prefix, carryon):
        """Compute and print a new line of the tree.

        This is a recursive function.

        arguments
        tree -- tree to print
        prefix -- prefix to the current line to print
        carryon -- prefix which is used to carry on the vertical lines

        """
        level = prefix.count(self.cross) + prefix.count(self.vline)
        len_children = 0
        if isinstance(tree, _Node):
            len_children = len(tree.children)

        # add vertex
        prefix += str(tree)
        # and as many spaces as the vertex is long
        carryon += self.space * len(str(tree))
        if (level == self.maxdepth) or (not isinstance(tree, _Node)) or\
           (len_children == 0):
            self.stream.write(prefix + '\n')
            return
        else:
            # add in between connections
            prefix += self.hline
            carryon += self.space
            # if there is more than one branch, add a cross
            if len(tree.children) > 1:
                prefix += self.cross
                carryon += self.vline
            prefix += self.hline
            carryon += self.space

            if len_children > 0:
                # print the first branch (on the same line)
                self._print(tree.children[0], prefix, carryon)
                for b in range(1, len_children):
                    # the carryon becomes the prefix for all following children
                    prefix = carryon[:-2] + self.cross + self.hline
                    # remove the vlines for any children of last branch
                    if b == (len_children - 1):
                        carryon = carryon[:-2] + 2 * self.space
                    self._print(tree.children[b], prefix, carryon)
                    # leave a free line before the next branch
                    if b == (len_children - 1):
                        if len(carryon.strip(' ')) == 0:
                            return
                        self.stream.write(carryon[:-2].rstrip() + '\n')


class ConsoleBrowser(StreamBrowser):
    """RefBrowser that prints to the console (stdout)."""

    def __init__(self, *args, **kwargs):
        super(ConsoleBrowser, self).__init__(*args, **kwargs)
        if not self.stream:
            self.stream = sys.stdout


class FileBrowser(StreamBrowser):
    """RefBrowser implementation which prints the tree to a file."""

    def print_tree(self, filename, tree=None):
        """ Print referrers tree to file (in text format).

        keyword arguments
        tree -- if not None, the passed tree will be printed.

        """
        old_stream = self.stream
        self.stream = open(filename, 'w')
        try:
            super(FileBrowser, self).print_tree(tree=tree)
        finally:
            self.stream.close()
            self.stream = old_stream


# Code for interactive browser (GUI)
# ==================================

# The interactive browser requires Tkinter which is not always available. To
# avoid an import error when loading the module, we encapsulate most of the
# code in the following try-except-block. The InteractiveBrowser itself
# remains outside this block. If you try to instantiate it without having
# Tkinter installed, the import error will be raised.
try:
    if sys.version_info < (3, 5, 2):
        from idlelib import TreeWidget as _TreeWidget
    else:
        from idlelib import tree as _TreeWidget

    class _TreeNode(_TreeWidget.TreeNode):
        """TreeNode used by the InteractiveBrowser.

        Not to be confused with _Node. This one is used in the GUI
        context.

        """
        def reload_referrers(self):
            """Reload all referrers for this _TreeNode."""
            self.item.node = self.item.reftree._get_tree(self.item.node.o, 1)
            self.item._clear_children()
            self.expand()
            self.update()

        def print_object(self):
            """Print object which this _TreeNode represents to console."""
            print(self.item.node.o)

        def drawtext(self):
            """Override drawtext from _TreeWidget.TreeNode.

            This seems to be a good place to add the popup menu.

            """
            _TreeWidget.TreeNode.drawtext(self)
            # create a menu
            menu = tkinter.Menu(self.canvas, tearoff=0)
            menu.add_command(label="reload referrers",
                             command=self.reload_referrers)
            menu.add_command(label="print", command=self.print_object)
            menu.add_separator()
            menu.add_command(label="expand", command=self.expand)
            menu.add_separator()
            # the popup only disappears when to click on it
            menu.add_command(label="Close Popup Menu")

            def do_popup(event):
                menu.post(event.x_root, event.y_root)

            self.label.bind("<Button-3>", do_popup)
            # override, i.e. disable the editing of items

            # disable editing of TreeNodes
            def edit(self, event=None):
                pass  # see comment above

            def edit_finish(self, event=None):
                pass  # see comment above

            def edit_cancel(self, event=None):
                pass  # see comment above

    class _ReferrerTreeItem(_TreeWidget.TreeItem, tkinter.Label):
        """Tree item wrapper around _Node object."""

        def __init__(self, parentwindow, node, reftree):  # constr calls
            """You need to provide the parent window, the node this TreeItem
            represents, as well as the tree (_Node) which the node
            belongs to.

            """
            _TreeWidget.TreeItem.__init__(self)
            tkinter.Label.__init__(self, parentwindow)
            self.node = node
            self.parentwindow = parentwindow
            self.reftree = reftree

        def _clear_children(self):
            """Clear children list from any TreeNode instances.

            Normally these objects are not required for memory profiling, as
            they are part of the profiler.

            """
            new_children = []
            for child in self.node.children:
                if not isinstance(child, _TreeNode):
                    new_children.append(child)
            self.node.children = new_children

        def GetText(self):
            return str(self.node)

        def GetIconName(self):
            """Different icon when object cannot be expanded, i.e. has no
            referrers.

            """
            if not self.IsExpandable():
                return "python"

        def IsExpandable(self):
            """An object is expandable when it is a node which has children and
            is a container object.

            """
            if not isinstance(self.node, _Node):
                return False
            else:
                if len(self.node.children) > 0:
                    return True
                else:
                    return muppy._is_containerobject(self.node.o)

        def GetSubList(self):
            """This method is the point where further referrers are computed.

            Thus, the computation is done on-demand and only when needed.

            """
            sublist = []

            children = self.node.children
            if (len(children) == 0) and\
                    (muppy._is_containerobject(self.node.o)):
                self.node = self.reftree._get_tree(self.node.o, 1)
                self._clear_children()
                children = self.node.children

            for child in children:
                item = _ReferrerTreeItem(self.parentwindow, child,
                                         self.reftree)
                sublist.append(item)
            return sublist

except ImportError:
    _TreeWidget = None


def gui_default_str_function(o):
    """Default str function for InteractiveBrowser."""
    return summary._repr(o) + '(id=%s)' % id(o)


class InteractiveBrowser(RefBrowser):
    """Interactive referrers browser.

    The interactive browser is based on a TreeWidget implemented in IDLE. It is
    available only if you have Tcl/Tk installed. If you try to instantiate the
    interactive browser without having Tkinter installed, an ImportError will
    be raised.

    """
    def __init__(self, rootobject, maxdepth=3,
                 str_func=gui_default_str_function, repeat=True):
        """You have to provide the root object used in the refbrowser.

        keyword arguments
        maxdepth -- maximum depth of the initial tree
        str_func -- function used when calling str(node)
        repeat -- should nodes appear repeatedly in the tree, or should be
                  referred to existing nodes

        """
        if tkinter is None:
            raise ImportError(
                "InteractiveBrowser requires Tkinter to be installed.")
        RefBrowser.__init__(self, rootobject, maxdepth, str_func, repeat)

    def main(self, standalone=False):
        """Create interactive browser window.

        keyword arguments
        standalone -- Set to true, if the browser is not attached to other
        windows

        """
        window = tkinter.Tk()
        sc = _TreeWidget.ScrolledCanvas(window, bg="white",
                                        highlightthickness=0, takefocus=1)
        sc.frame.pack(expand=1, fill="both")
        item = _ReferrerTreeItem(window, self.get_tree(), self)
        node = _TreeNode(sc.canvas, None, item)
        node.expand()
        if standalone:
            window.mainloop()


# list to hold to referrers
superlist = []
root = "root"
for i in range(3):
    tmp = [root]
    superlist.append(tmp)


def foo(o):
    return str(type(o))


def print_sample():
    cb = ConsoleBrowser(root, str_func=foo)
    cb.print_tree()


def write_sample():
    fb = FileBrowser(root, str_func=foo)
    fb.print_tree('sample.txt')


if __name__ == "__main__":
    write_sample()