Python textwrap: Format Text

Published on: August 10, 2026
Reading time: 4 minutes
Text editor representing formatting with Python textwrap

The textwrap module formats paragraphs for terminals, reports, logs, messages, documentation, and text-based interfaces. It wraps lines to a target width, adds indentation, shortens text by words, and removes common indentation from multiline strings. This replaces fragile custom formatting code with a consistent standard-library API.

This guide covers wrap(), fill(), shorten(), dedent(), indent(), and TextWrapper, including Unicode, long tokens, hyphens, paragraphs, and performance.

Wrapping one paragraph

wrap() accepts one paragraph and returns a list of lines without trailing newline characters.

import textwrap

text = "Python helps developers build clear tools for repetitive tasks."
lines = textwrap.wrap(text, width=24)
print(lines)

The width is measured in Python characters, not pixels or actual terminal columns. Emoji, combining marks, and wide East Asian characters may display differently.

Producing a ready-to-print string

fill() is equivalent to joining wrap() output with newline characters. It is convenient for printing or writing a complete paragraph.

formatted = textwrap.fill(text, width=40)
print(formatted)

Both functions accept the same options: indentation, long-word behavior, hyphens, whitespace normalization, maximum lines, and placeholders.

First-line and hanging indentation

initial_indent applies to the first line. subsequent_indent applies to every remaining line and counts toward the width.

output = textwrap.fill(
    text,
    width=44,
    initial_indent="* ",
    subsequent_indent="  ",
)
print(output)

This works well for CLI lists, plain-text email, terminal help, and reports. Account for the prefix when choosing a line width.

Shortening text by whole words

shorten() collapses whitespace and removes words from the end until the remaining text plus the placeholder fits.

summary = textwrap.shorten(
    "A very long description for a compact interface card",
    width=32,
    placeholder="...",
)
print(summary)

The function avoids arbitrary cuts inside ordinary words. The placeholder itself must fit. If original whitespace must be preserved, use a different policy.

Removing common indentation

Triple-quoted strings inside functions inherit source indentation. dedent() removes the common margin while preserving relative indentation.

def message():
    text = """
        Hello,
          this item remains indented.
        See you soon.
    """
    return textwrap.dedent(text).strip()

Tabs and spaces are both whitespace but are not equivalent indentation. Mixing them may prevent the expected result. Python 3.14 improved normalization of blank lines containing whitespace characters.

Adding prefixes to selected lines

indent() adds a prefix to nonblank lines by default. An optional predicate controls which lines receive it.

block = "first\n\nsecond"
print(textwrap.indent(block, "> "))

all_lines = textwrap.indent(block, "+ ", lambda line: True)

This is useful for quotations, logs, comments, forwarded messages, and code blocks. The predicate receives each line with its line ending when present.

Words longer than the width

By default, break_long_words=True allows splitting a long token to enforce the width. This can be undesirable for URLs, hashes, identifiers, and commands.

output = textwrap.fill(
    "an_extremely_long_identifier_without_spaces",
    width=20,
    break_long_words=False,
)
print(output)

When breaking is disabled, a line may exceed the configured width. Choose whether preserving the token or preserving layout is more important.

Hyphen handling

break_on_hyphens=True allows preferred breaks after hyphens. The behavior follows English-oriented conventions and may not be appropriate for every language or technical identifier.

For truly indivisible tokens, set both break_on_hyphens=False and break_long_words=False.

Tabs and whitespace replacement

expand_tabs=True expands tabs according to tabsize. Then replace_whitespace=True replaces tabs, newlines, vertical tabs, form feeds, and carriage returns with ordinary spaces.

If whitespace replacement is disabled, embedded newlines may produce surprising output. Split the input into paragraphs before wrapping it.

Process one paragraph at a time

wrap() and fill() are designed for a single paragraph. Split longer documents and preserve blank lines explicitly.

def format_document(text, width=70):
    blocks = text.split("\n\n")
    return "\n\n".join(
        textwrap.fill(block, width=width)
        for block in blocks
        if block.strip()
    )

Documents containing lists, headings, tables, or code blocks require a more structured parser.

Limiting output lines

max_lines and placeholder create compact previews.

