CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-objgraph

Draws Python object reference graphs with graphviz

Pending
Overview
Eval results
Files

reference-chains.mddocs/

Reference Chain Analysis

Tools for tracing reference chains between objects to understand how objects are connected, what keeps them alive, and how to reach specific objects. These functions help diagnose memory leaks and understand object relationships.

Capabilities

Forward Reference Chain Finding

Find a path from a starting object to any object matching a predicate by following object references.

def find_ref_chain(obj, predicate, max_depth=20, extra_ignore=()):
    """
    Find a shortest chain of references leading from obj.
    
    Args:
        obj: Starting object for the search
        predicate (callable): Function taking object and returning bool; target match condition
        max_depth (int): Maximum search depth to prevent infinite recursion
        extra_ignore (tuple): Object IDs to exclude from the search
        
    Returns:
        list: Reference chain from obj to matching object, or [obj] if no chain found
        
    Note:
        The end of the chain will be some object that matches your predicate.
        Returns the chain in forward direction (from source to target).
    """

Usage examples:

import objgraph
import sys

# Find path from an object to any module
my_object = [1, 2, 3]
chain = objgraph.find_ref_chain(my_object, lambda x: objgraph.is_proper_module(x))
print(f"Chain length: {len(chain)}")
for i, obj in enumerate(chain):
    print(f"  {i}: {type(obj).__name__} - {repr(obj)[:50]}")

# Find path to a specific class
class MyTarget:
    pass

target = MyTarget()
some_container = {'target': target, 'other': 'data'}

chain = objgraph.find_ref_chain(
    some_container, 
    lambda x: isinstance(x, MyTarget)
)
if len(chain) > 1:
    print("Found path to MyTarget instance")

# Find path to any function
def sample_function():
    pass

namespace = {'func': sample_function, 'data': [1, 2, 3]}
chain = objgraph.find_ref_chain(
    namespace,
    lambda x: callable(x) and hasattr(x, '__name__')
)

# Ignore specific objects in the search
frame = sys._getframe()
chain = objgraph.find_ref_chain(
    my_object,
    lambda x: objgraph.is_proper_module(x),
    extra_ignore=(id(frame), id(locals()))
)

Backward Reference Chain Finding

Find a path from any object matching a predicate to a target object by following back-references.

def find_backref_chain(obj, predicate, max_depth=20, extra_ignore=()):
    """
    Find a shortest chain of references leading to obj.
    
    Args:
        obj: Target object for the search
        predicate (callable): Function taking object and returning bool; source match condition  
        max_depth (int): Maximum search depth to prevent infinite recursion
        extra_ignore (tuple): Object IDs to exclude from the search
        
    Returns:
        list: Reference chain from matching object to obj, or [obj] if no chain found
        
    Note:
        The start of the chain will be some object that matches your predicate.
        Useful for finding what keeps an object alive.
    """

Usage examples:

import objgraph

# Find what module keeps an object alive
my_object = [1, 2, 3]
# Store it in a global for this example
globals()['my_global_list'] = my_object

chain = objgraph.find_backref_chain(my_object, objgraph.is_proper_module)
if len(chain) > 1:
    print(f"Object is reachable from module: {chain[0]}")
    print(f"Chain length: {len(chain)}")
    for i, obj in enumerate(chain):
        print(f"  {i}: {type(obj).__name__}")

# Find what keeps a custom object alive
class MyClass:
    def __init__(self, name):
        self.name = name

obj = MyClass("example")
container = {'my_obj': obj}
root_dict = {'container': container}

# Find path from any dict to our object
chain = objgraph.find_backref_chain(
    obj, 
    lambda x: isinstance(x, dict) and len(x) > 0
)

# Find path from module to object (common for debugging)
import types
chain = objgraph.find_backref_chain(
    obj,
    lambda x: isinstance(x, types.ModuleType)
)

