a brief history
In the beginning there was modulus string formatting.
'Hello %s!' % 'world'
What about supplying multiple arguments?
'%s %s!' % 'Hello', 'world'
To insert multiple arguments into a string make a tuple.
'%s %s!' % ('Hello', 'world')
What if we want to insert a tuple into the format string?
'A tuple looks like this: %s' % ('first', 'second')
Put the tuple into another tuple.
'A tuple looks like this: %s' % (('first', 'second'))
Don't forget the trailing comma!
'A tuple looks like this: %s' % (('first', 'second'),)
Key word arguments can be passed by using a dict instead of a tuple.
'%(greeting)s %(planet)s' % {'planet': 'world', 'greeting': 'Hello'}
What if we want to use both positional and keyword arguments?
The modulus operator doesn't do that...
Then came str.format()
'Hello {}!'.format('world')
Multiple arguments work as expected.
'{} {}!'.format('Hello', 'world')
We can use keyword arguments.
'{greeting} world!'.format(greeting='Good day')
They can even be mixed.
'{greeting} {}!'.format('world', greeting='Hello')
planet = 'world'
f'Hello {planet}!'
f-strings support arbitrary expressions.
users = ['Bob', 'Sally', 'Jon']
f'There are {len(users)} users including {", ".join(users)}.'