Run this at pythontutor.com to see
 some pointers in python ...

 Jim Mahoney | cs.bennington.college | Dec 2020

class Node:
    """ A node in a graph, with neighbors """
    def __init__(self, name):
        self.name = name
        self.neighbors = []
    def set_neighbor(self, other):
    def __str__(self):
        return "<node '{}' → {} >".format(
            self.name, (n.name for n in self.neighbors))

def make_graph(names=('a', 'b', 'c')):
    """ Return a circular graph. """
    a = Node(names[0])
    b = Node(names[1])
    c = Node(names[2])
    return a

def leaky():
    """ Create a graph ... but don't return it """
    local1 = make_graph(names=('_a', '_b', '_c'))
    # That local variable contains circular references ...
    # so even after we leave this function, the reference
    # counts for the nodes in the graph won't drop to zero,
    # and so it won't be eligible for garbage collection
    # ... which is a memory leak
    return None

def not_leaky():
    local2 = [1,2,3,4,5]
    # That local variable is accessible only from this function.
    # Once we leave the function, there are no references to the list,
    # and so it is eligible for garbage collection.
    return None

def main():
    g1 = make_graph()
    g2 = not_leaky()
    g3 = leaky()
    print("At this point, which allocated memory blocks are reachable?")
    wait = input("End program? ")