# Ignore current frame to avoid finding temporary references
import sys
chain = objgraph.find_backref_chain(
    obj,
    objgraph.is_proper_module,
    extra_ignore=(id(sys._getframe()), id(locals()))
)

Common Workflows

Memory Leak Investigation

Use reference chains to understand what keeps objects alive when they should be garbage collected.

import objgraph

# Suppose you have objects that should be garbage collected but aren't
class PotentiallyLeakedClass:
    def __init__(self, data):
        self.data = data

# Create some objects
leaked_candidates = [PotentiallyLeakedClass(f"data_{i}") for i in range(5)]

# Clear obvious references
leaked_candidates.clear()

# But suppose objects are still alive - find what keeps them alive
remaining_objects = objgraph.by_type('PotentiallyLeakedClass')
if remaining_objects:
    print(f"Found {len(remaining_objects)} objects that should be gone")
    
    # For each remaining object, find what root keeps it alive
    for obj in remaining_objects[:3]:  # Check first 3
        chain = objgraph.find_backref_chain(obj, objgraph.is_proper_module)
        if len(chain) > 1:
            print(f"Object kept alive by: {chain[0]}")
            print(f"Path length: {len(chain)}")
            # Visualize the chain
            objgraph.show_chain(chain)

Object Reachability Analysis

Understand how objects are connected and reachable from different roots.

import objgraph

# Create a complex object structure
class Node:
    def __init__(self, value):
        self.value = value
        self.children = []
        self.parent = None
    
    def add_child(self, child):
        child.parent = self
        self.children.append(child)

# Create a tree structure
root = Node("root")
child1 = Node("child1") 
child2 = Node("child2")
grandchild = Node("grandchild")

root.add_child(child1)
root.add_child(child2)
child1.add_child(grandchild)

# Find path from grandchild to root
chain = objgraph.find_ref_chain(
    grandchild,
    lambda x: isinstance(x, Node) and x.value == "root"
)

print(f"Path from grandchild to root: {len(chain)} steps")
for i, node in enumerate(chain):
    if isinstance(node, Node):
        print(f"  {i}: Node({node.value})")
    else:
        print(f"  {i}: {type(node).__name__}")

# Find what ultimately keeps grandchild alive
backchain = objgraph.find_backref_chain(grandchild, objgraph.is_proper_module)
print(f"Grandchild kept alive by chain of length: {len(backchain)}")

Custom Predicate Examples

import objgraph
import types

# Find path to any function
chain = objgraph.find_ref_chain(
    some_object,
    lambda x: isinstance(x, types.FunctionType)
)

# Find path to large containers
chain = objgraph.find_ref_chain(
    some_object,
    lambda x: hasattr(x, '__len__') and len(x) > 1000
)

# Find path to objects with specific attributes
chain = objgraph.find_ref_chain(
    some_object,
    lambda x: hasattr(x, 'connection') and hasattr(x, 'execute')
)

# Find path from specific module
import json
chain = objgraph.find_backref_chain(
    target_object,
    lambda x: x is json  # Specifically the json module
)

# Find path from any class definition
chain = objgraph.find_backref_chain(
    instance_object,
    lambda x: isinstance(x, type)  # Any class/type object
)

Integration with Visualization

Reference chains work well with objgraph's visualization functions:

import objgraph

# Find a chain and visualize it
my_object = [1, 2, 3]
chain = objgraph.find_backref_chain(my_object, objgraph.is_proper_module)

if len(chain) > 1:
    # Show the entire chain
    objgraph.show_chain(chain, filename='chain.png')
    
    # Show backrefs from the object with chain context
    objgraph.show_backrefs([my_object], 
                          filter=lambda x: id(x) in {id(obj) for obj in chain},
                          filename='chain_context.png')

Install with Tessl CLI

npx tessl i tessl/pypi-objgraph

docs

graph-visualization.md

growth-analysis.md

index.md

object-discovery.md

object-statistics.md

reference-chains.md

tile.json