multi line string formatting in python

One way to make multi-line literal strings look good is to use a backslash to escape the newline, like this:

s = '''\
*********************************************************************
                                 hello
*********************************************************************
'''
print(s)

output

*********************************************************************
                                 hello
*********************************************************************

However, PEP-008 discourages backslash usage like that. It’s too fragile: if there’s a space between the backslash and the newline then the newline won’t get escaped, and the backslash will get printed.

A more versatile approach is to use a function which calculates the amount of padding required to centre the text, and applies it via a nested formatting specifier. For example:

def banner(s, width=69):
    stars = '*' * width
    pad = (width + len(s)) // 2
    return '{0}\n{1:>{2}}\n{0}'.format(stars, s, pad)

print(banner('hello'))
print(banner('Hello, world', width=16))

output

*********************************************************************
                                hello
*********************************************************************
****************
  Hello, world
****************


How it works

That format string is a little dense, so I guess I should try to explain it. 😉 For full information on this topic please see Format String Syntax in the docs. The explanation below borrows from & paraphrases those docs.

'{0}\n{1:>{2}}\n{0}'.format(stars, s, pad)

The stuff enclosed in {} in a format string is called a “replacement field”. The first item in a replacement field is the optional field name. This lets us identify which arg of .format goes with this replacement field. There are a couple of possible variations for field names, this format string uses numeric names, so it identifies the .format args by their position. That is, 0 corresponds to stars, 1 corresponds to s and 2 corresponds to pad.

If no field names are given they get automatically filled by the numbers 0, 1, 2, … etc (unless you’re using Python 2.6, where field names are mandatory). That’s quite useful most of the time, so most format strings don’t bother using field names.

After the field name we can give a “format specifier” or “format spec” which describes how the value is to be presented. A colon : separates the field name from the format spec. If you don’t supply a format spec then you get a default one, and most of the time that’s adequate. But here we do want a little more control, so we need to supply a format spec.

In a form spec the > sign forces the field to be right-aligned within the available space. After the alignment sign we can provide a number to specify the minimum field width; the field will automatically be made larger if necessary.

For example, '{0:>6}'.format('test') says to put argument 0 (‘test’) in a space that’s at least 6 chars wide, aligned to the right. Which results in the string ' test'.

But a format spec can actually contain a whole new replacement field! This allows us to supply a variable to control the field width. So in my format string {1:>{2}} says to put arg 1 here (s), right aligned in a field with a width given by arg 2 (pad). Only one level of replacement field nesting is permitted, but it’s hard to think of a situation where you’d actually want deeper nesting.

So putting it all together: '{0}\n{1:>{2}}\n{0}' tells .format to build a string that starts with arg 0 (stars) using the default format spec, followed by a newline, followed by arg 1 (s) right aligned in a field of width pad, followed by another newline, finally followed by arg 0 (stars) again.

I hope that made enough sense. 🙂


In Python 3.6+, we could use an f-string:

def banner(s, width=69):
    stars = '*' * width
    pad = (width + len(s)) // 2
    return f'{stars}\n{s:>{pad}}\n{stars}'

Leave a Comment