views:

92

answers:

4

I frequently find myself using the following pattern for string formatting.

a = 3
b = 'foo'
c = dict(mykey='myval')

#prints a is 3, b is foo, mykey is myval
print('a is {a}, b is {b}, mykey is {c[mykey]}'.format(**vars()))

That is, I often have the values I need to print in the local namespace, represented by a call to vars(). As I look over my code, however, it seems awfully unpythonic to be constantly repeating the .format(**vars()) pattern.

I'd like to create a function that will capture this pattern. It would be something like the following.

# doesn't work
def lfmt(s):
    """
    lfmt (local format) will format the string using variables
    in the caller's local namespace.
    """
    return s.format(**vars())

Except that by the time I'm in the lfmt namespace, vars() is no longer what I want.

How can I write lfmt so that it executes vars() in the caller's namespace such that the following code would work as the example above?

print(lfmt('a is {a}, b is {b}, mykey is {c[mykey]}'))
+1  A: 

You have to inspect the variables from the calling frames.

This will get you started:

import inspect
import pprint

def lfmt(s):
    for frame in inspect.getouterframes(inspect.currentframe()):
        f = frame[0]
        print pprint.pformat(f.f_locals)
    return '???'

if __name__ == '__main__':
    a = 10
    b = 20
    c = 30
    lfmt('test')
lost-theory
This is a good start... now to make it work.
Jason R. Coombs
A: 

Here you are:

import sys

def lfmt(s):
    """
    lfmt (local format) will format the string using variables
    in the caller's local namespace.
    """

    if hasattr(sys, "tracebacklimit") and sys.tracebacklimit == 0:
        raise Exception, "failfailfail"

    try:
        raise ZeroDivisionError
    except ZeroDivisionError:
        f = sys.exc_info()[2].tb_frame.f_back

    return s.format(**f.f_locals)

a = 5
somestring = "text"
print lfmt("{a} {somestring}")

The fact that it works doesn't mean you should use it. This is what developers call "major hack", usually shipped with a comment "XXX fix me XXX".

AndiDog
This is good. And I think there's a cleaner solution that involves the inspect module that lost-theory pointed out.
Jason R. Coombs
+1  A: 

Edit: In order for lfmt to work when called from different namespaces, you'll need the inspect module. Note, as the documentation warns, the inspect module may not be suitable for production code since it may not work with all implementations of Python

import inspect
def lfmt(s):
    caller = inspect.currentframe().f_back
    return s.format(**caller.f_locals)

a = 3
b = 'foo'
c = dict(mykey='myval')

print(lfmt('a is {a}, b is {b}, mykey is {c[mykey]}'))
# a is 3, b is foo, mykey is myval
unutbu
Thanks. That works when the caller is in the global namespace, but not when the caller is in some other scope.
Jason R. Coombs
@Jason: Yes, you are right. I've edited my answer to make lfmt work across namespaces.
unutbu
Excellent. That's almost it. Change f_globals to f_locals and it's exactly what I'm looking for.
Jason R. Coombs
Okay, I'm down with that :)
unutbu
A: 

Is it so bad to type ,vars each time you call the function?

def lfmt(s,v):
    """
    lfmt (local format) will format the string using variables
    from the dict returned by calling v()"""
    return s.format(**v())

print(lfmt('a is {a}, b is {b}, mykey is {c[mykey]}',vars))
gnibbler
It's not _so_ bad, but if I'm going to go through the trouble of creating a function, I'm not going to ask the caller to pass the same thing every time, especially if there's a better way.
Jason R. Coombs