Rumpelstiltskin, or, a Question of Frames

A name is an extremely powerful device. From the Grimm Brother’s Rumpelstiltskin to He-Who-Must-Not-Be-Named, literary texts are filled with examples of the power of names.

The same principles hold in the world of computer science. When programming, once you’ve determined the name of an object you wield tremendous power over it. However, names can often be a burden. I can’t fathom how many times I’ve used the following construct:


foo = "To-day do I bake, to-morrow I brew,"
bar = "The day after that the queen's child comes in;"
baz = "And oh! I am glad that nobody knew"
qux = "That the name I am called is Rumpelstiltskin!"
dwarf_song = {'foo': foo,
              'bar': bar,
              'baz': baz,
              'qux': qux}

Creating a dictionary with keys corresponding to the string representation of the variables should be an easier task given the frequency with which I do it. Surely there must be some way to introspect the variable name from the object itself. (There is, and don’t call me Shirley)

In Python, the locals() function returns a dictionary of the local symbols table. Using the environment of previous example, the locals() call would return


{'bar': "The day after that the queen's child comes in;",
'qux': 'That the name I am called is Rumpelstiltskin!',
'baz': 'And oh! I am glad that nobody knew',
'foo': 'To-day do I bake, to-morrow I brew,'}

This is almost what we want! However, callings locals() within a function will ruin our day.


>>> foo = "Can you find me?"
>>> def inner_function():
...    print locals()
...
>>> inner_function()
{}
>>>

In this case, locals() returns {} because the variables in the environment of the calling function (or calling ‘frame’) aren’t visible within inner_function.

Frames to the Rescue

Luckily, we wont be foiled that easily. Python’s inspect module allows us to get information about classes, modules, methods, and objects (among other things). Of particular interest is the inspect.getcurrentframe() call that exposes the current “frame record” or environment. This frame contains (you guessed it!) a dictionary of all local variables. When combined with the inspect.getouterframes(frame) function, we can traverse the stack frames, collecting variables as we go.


import inspect
def make_dict(*params):
    d = {}
    current_frame = inspect.currentframe()
    # reverse the outer frames so we work from the top frame down
    outer_frames = reversed(inspect.getouterframes(current_frame))
    for frame in outer_frames:
        local_vars = frame[0].f_locals
        for p in params:
            for l in local_vars:
                if p == l:
                    d[p] = local_vars[l]
                    break
    # add missing params
    for p in params:
        if p not in d:
            d[p] = None
    return d

Testing our function with the first example, we are triumphant:


>>> dwarf_song = make_dict('foo', 'bar', 'baz', 'qux', 'missing')
>>> dwarf_song
{'baz': 'And oh! I am glad that nobody knew',
'foo': 'To-day do I bake, to-morrow I brew,',
'bar': "The day after that the queen's child comes in;",
'missing': None,
'qux': 'That the name I am called is Rumpelstiltskin!'}