Home Tutorials Standard Library

Standard Library

Python's textwrap Module: Wrapping, Indenting, and Shortening Text

Pyford Notes July 6, 2026 6 min read
Key points
  • textwrap.fill(text, width=N) returns a single re-wrapped string; textwrap.wrap() returns the same content as a list of individual lines.
  • textwrap.dedent() removes whatever whitespace prefix is common to every line — built specifically for cleaning up indented triple-quoted strings in source code.
  • indent(text, prefix) adds a prefix to the start of every line, which is the more correct tool than a manual newline-join loop for adding blockquote markers or comment characters.
  • For repeated calls with the same non-default settings, building one TextWrapper instance and calling .wrap() on it repeatedly avoids re-parsing the same options every time.

wrap() and fill(): breaking long lines

textwrap exists to reflow a block of text to a maximum line width, breaking on whitespace rather than mid-word:

import textwrap

text = "This function validates the input configuration file before the application starts, raising a descriptive error if any required field is missing."

print(textwrap.fill(text, width=40))
This function validates the input
configuration file before the
application starts, raising a
descriptive error if any required
field is missing.

fill() returns one string with embedded newlines, ready to print. wrap() does the identical reflow but returns a list of the individual lines instead — useful when the caller wants to process, count, or further transform each line before joining them, such as prefixing every line with a timestamp or log level.

indent(): adding a prefix to every line

indent() adds a fixed prefix to the start of each line in a multi-line string, skipping blank lines by default:

quoted = textwrap.indent(text_block, "> ")
comment = textwrap.indent(code_snippet, "# ")

The optional predicate argument controls which lines get the prefix — passing a predicate that always returns true forces it onto blank lines too, which the default behaviour deliberately skips (a blank line followed by a lone "> " reads oddly in rendered Markdown, so skipping it is usually what's wanted). Reaching for indent() instead of a manual join-with-prefix loop is shorter and handles the blank-line edge case correctly without extra thought.

dedent(): stripping common leading whitespace

dedent() solves a specific, common problem: a triple-quoted string written indented to match the surrounding code carries that indentation into the string's actual content:

def build_message():
    text = """
    Dear customer,

    Your order has shipped.
    """
    return textwrap.dedent(text).strip()

Without dedent(), every line of that string keeps the four (or more) leading spaces that made the source code itself readable, which is rarely what should end up in an email or log message. dedent() looks at every non-blank line, finds the longest whitespace prefix common to all of them, and removes exactly that much — it won't remove indentation that isn't uniform across every line, so a single misaligned line in the block prevents any dedenting at all, which is worth checking for when it doesn't seem to be working.

shorten(): truncating with a placeholder

shorten() collapses whitespace and truncates to a maximum width, adding a placeholder to signal the cut:

textwrap.shorten("The quick brown fox jumps over the lazy dog", width=20)
# 'The quick brown [...]'

Unlike a plain string slice, shorten() always breaks on a word boundary rather than mid-word, and the placeholder keyword lets the trailing marker be customised or removed. It also collapses any run of whitespace (including newlines) into single spaces first, which makes it a convenient one-call way to turn a multi-line description into a single-line summary for a table or listing page.

TextWrapper for repeated custom settings

The module-level functions like fill() and wrap() are actually thin wrappers that build a temporary TextWrapper instance on every call. When the same non-default options — a custom width, break_long_words=False, a specific subsequent_indent for hanging indentation — are needed repeatedly, constructing one TextWrapper up front and reusing it avoids re-validating the same arguments on every call:

wrapper = textwrap.TextWrapper(width=72, subsequent_indent="    ")
for paragraph in paragraphs:
    print(wrapper.fill(paragraph))

This is the same pattern as building one compiled regular expression object instead of calling re.match() fresh each time — a minor performance detail on small text, but worth doing when wrapping runs inside a loop over many documents. CLI tools that format help text, changelogs, or terminal output at a fixed width are the most common place this whole module ends up used in practice.