/dozer/reftree.py

https://bitbucket.org/bbangert/dozer/ · Python · 190 lines · 139 code · 44 blank · 7 comment · 39 complexity · 467e520c9973c50cc98a42efb5dab215 MD5 · raw file

  1. import gc
  2. import sys
  3. from types import FrameType
  4. class Tree(object):
  5. def __init__(self, obj, req):
  6. self.req = req
  7. self.obj = obj
  8. self.filename = sys._getframe().f_code.co_filename
  9. self._ignore = {}
  10. def ignore(self, *objects):
  11. for obj in objects:
  12. self._ignore[id(obj)] = None
  13. def ignore_caller(self):
  14. f = sys._getframe() # = this function
  15. cur = f.f_back # = the function that called us (probably 'walk')
  16. self.ignore(cur, cur.f_builtins, cur.f_locals, cur.f_globals)
  17. caller = f.f_back # = the 'real' caller
  18. self.ignore(caller, caller.f_builtins, caller.f_locals, caller.f_globals)
  19. def walk(self, maxresults=100, maxdepth=None):
  20. """Walk the object tree, ignoring duplicates and circular refs."""
  21. self.seen = {}
  22. self.ignore(self, self.__dict__, self.obj, self.seen, self._ignore)
  23. # Ignore the calling frame, its builtins, globals and locals
  24. self.ignore_caller()
  25. self.maxdepth = maxdepth
  26. count = 0
  27. for result in self._gen(self.obj):
  28. yield result
  29. count += 1
  30. if maxresults and count >= maxresults:
  31. yield 0, 0, "==== Max results reached ===="
  32. raise StopIteration
  33. def print_tree(self, maxresults=100, maxdepth=None):
  34. """Walk the object tree, pretty-printing each branch."""
  35. self.ignore_caller()
  36. for depth, refid, rep in self.walk(maxresults, maxdepth):
  37. print ("%9d" % refid), (" " * depth * 2), rep
  38. def _repr_container(obj):
  39. return "%s of len %s: %r" % (type(obj).__name__, len(obj), obj)
  40. repr_dict = _repr_container
  41. repr_set = _repr_container
  42. repr_list = _repr_container
  43. repr_tuple = _repr_container
  44. def repr_str(obj):
  45. return "%s of len %s: %r" % (type(obj).__name__, len(obj), obj)
  46. repr_unicode = repr_str
  47. def repr_frame(obj):
  48. return "frame from %s line %s" % (obj.f_code.co_filename, obj.f_lineno)
  49. def get_repr(obj, limit=250):
  50. typename = getattr(type(obj), "__name__", None)
  51. handler = globals().get("repr_%s" % typename, repr)
  52. try:
  53. result = handler(obj)
  54. except:
  55. result = "unrepresentable object: %r" % sys.exc_info()[1]
  56. if len(result) > limit:
  57. result = result[:limit] + "..."
  58. return result
  59. class ReferentTree(Tree):
  60. def _gen(self, obj, depth=0):
  61. if self.maxdepth and depth >= self.maxdepth:
  62. yield depth, 0, "---- Max depth reached ----"
  63. raise StopIteration
  64. for ref in gc.get_referents(obj):
  65. if id(ref) in self._ignore:
  66. continue
  67. elif id(ref) in self.seen:
  68. yield depth, id(ref), "!" + get_repr(ref)
  69. continue
  70. else:
  71. self.seen[id(ref)] = None
  72. yield depth, id(ref), get_repr(ref)
  73. for child in self._gen(ref, depth + 1):
  74. yield child
  75. class ReferrerTree(Tree):
  76. def _gen(self, obj, depth=0):
  77. if self.maxdepth and depth >= self.maxdepth:
  78. yield depth, 0, "---- Max depth reached ----"
  79. raise StopIteration
  80. refs = gc.get_referrers(obj)
  81. refiter = iter(refs)
  82. self.ignore(refs, refiter)
  83. for ref in refiter:
  84. # Exclude all frames that are from this module.
  85. if isinstance(ref, FrameType):
  86. if ref.f_code.co_filename == self.filename:
  87. continue
  88. if id(ref) in self._ignore:
  89. continue
  90. elif id(ref) in self.seen:
  91. yield depth, id(ref), "!" + get_repr(ref)
  92. continue
  93. else:
  94. self.seen[id(ref)] = None
  95. yield depth, id(ref), get_repr(ref)
  96. for parent in self._gen(ref, depth + 1):
  97. yield parent
  98. class CircularReferents(Tree):
  99. def walk(self, maxresults=100, maxdepth=None):
  100. """Walk the object tree, showing circular referents."""
  101. self.stops = 0
  102. self.seen = {}
  103. self.ignore(self, self.__dict__, self.seen, self._ignore)
  104. # Ignore the calling frame, its builtins, globals and locals
  105. self.ignore_caller()
  106. self.maxdepth = maxdepth
  107. count = 0
  108. for result in self._gen(self.obj):
  109. yield result
  110. count += 1
  111. if maxresults and count >= maxresults:
  112. yield 0, 0, "==== Max results reached ===="
  113. raise StopIteration
  114. def _gen(self, obj, depth=0, trail=None):
  115. if self.maxdepth and depth >= self.maxdepth:
  116. self.stops += 1
  117. raise StopIteration
  118. if trail is None:
  119. trail = []
  120. for ref in gc.get_referents(obj):
  121. if id(ref) in self._ignore:
  122. continue
  123. elif id(ref) in self.seen:
  124. continue
  125. else:
  126. self.seen[id(ref)] = None
  127. refrepr = get_repr(ref)
  128. if id(ref) == id(self.obj):
  129. yield trail + [refrepr,]
  130. for child in self._gen(ref, depth + 1, trail + [refrepr,]):
  131. yield child
  132. def print_tree(self, maxresults=100, maxdepth=None):
  133. """Walk the object tree, pretty-printing each branch."""
  134. self.ignore_caller()
  135. for trail in self.walk(maxresults, maxdepth):
  136. print trail
  137. if self.stops:
  138. print "%s paths stopped because max depth reached" % self.stops
  139. def count_objects():
  140. d = {}
  141. for obj in gc.get_objects():
  142. objtype = type(obj)
  143. d[objtype] = d.get(objtype, 0) + 1
  144. d = [(v, k) for k, v in d.iteritems()]
  145. d.sort()
  146. return d