Ruby-like string interpolation in Python

I’ve been experimenting with Rails a bit for the past couple weeks. This has been pretty exciting for me, as I’ve generally been much more of a Python developer and I’d never touched Ruby before, so it was very interesting to see a completely different approach to programming and language philosophy.

While the lack of parentheses and many conventions that you “just have to learn” sometimes frustrate me, at least from my first couple of weeks with the language, I’ve seen quite a few features that I like and somewhat wish I had available to me in Python. One of these is string interpolation.

String interpolation: To replace a variable with its value within a string.

Ruby

Ruby implements string interpolation like this:

2.1.1 :001 > stringvar = "I'm a string"
 => "I'm a string"
2.1.1 :002 > numvar = 3
 => 3
2.1.1 :003 > "This is stringvar: #{stringvar}, and this is numvar: #{numvar}"
=> "This is stringvar: I'm a string, and this is numvar: 3"

Note that it only works with double-quotes (") which are “weaker.” If you try it with single-quotes, you’ll get the following:

2.1.1 :004 > puts 'This is stringvar: #{stringvar}, and this is numvar: #{numvar}'
This is stringvar: #{stringvar}, and this is numvar: #{numvar}

My understanding is that you can do this with anything accessible within the local scope, so that would include class or global variables (e.g., ones beginning with @). The flexibility of string interpolation goes even farther, though, as you can actually call methods and perform other arbitrary operations within the interpolation:

2.1.1 :010 > "Upper case: #{stringvar.upcase}"
 => "Upper case: I'M A STRING"
2.1.1 :011 > "Addition: #{numvar + 4}"
 => "Addition: 7"
2.1.1 :012 > def add10(num)
2.1.1 :013?>   num + 10
2.1.1 :014?> end
 => :add10
2.1.1 :015 > "Method call: #{add10 numvar}"
 => "Method call: 13"

Clearly powerful, but could also be somewhat dangerous.

Python

Python, with the philosophy “explicit is better than implicit”, does not have this string interpolation in quite the same way—you need to pass in what you’d like to interpolate explicitly.

The old syntax for this (and still used by many today) is the following:

>>> "This is a string %s, or a number: %d, or a number as a string: %s" % \
...    ('arbitrary string', 3, 39)
'This is a string arbitrary string, or a number: 3, or a number as a string: 39
>>> "Example with formatting: %.4f" % 3
'Example with formatting: 3.0000'
>>> "Named arguments: %(hey)s, %(name)s" % {'hey': 'Hey', 'name': 'Jason'}
'Named arguments: Hey, Jason'

The new style is a .format method called directly on the string:

>>> "Lots of {} to {verb} here, like binary conversion: {num:b}".format(
        'examples', verb='show', num=3
    )
'Lots of examples to show here, like binary conversion: 11'

And you are also able to access objects’ attributes or items, like so:

>>> "Item 1: {0[1]} | Dictionary 'key': {1[key]}".format(['a', 'b'], {'key': 'value'})
"Item 1: b | Dictionary 'key': value"

Note that above, we’re calling the different arguments positionally, so {0} refers to the first positional argument to format and {1} to the second (Python is 0-indexed!), and then we’re accessing internals via brackets.

You can’t call methods:

>>> def add1(num):
...   return num + 1
...
>>> "Test: {add1(test)}".format(test=3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: 'add1(test)'

There are lots of other good examples that can be found in the Python docs here.

Accessing variables in the immediate scope

Unfortunately, you can’t do what Ruby does in terms of accessing any variable or value in the string’s scope. This makes sense from an explicitness perspective (we know exactly what we’re passing to the string at all times), but sometimes it is convenient to not have to worry about that and to be able to type quickly.

The best way I found to do this in Python is to pass in the locals() dictionary, which is a dictionary of the local scope.

>>> a = 1
>>> b = "hey"
>>> "This is a: {a} | This is b: {b}".format(**locals())
'This is a: 1 | This is b: hey'
>>> locals()
{'a': 1, 'b': 'hey', '__builtins__': <module '__builtin__' (built-in)>, '__package__': None, '__name__': '__main__', '__doc__': None}

It’s a bit less “Pythonic,” but ultimately it still gets the job done.

Within classes

This doesn’t really work quite so well in classes and starts to become unwieldy. As you’ll see below, accessing the class instance’s attributes don’t work with locals() because they’re all actually attached to self within the methods.

>>> class Test(object):
...     a = 3
...     def __init__(self):
...         self.b = 'hey'
...         print locals()

>>> t = Test()
{'self': <__main__.Test at 0x10e978e50>}

Ultimately, if we really wanted to, we could still access them within the string this way:

>>> class Test(object):
>>>     a = 3
>>>     b = 'hey'
>>>     def stringinterp(self):
>>>         return "a: {0.a}, b: {0.b}".format(self)

>>> Test().stringintepr()
'a: 3, b: hey'

But at that point, I don’t think it would be very convenient, and would effectively be defeating the purpose of string interpolation anyway.