skip to content | skip to sidebar

Python string interpolation trick

December 7th, 2008

Today, I will show you a trick that may help you with the heavier string formatting jobs in Python.

Most people using Python know how to format output using string interpolation. It works much like C’s sprintf, except for the funny % operator:

>>> a = "the Knights"
>>> b = "Ni"
>>> "we are %s who say %s" % (a, b)
'we are the Knights who say Ni'

What is less known is that you can replace the right hand argument by a dictionary and use its keys to tell what value goes where. That’s handy when you need to insert more than one or two in a string, or when a single value is used more than once. Especially for localisation strings, it gives you the flexibility to determine the when and where of the values in the string itself.

>>> "we are %(who)s who say %(what)s" % {"who": a, "what": b}
'we are the Knights who say Ni'

Combine that with the even lesser known function vars() which returns a dictionary of the local variables and their values:

>>> vars()
{'__builtins__': <module '__builtin__' (built-in)>, '__name__': 
 '__main__', 'b': 'Ni', '__doc__': None, 'a': 'the Knights'}

Use it at the right hand, and you can simply use the variable names as keywords:

>>> "we are %(a)s who say %(b)s" % vars()
'we are the Knights who say Ni'

This probably is the closest you can get to the Perl and PHP style syntax "we are the $a who say $b".

Share and Enjoy!

2 Responses to “Python string interpolation trick”

  1. luc Says:

    Hoi,

    Used this technique to make a evaluation function for record processing.

    1. write evaluation function as string interpollation, use field names as vars
    2. feed field list
    3. feed value list

    Need to use str() for correct string processing in the eval function…

    def eval_vars(eval_string, fields, rec, echo = False):
    # evaluate expression
    d = {}
    for f, x in zip(fields, rec):
    d[f]=x

    e_string = eval_string % d
    if echo:
    print e_string, eval(e_string)
    try:
    return eval(e_string)
    except:
    return False

    a = [‘POSTCODE’, ‘WAARDE’,’INTEGER’]
    b = [‘9000’ , 45.23, 17]
    c = [‘9315’, 65.2 , 17]
    d = [‘9031’, 62.5 , 23]
    e = [‘9031’, 62.5 , 17]

    eval_vars(‘%(WAARDE)f > 50.0’, a, b, True)
    eval_vars(‘”%(POSTCODE)s”[:2] == str(90)’, a, b, True)
    eval_vars(‘str(%(POSTCODE)s) == str(9000)’, a, b, True)
    eval_vars(‘str(%(POSTCODE)s)[:2] == str(90)’, a, b, True)
    eval_vars(‘%(INTEGER)i == 17’, a, b, True)
    eval_vars(‘%(INTEGER)i == 17 and str(%(POSTCODE)s)[:2] == str(90)’, a, c, True)
    eval_vars(‘(%(INTEGER)i in range(5,16) or %(INTEGER)i in range(20,25)) and str(%(POSTCODE)s)[:2] == str(90)’, a, d, True)
    eval_vars(‘(%(INTEGER)i in range(5,16) or %(INTEGER)i in range(20,25)) and str(%(POSTCODE)s)[:2] == str(90)’, a, e, True)

    I can write any query on any dataset now…

    Feeding a dictionary would be shorter, but loading large datasets as lists saves a lot of time and memory.

    Thanks for the idea….

    Luc

  2. Bramz Says:

    Hi Luc,

    You’re welcome. That’s neat application you’ve shown there. Here’s a few ideas on taking this a little bit further.

    disclaimer: none of the following ideas use string interpolation so it is a bit off-topic =)

    You can get a cleaner syntax in your predicate strings by feeding d directly to the eval function. That way you can use your field names as identifiers in eval_string. Like so:

    def eval_vars(eval_string, fields, rec, echo = False):
    	d = dict(zip(fields, rec))
    	if echo:
    		print eval_string, d, eval(eval_string, d)
    	try:
    		return eval(eval_string, d)
    	except:
    		return False
    
    eval_vars('WAARDE > 50.0', a, b, True)
    eval_vars('POSTCODE[:2] == "90"', a, b, True)
    eval_vars('POSTCODE == "9000"', a, b, True)
    eval_vars('INTEGER == 17', a, b, True)
    eval_vars('INTEGER == 17 and POSTCODE[:2] == "90"', a, c, True)
    eval_vars('(INTEGER in range(5,16) or INTEGER in range(20,25)) '
    	'and POSTCODE[:2] == "90"', a, d, True)
    eval_vars('(INTEGER in range(5,16) or INTEGER in range(20,25)) '
    	'and POSTCODE[:2] == "90"', a, e, True)

    You might notice another small improvement in building d itself. dict accepts a sequence of (key, value) pairs, so you can zip the field names and the valules.

    Another way of doing all this, is using lambda expressions instead of strings. This might be cleaner if you are embedding the predicates in the python code, but you can’t pull them from let’s say a text box anymore. Constructing d becomes somewhat harder, as you must only keep the fields that are really arguments.

    def eval_vars(predicate, fields, rec):
    	arg_names = predicate.__code__.co_varnames[
    		:predicate.__code__.co_argcount]
    	d = dict((k, v) for k, v in zip(fields, rec)
    		if k in arg_names)
    	return predicate(**d)
    
    print eval_vars(lambda WAARDE: WAARDE > 50.0, a, b)
    print eval_vars(lambda POSTCODE: POSTCODE[:2] == "90", a, b)
    print eval_vars(lambda POSTCODE: POSTCODE == "9000", a, b)
    print eval_vars(lambda INTEGER: INTEGER == 17, a, b)
    print eval_vars(lambda INTEGER, POSTCODE: INTEGER == 17 and
    	POSTCODE[:2] == "90", a, c)
    print eval_vars(lambda INTEGER, POSTCODE:
    	(INTEGER in range(5,16) or INTEGER in range(20,25)) and
    	POSTCODE[:2] == "90", a, d)
    print eval_vars(lambda INTEGER, POSTCODE:
    	(INTEGER in range(5,16) or INTEGER in range(20,25)) and
    	POSTCODE[:2] == "90", a, e)

    Cheers,
    Bram