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.
Recommended practices
- 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
TextWrapperfor large batches. - Separate text formatting from visual layout.
Related guides
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.







