Text Wrapping & Formatting (textwrap)
Reviewed & published by Brayan K
The textwrap module reflows plain text to a chosen line width and tidies indentation, turning a long run-on string into neat, readable lines.
Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Whenever you print to a terminal, build a help message, or format an email, textwrap gives you wrapping, indenting, dedenting, and clean truncation in a single line of code.
What You'll Learn in This Lesson
- Wrapping text to a width with fill and wrap
- Choosing a width that reads well
- Adding a prefix to lines with indent
- Stripping shared indentation with dedent
- Truncating cleanly with shorten
- Hanging indents via initial / subsequent_indent
1 fill & wrap: Reflow to a Width
textwrap.fill(text, width=N) breaks text into lines no longer than N characters and returns one ready-to-print string. textwrap.wrap does the same but hands back a list of lines:
import textwrap
text = "Python's textwrap module wraps long paragraphs of text to a chosen width neatly."
# fill -> one string with newlines, ready to print
print(textwrap.fill(text, width=30))
# Python's textwrap module wraps
# long paragraphs of text to a
# chosen width neatly.
# wrap -> a list of separate lines
lines = textwrap.wrap(text, width=30)
print(len(lines)) # 3import textwrap
quote = "Simple is better than complex and complex is better than complicated."
print(textwrap.fill(quote, width=25))
print("---")
lines = textwrap.wrap(quote, width=25)
print("line count:", len(lines))
# ✅ Expected output:
# Simple is better than
# complex and complex is
# better than complicated.
# ---
# line count: 32 indent & dedent
indent(text, prefix) adds a prefix to the start of each non-blank line — great for quoting or block-marking. dedent(text) does the reverse: it removes the common leading whitespace shared by every line, perfect for triple-quoted strings:
import textwrap
notice = "All systems operational.\nNo issues detected."
# Prefix every line (e.g. for a quoted block)
print(textwrap.indent(notice, "> "))
# > All systems operational.
# > No issues detected.
# dedent strips the SHARED leading whitespace
block = """
Step 1: install
Step 2: configure
"""
print(textwrap.dedent(block))
#
# Step 1: install
# Step 2: configurededent only removes the indentation that all lines share, so any deeper, relative indentation inside the block is kept intact.
import textwrap
help_text = """
usage: tool [options]
-h show help
-v verbose mode
"""
# Strip the shared 8-space indent, keep the inner 2-space indent
clean = textwrap.dedent(help_text)
print(clean)
# ✅ Expected output:
#
# usage: tool [options]
# -h show help
# -v verbose mode3 shorten & Hanging Indents
shorten(text, width) truncates on a word boundary and adds a placeholder so the result fits — ideal for previews. And initial_indent / subsequent_indent give you bullet-style "hanging" indents where wrapped lines align under the first:
import textwrap
text = "The quick brown fox jumps over the lazy dog repeatedly all day long"
# shorten truncates on a word boundary with a placeholder
print(textwrap.shorten(text, width=30))
# The quick brown fox [...]
# Custom placeholder
print(textwrap.shorten(text, width=30, placeholder=" ..."))
# The quick brown fox jumps ...
# Hanging indent: first line gets "- ", the rest get " "
para = "Bullet points often need a hanging indent so wrapped lines align under the first."
print(textwrap.fill(para, width=40,
initial_indent="- ",
subsequent_indent=" "))import textwrap
# A preview for a long article title
title = "Ten surprising facts about the deep ocean you never knew"
print(textwrap.shorten(title, width=35))
# A numbered list item with a hanging indent
item = "This is a fairly long list item that needs to wrap nicely under the number."
print(textwrap.fill(item, width=40,
initial_indent="1. ",
subsequent_indent=" "))
# ✅ Expected output:
# Ten surprising facts about [...]
# 1. This is a fairly long list item that
# needs to wrap nicely under the
# number.🎯 YOUR TURN: Fill in the Blanks
Replace each ___ with the right textwrap function so the message wraps to width 20 and each line is quoted with "| ".
import textwrap
msg = "Please remember to save your work before closing the editor."
# 1. Wrap to a 20-char width -> use ___
wrapped = textwrap.___(msg, width=20)
# 2. Prefix every line with "| " -> use ___
quoted = textwrap.___(wrapped, "| ")
print(quoted)
# Expected output:
# | Please remember to
# | save your work
# | before closing the
# | editor.Common Errors
❌ Expecting wrap to return a string
print("X" + textwrap.wrap("hello world", 5))
# TypeError: can only concatenate str (not "list") to str✅ Use fill for a string, or join the list yourself
import textwrap
print(textwrap.fill("hello world", 5)) # a single string❌ dedent does nothing because indentation isn't shared
# The first line has no leading spaces, so nothing is common
block = "no indent\n indented"
print(repr(textwrap.dedent(block))) # unchanged✅ Make sure every line shares the indent (a leading newline helps)
import textwrap
block = "\n line one\n line two\n"
print(textwrap.dedent(block)) # both lines un-indented❌ Using a width too small for shorten's placeholder
textwrap.shorten("hello world", width=3)
# ValueError: placeholder too large for max width✅ Leave room for the placeholder (default " [...]" is 6 chars)
import textwrap
print(textwrap.shorten("hello world", width=10)) # hello [...]🎯 Mini Challenge: Format a Notice Box
Take a long message, wrap it to width 30, then quote every line with "# " to make a tidy comment-style notice block.
import textwrap
message = (
"This release fixes the login bug and improves "
"startup speed. Please update at your earliest convenience."
)
wrapped = textwrap.fill(message, width=30)
notice = textwrap.indent(wrapped, "# ")
print(notice)
# ✅ Expected output:
# # This release fixes the login
# # bug and improves startup
# # speed. Please update at your
# # earliest convenience.📚 Keep Learning
- textwrap — Text wrapping and filling (official docs)
- TextWrapper — reusable wrapper with full options
- String methods — strip, splitlines, and friends
Frequently Asked Questions
Lesson complete — your output looks polished!
You can reflow text with fill and wrap, prefix lines with indent, clean up triple-quoted blocks with dedent, truncate cleanly with shorten, and build hanging indents with initial_indent and subsequent_indent.
Practice quiz
What does textwrap.fill return?
- A list of line strings
- The number of lines
- One wrapped string with newlines, ready to print
- A TextWrapper object
Answer: One wrapped string with newlines, ready to print. fill returns a single string with newlines inserted — it's just ' '.join(wrap(...)).
What does textwrap.wrap return?
- A list of separate line strings
- A single wrapped string
- A dictionary of lines
- None
Answer: A list of separate line strings. wrap returns a list of lines, handy when you want to count or process each line individually.
How is fill related to wrap?
- They are unrelated
- wrap is fill.split()
- fill ignores the width argument
- fill is ' '.join(wrap(...))
Answer: fill is ' '.join(wrap(...)). fill is exactly ' '.join(wrap(...)) — same wrapping work, but it returns a printable string.
What does textwrap.indent(text, prefix) do?
- Removes leading whitespace
- Adds the prefix to the start of each non-blank line
- Wraps text to a width
- Truncates text
Answer: Adds the prefix to the start of each non-blank line. indent prepends the given prefix (e.g. '> ') to every non-blank line, great for quoting blocks.
What does textwrap.dedent remove?
- The common leading whitespace shared by every line
- All whitespace
- Trailing whitespace only
- Blank lines
Answer: The common leading whitespace shared by every line. dedent strips only the indentation that ALL lines share, preserving any deeper relative indentation.
What does textwrap.shorten do that simple slicing does not?
- It pads the text to the width
- It reverses the text
- It truncates on a word boundary and adds a placeholder
- It encodes the text to bytes
Answer: It truncates on a word boundary and adds a placeholder. shorten collapses whitespace, truncates at a word boundary, and appends a placeholder (default ' [...]').
What is the default placeholder used by textwrap.shorten?
- '...'
- ' [...]'
- '(more)'
- '~~~'
Answer: ' [...]'. The default placeholder is ' [...]', six characters long, so the width must leave room for it.
What raises a ValueError in textwrap.shorten?
- Passing a list instead of a string
- Using a width over 1000
- Omitting the width
- A width too small to fit even the placeholder
Answer: A width too small to fit even the placeholder. If width is too small for the placeholder, shorten raises 'placeholder too large for max width'.
What do initial_indent and subsequent_indent create together?
- A centered block
- A hanging indent where wrapped lines align under the first
- Double-spaced text
- A numbered list automatically
Answer: A hanging indent where wrapped lines align under the first. initial_indent prefixes the first line and subsequent_indent the rest, producing bullet-style hanging indents.
Why might dedent appear to do nothing on a string?
- The string is too long
- dedent only works on bytes
- Not all lines share a common leading indentation
- The width was not set
Answer: Not all lines share a common leading indentation. dedent removes only shared indentation; if one line (e.g. the first) has no leading spaces, nothing is common to strip.
Continue this course
- Previous: Binary Data with struct
- Next: Benchmarking with timeit — Measure tiny code snippets accurately and compare approaches
- Quick reference: Python cheat sheet
Frequently asked questions
What does the textwrap module do?
textwrap reflows plain text to a chosen line width and handles indentation. textwrap.fill returns one wrapped string, textwrap.wrap returns a list of lines, and helpers like indent, dedent, and shorten clean up leading whitespace and truncate long text. It's perfect for terminal output, help messages, emails, and console reports.
What's the difference between fill and wrap?
They do the same wrapping work but return different shapes. textwrap.wrap returns a list of separate line strings, which is handy when you want to process or count lines. textwrap.fill is just ' '.join(wrap(...)) — a single ready-to-print string. Use fill when you want to print, wrap when you want the individual lines.
When should I use dedent?
Use textwrap.dedent to strip the common leading whitespace from every line of a multi-line string — most often a triple-quoted block that's indented to match your code. It keeps your source readable while producing clean, left-aligned output. It removes only the shared indentation, so relative indentation inside the block is preserved.
How is shorten different from slicing a string?
Slicing like text[:30] can cut a word in half and gives an abrupt ending. textwrap.shorten collapses whitespace, then truncates on a word boundary and appends a placeholder (default ' [...]') so the result fits the width AND reads cleanly. It's the right tool for previews and summaries.