preview = textwrap.fill(
    long_text,
    width=50,
    max_lines=2,
    placeholder=" [...]",
)

The placeholder participates in width calculation. Test small widths and translations because a longer localized placeholder may not fit.

Reusing TextWrapper

Convenience functions create a wrapper for each call. Reuse a TextWrapper instance when formatting many strings with the same configuration.

wrapper = textwrap.TextWrapper(
    width=60,
    subsequent_indent="  ",
    break_long_words=False,
)

for paragraph in paragraphs:
    print(wrapper.fill(paragraph))

The instance is mutable. Avoid sharing it between threads if options change during use. A fixed wrapper per worker is simpler.

Unicode and display width

textwrap counts Python string characters. Terminals may render CJK characters as two columns, combining accents as zero extra columns, and emoji sequences as one visible symbol.

When exact visual alignment is required, combine a display-width library with a text-wrapping policy. Do not assume len() equals terminal columns.

HTML and Markdown considerations

Wrapping raw HTML or Markdown may split tags, links, tables, and code fences. Parse the document or format only known text nodes.

Browser layout is controlled by CSS and available width, so a fixed character count is not a substitute for responsive design.

Example: command-line help

def help_text(title, description):
    header = textwrap.dedent(f"""
        {title}
        {'=' * len(title)}
    """).strip()
    body = textwrap.fill(
        description,
        width=72,
        initial_indent="  ",
        subsequent_indent="  ",
        break_long_words=False,
    )
    return f"{header}\n{body}"

The example separates header structure from paragraph formatting and preserves long identifiers.

Common mistakes

  • Passing multiple paragraphs as one input.
  • Expecting exact visual width for all Unicode.
  • Breaking URLs and identifiers accidentally.
  • Mixing tabs and spaces before dedent().
  • Expecting shorten() to preserve whitespace.
  • Sharing a mutable wrapper across threads.
  • Formatting HTML or Markdown as plain text.
  • Choose width for a specific output surface.
  • Format paragraphs separately.
  • Preserve long tokens when required.
  • Test localized placeholders.
  • Use dedent().strip() for triple-quoted strings.
  • Reuse TextWrapper for large batches.
  • Separate text formatting from visual layout.

Continue with Python difflib, Python locale, Python pydoc, Python fileinput, and Python linecache.

See the official textwrap documentation and the string documentation.

Conclusion

textwrap provides a complete API for presenting text in width-constrained environments. Correct use requires paragraph-aware processing, an explicit policy for long tokens, and awareness that character count does not always equal visual width.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Folder and magnifying glass representing file-name filters with Python fnmatch
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python fnmatch: Filter File Names

    Learn Python fnmatch to filter file names with shell wildcards, control case sensitivity, exclude patterns, and distinguish glob from regex.

    Ler mais

    Tempo de leitura: 5 minutos
    10/08/2026
    Monitor with binary data representing compact numeric arrays in Python
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python array: Compact Numeric Data

    Learn Python array for compact numeric storage, binary files, byte order, memory views, and safe buffer interoperability.

    Ler mais

    Tempo de leitura: 6 minutos
    10/08/2026
    Color wheel representing RGB, HSV, and HLS conversions with Python colorsys
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python colorsys: RGB, HSV, and HLS

    Learn Python colorsys to convert colors between RGB, HSV, HLS, and YIQ, generate palettes, and avoid scale and precision mistakes.

    Ler mais

    Tempo de leitura: 5 minutos
    09/08/2026
    Configuration icon representing plist files with Python plistlib
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python plistlib: Apple Plist Files

    Learn Python plistlib to read and write XML and binary plist files, validate data, handle dates, bytes, and UIDs safely.

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026
    Digital padlock representing host credentials with Python netrc
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python netrc: Credentials by Host

    Learn Python netrc to read credentials by host, validate permissions, handle parse errors, and integrate network clients safely.

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026
    Digital message representing quoted-printable encoding with Python quopri
    Libraries and Modules
    Foto de perfil de Leandro Hirt da Academify

    Python quopri: Quoted-Printable

    Learn Python quopri to encode and decode quoted-printable data in email, files, and MIME integrations safely.

    Ler mais

    Tempo de leitura: 6 minutos
    08/08/2026