Guide to String Formatting with Python
Table of Contents
String formatting is a robust and powerful part of any python programmer’s toolkit – nearly every piece of production software takes advantage of it in one way or another. The means of formatting strings, though, have greatly evolved over Python’s lifetime. From the %
formatting, to the format()
method, to formatted string literals, there’s no limit as to the potential of string crafting.
Composing the shape and content of strings in runtime is a basic capability of any high-level language. Whether you’re inserting variable values into a log message, composing output based on user input, or constructing messages to a client involving a timestamp – you’ll often need this function. At a basic level, you can concatenate strings simply using +
, but this is inefficient and also hard to make expressive. This is exactly where Python’s string formatting capabilities come in.
You can check out Kite’s Github repository to easily access the code from this post and others from their Python series.
The old way: printf
style formatting.
One of the initial ways of formatting strings in Python was based on the convention used by printf. Inserting a value into a string was done by representing it as a %, followed by a character indicating its type. So, to insert a string and an integer:
>>> this = "this"
>>> five = 5
>>> "%s is a %d" % (this, five)
'this is a 5'
%d
will throw a TypeError if the input is not an integer. %s
is the same as calling str()
on the input. This can be used with any object, not just strings:
>>> "%s is a list" % [1,2,3]
'[1, 2, 3] is a list'
%r
is the same as calling repr()
on the input, contrasting with %s
:
>>> "%s sounds like %r" % ("Seaweed", "Seaweed")
"Seaweed sounds like 'Seaweed'"
With floats, the number of digits displayed can be controlled by inserting that number:
>>> "%.3f" % 6.1234567
'6.123'
Note that when the digits are truncated, the value isn’t rounded. To add padding to strings, add the padding amount like this:
>>> for w in ['some', 'words', 'are', 'longer']:
... print("|%15s" % w)
...
| some
| words
| are
| longer
A dictionary can also be used to insert values into a string:
>>> ship_info = {'ship': 'personiples', 'captain': 'Archaeus'}
>>> "%(ship)s was run hard by %(captain)s" % ship_info
'personiples was run hard by Archaeus'
The main downsides of the printf style is that it’s very easy to introduce bugs, that it can be limited in how many arguments could be passed, and it’s also not as intuitive or expressive as more recent innovations.
Python 3: str.format()
With Python 3, a new way of formatting strings was introduced: the str.format()
method using the format specification mini-language that offered more powerful formatting. This was backported to Python 2.6.
This new specification uses curly brackets to indicate replacement fields rather than %s
In the %
style. The position of the arguments determines the position in the target string, which can be done with the str.format()
style as well:
>>> "{} comes before {}".format('a','b')
'a comes before b'
Now, we can also specify the index to the argument, allowing repeating and changing the order of the original arguments:
>>> "{1} is after {0} which is before {1}".format('a','b')
'b is after a which is before b'
Even more exciting is the ability to access arguments by name:
>>> "{cat} loves {dog}, {dog} loves {cat}".format(cat='Whiskers', dog='Rover')
'Whiskers loves Rover, Rover loves Whiskers'
This mechanism can be used in conjunction with dictionaries:
>>> ship_captains = {'The Irish Rover': 'Mick McCann', 'Davey Crockett': 'Burgess'}
>>> "{Davey Crockett} and {The Irish Rover} are both ship captains".format(**ship_captains)
'Burgess and Mick McCann are both ship captains'
The replacement field can contain any expression, including accessing object attributes:
>>> class Ship:
... def __init__(self, name, masts, captain):
... self.name = name
... self.masts = masts
... self.captain = captain
...
... def __str__(self):
... msg = "{self.name} had {self.masts} masts and was captained by {self.captain}"
... return msg.format(self=self)
...
>>> ships = [ Ship("The Irish Rover", 27, 'Mick McCann'),
... Ship("Davey Crockett", 3, 'Burgess'),
... Ship("The John B", 2, 'Richard Le Gallienne') ]
>>>
>>> for ship in ships:
... print(ship)
...
The Irish Rover had 27 masts and was captained by Mick McCann
Davey Crockett had 3 masts and was captained by Burgess
The John B had 2 masts and was captained by Richard Le Gallienne
Finally, we can add format specification arguments after the field name or index – for example, to align, we can use >
or <
followed by the desired padding:
>>> for ship in ships:
... print("|{ship.name:>22}|{ship.captain:>22}|{ship.masts:>22}|".format(ship=ship))
...
| The Irish Rover| Mick McCann| 27|
| Davey Crockett| Burgess| 3|
| The John B| Richard Le Gallienne| 2|
The New Standard: F
-Strings
While the str.format method is less bug-prone than the printf style, it’s still not the most intuitive framework to use. A much more readable and intuitive solution is the use of f-strings. Formatted string literals, or f-strings, were introduced in Python 3.6 and are an exciting addition to our arsenal. They’re indicated by an f
or F
before the opening quotation mark of a string. They let us use the same replacement fields as with the str.format()
, but with the expressions within those fields in our current environment rather than being passed into the format method.
>>> strings_count = 5
>>> frets_count = 21
>>> f"My banjo has {strings_count} strings and {frets_count} frets"
'My banjo has 5 strings and 21 frets
Here, you can see we’ve referenced the variables strings_count
and frets_count
within the f-string. We can use expressions that access list contents in replacement fields:
>>> arrivals = ['The Irish Rover', 'The Titanic', 'The Rueben']
>>> f'The first to arrive was {arrivals[0]} and the last was {arrivals[-1]}'
'The first to arrive was The Irish Rover and the last was The Rueben'
We follow the expression with a conversion field, represented by the conversion type preceded by a !
. To use the repr()
form in a string, use the conversion field !r
:
>>> ship_name = "Davey Crockett"
>>> f'The ships name was spelled {ship_name!r}'
"The ships name was spelled 'Davey Crockett'"
This is the same as calling repr()
directly:
>>> f'The ships name was spelled {repr(ship_name)}'
"The ships name was spelled 'Davey Crockett'"
There is also a conversion type for the ascii()
function:
>>> check = “√”
>>> f"The ascii version of {check} is {check!a}"
"The ascii version of √ is '√'"
We can also nest fields:
>>> rag_count = 1000000
>>> padding = 10
>>> f'Sligo rags: {rag_count:{padding}d}'
'Sligo rags: 1000000'
In this post, we’ve only just skimmed the surface of some of what can be done with Python string formatting. The power and choices available with different string formatting options gives any python programmer a myriad of options to format strings in the ways you’d like.
With the introduction of F-strings, we have a powerful, intuitive and readable solution. To the python programmer who wants to learn more, I’d suggest looking at the format string mini language documentation and opening a shell to try out f-strings with different format options – they are a really fun and powerful innovation.
About the author: Kennedy Behrman has been programming with Python since 2006. He founded Programmatic AI Labs in 2018 and currently consults on the cloud and data science projects.
This post is a part of Kite’s series on Python fundamentals. You can check out the code from this and other posts on our GitHub repository.