Skip to content
Advertisement

weakref (WeakKeyDictionary) to frame (FrameType) objects

I want to have a dict mapping from active frame (FrameType) objects to some data. Active meaning that it is in the current execution stack trace.

However, holding a reference to the frame object is not a good idea because that would also keep inactive frames and that will blow up the memory very soon because of all the references to all the local variables.

The natural solution would be to use a weakref to the frame objects, or more specifically WeakKeyDictionary.

However, currently it is not possible to create a weakref to a frame object:

import weakref
import sys

f = sys._getframe()
weakref.ref(f)

yields

TypeError: cannot create weak reference to 'frame' object

I assume this is a CPython implementation detail?

So, what are the options?

I could maybe anyway create references but try to clean them up as soon as possible when they are not active anymore. How do I actually check if a frame object is active?

Advertisement

Answer

Here’s a crazy idea: If you can’t hold a reference to the frame, make the frame hold a reference to you.

class FrameDict:
    def __init__(self):
        self._data_by_frame_id = {}
    
    def __setitem__(self, frame, data):
        frame_id = id(frame)
        
        # Make the frame hold a reference to a KeyDeleter, so when the frame
        # dies, the KeyDeleter dies, and the frame is removed from the dict
        deleter = KeyDeleter(self._data_by_frame_id, frame_id)
        frame.f_locals[deleter] = deleter

        self._data_by_frame_id[frame_id] = data
    
    def __getitem__(self, frame):
        frame_id = id(frame)
        return self._data_by_frame_id[frame_id]
        

class KeyDeleter:
    def __init__(self, mapping, key):
        self.mapping = mapping
        self.key = key
    
    def __del__(self):
        del self.mapping[self.key]

Demo:

import inspect

def new_frame():
    return inspect.currentframe()

frame_dict = FrameDict()

frame = new_frame()
frame_dict[frame] = 'foobar'
print(frame_dict[frame])  # foobar

del frame
print(frame_dict._data_by_frame_id)  # {}
User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement