Consider the following code:
import random                                                                   
class Trie:                                                                     
    def __init__(self, children, end):                                          
        self.children = children                                                
        self.end = end                                                          
def trie_empty():                                                               
    return Trie(dict(), False)                                                  
def trie_insert(x, t):                                                          
    if not x:                                                                   
        t.end = True                                                            
        return                                                                  
    try:                                                                        
        t2 = t.children[x[0]]                                                   
    except KeyError:                                                            
        t2 = trie_empty()                                                       
        t.children[x[0]] = t2                                                     
    trie_insert(x[1:], t2)                                                      
def fill_dict(root):                                                            
    memo = dict()                                                               
    def fill(pfx='', depth=0):                                                  
        try:                                                                    
            memo[pfx]                                                           
        except KeyError:                                                        
            pass                                                                
        else:                                                                   
            return                                                              
        if depth > 6:                                                           
            return                                                              
        for ci in range(ord('a'), ord('d') + 1):                                
            fill(pfx + chr(ci), depth + 1)                                      
        bw = None                                                               
        memo[pfx] = None, bw                                                    
    fill()                                                                      
    # del memo                                                                  
def random_word():                                                              
    l = int(random.random() * 10)                                               
    w = ''.join([chr(int(random.random() * 26) + ord('a')) for _ in range(l)])  
    return w                                                                    
def main():                                                                     
    t = trie_empty()                                                            
    for _ in range(10000):                                                      
        trie_insert(random_word(), t)                                           
    while True:                                                                 
        fill_dict(t)                                                            
if __name__ == '__main__':                                                      
    main()
When I run this, it continues to use more memory until I kill it. If I uncomment the del memo, it runs while using a constant amount of memory. From this, I conclude that the local variable memo is not being cleaned up when fill_dict returns.
This behavior is really mysterious to me, especially because basically all of the above code is necessary to see this behavior. even the completely unused argument to fill_dict cannot be omitted for the program to use unbounded memory.
This is really frustrating. Surely a modern, garbage-collected language can clean up its own variables and I shouldn't have to manually delete function-local variables. Even C can clean up the stack when a function returns. Why can't Python (in this situation)?
 
    