Changing the Indentation of a Multiline String

Credit: Tom Good

Problem

You have a string made up of multiple lines, and you need to build another string from it, adding or removing leading spaces on each line so that the indentation of each line is some absolute number of spaces.

Solution

We don’t need re for this. The string module (or string methods, in Python 2.0 and later) is quite sufficient:

import string
def reindent(s, numSpaces):
    s = string.split(s, '\n')
    s = [(numSpaces * ' ') + string.lstrip(line) for line in s]
    s = string.join(s, '\n')
    return s

Discussion

When working with text, it may be necessary to change the indentation level of a block. This recipe’s code takes a multiline string and adds or removes leading spaces on each line so that the indentation level of each line of the block matches some absolute number of spaces. For example:

>>> x = """line one
... line two
... and line three
... """
>>> print x
line one
line two
and line three

>>> print reindent(x, 8)
        line one
        line two
        and line three

Even if the lines in s are initially indented differently, this recipe makes their indentation homogeneous. This is sometimes what we want, and sometimes not. A frequent need is to adjust the amount of leading spaces in each line, so that the relative indentation of each line in the block is preserved. This is not hard either, for either positive or negative values of the adjustment. However, negative values need a check to ensure that no nonspaces are snipped from the start of the lines. Thus, we may as well split the functionality into two functions to perform the transformations, plus one to measure the number of leading spaces of each line and return the result as a list:

def addSpaces(s, numAdd):
    white = " "*numAdd
    return white + white.join(s.splitlines(1))

def delSpaces(s, numDel):
    def aux(line, numDel=numDel, white=" "*numDel):
        if line[:numDel] != white:
            raise ValueError, "removing more spaces than there are!"
        return line[numDel:]
    return ''.join(map(aux, s.splitlines(1)))

def numSpaces(s):
    return [len(line)-len(line.lstrip()) for line in s.splitlines(  )]

This alternative approach relies on the string method splitlines (and so requires Python 2.0 or later, like any other recipe using string methods and/or list comprehensions), which is similar to a split on '\n', with the extra ability to leave the trailing newline on each line when called with a true argument. This is not often crucial (the last statement in delSpaces, for example, might just as easily return '\n'.join(map(aux, s.split('\n')))), but sometimes it turns out to be (addSpaces could not be quite as short and sweet without this ability of the splitlines string method).

For example, here’s how we can combine these functions to build another function that deletes enough leading spaces from each line to ensure that the least-indented line of the block becomes flush-left, while preserving the relative indentations of all the other lines:

def unIndentBlock(s):
    return delSpaces(s, min(numSpaces(s)))

See Also

The Library Reference section on sequence types.

Get Python Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.