The str.expandtabs(tabsize=8) method returns a copy of the string where all tab characters ( ) are replaced by one or more spaces depending on the current column position and the given tab stop width (defaulting to 8).
The Tab Stop Alignment Mechanism: expandtabs() does not blindly substitute a fixed count of spaces for every tab. Instead, it advances the cursor to the next tab stop column (calculated as tabsize - (col % tabsize) spaces), guaranteeing that multi-column tables and code blocks align into clean vertical columns regardless of varying word lengths.
The expandtabs() method accepts an optional integer defining tab stop spacing:
expanded_string = string.expandtabs(tabsize=8)
| Parameter | Type | Description |
|---|---|---|
tabsize |
int |
Optional (default=8). The column distance between tab stops. If omitted, defaults to 8. |
| Return Value | str |
Returns a new string with all characters replaced with spaces up to the next tab stop. |
TypeError |
Exception | Raised when tabsize is not an integer. |
In typewriter and terminal mechanics, tab stops exist at fixed column intervals: 0, tabsize, 2*tabsize, 3*tabsize, …. When Python encounters , it computes:
spaces = tabsize - (current_col % tabsize)
For example, with tabsize=8:
1 (after character 'a'), 8 - (1 % 8) = 7 spaces are added, placing the next character at column 8.4 (after 'item'), 8 - (4 % 8) = 4 spaces are added, placing the next character at column 8.8, 8 - (8 % 8) = 8 spaces are added, advancing to column 16.# Varying prefix lengths align to the exact same column 8:
s1 = "a\tb"
s2 = "ab\tc"
s3 = "abc\td"
s4 = "abcd\te"
print(s1.expandtabs(8))
print(s2.expandtabs(8))
print(s3.expandtabs(8))
print(s4.expandtabs(8))
Output:
a b
ab c
abc d
abcd e
A frequent beginner mistake is replacing tabs with a fixed number of spaces using str.replace(" ", " "). This breaks column alignment because words have different character lengths:
row1 = "ID\tName"
row2 = "1024\tAlex"
# Naive replace introduces column misalignment:
print("Naive replace:")
print(row1.replace("\t", " "))
print(row2.replace("\t", " "))
# expandtabs() perfectly aligns the 'Name' column:
print("\nCorrect expandtabs(8):")
print(row1.expandtabs(8))
print(row2.expandtabs(8))
Output:
Naive replace:
ID Name
1024 Alex
Correct expandtabs(8):
ID Name
1024 Alex
Depending on your formatting needs, you can customize tabsize to match programming indentation (typically 4 spaces) or compact lists:
raw_data = "Item\tPrice\tQty"
# Default tabsize=8
print("tabsize=8:")
print(raw_data.expandtabs())
# Custom tabsize=4
print("\ntabsize=4:")
print(raw_data.expandtabs(4))
# Custom tabsize=12 (wide columns)
print("\ntabsize=12:")
print(raw_data.expandtabs(12))
Output:
tabsize=8:
Item Price Qty
tabsize=4:
Item Price Qty
tabsize=12:
Item Price Qty
When expandtabs() encounters a newline (
) or carriage return (), the internal column counter automatically resets to 0. This guarantees that multi-line tables align identically on each row:
table = "Name\tAge\tRole\nAlice\t28\tEngineer\nBob\t34\tDesigner"
formatted_table = table.expandtabs(12)
print(formatted_table)
Output:
Name Age Role
Alice 28 Engineer
Bob 34 Designer
To fully appreciate how CPython executes expandtabs() under the hood, consider this pure Python reference implementation:
def simulate_expandtabs(text, tabsize=8):
if tabsize <= 0:
return text.replace("\t", "")
result = []
col = 0
for char in text:
if char == "\t":
spaces_to_add = tabsize - (col % tabsize)
result.append(" " * spaces_to_add)
col += spaces_to_add
elif char in ("\n", "\r"):
result.append(char)
col = 0
else:
result.append(char)
col += 1
return "".join(result)
sample = "Col1\tCol2\nLongerCol\tValue"
print("Built-in: \n" + sample.expandtabs(12))
print("Simulated: \n" + simulate_expandtabs(sample, 12))
Output:
Built-in:
Col1 Col2
LongerCol Value
Simulated:
Col1 Col2
LongerCol Value
An important edge case occurs with East Asian Full-Width characters (Chinese, Japanese Kanji, Korean Hangul). In CPython, str.expandtabs() increments the column counter by 1 codepoint per character. However, in terminal monospaced fonts, CJK characters occupy 2 visual character cells (ambiguous or wide display width):
# ASCII characters take 1 column in expandtabs:
ascii_tsv = "Book\t$10\nPen\t$2"
print(ascii_tsv.expandtabs(10))
# CJK characters count as 1 character in expandtabs, but take 2 cells on terminal screens:
cjk_tsv = "本\t$10\n万年筆\t$20"
print(cjk_tsv.expandtabs(10))
Output:
Book $10
Pen $2
本 $10
万年筆 $20
expandtabs() with terminal character cell measurement libraries (such as unicodedata.east_asian_width()) for pixel-perfect console grids.
Python gracefully handles zero and negative tabsize parameters:
tabsize=0: Tabs are completely eliminated (replaced with an empty string "").tabsize < 0: Negative numbers are treated identically to 0, stripping all tabs.tabsize=1: Every tab is replaced by exactly 1 space, collapsing tabs into single spaces.text = "One\tTwo\tThree"
print("tabsize=0: ", repr(text.expandtabs(0))) # 'OneTwoThree'
print("tabsize=-4:", repr(text.expandtabs(-4))) # 'OneTwoThree'
print("tabsize=1: ", repr(text.expandtabs(1))) # 'One Two Three'
Output:
tabsize=0: 'OneTwoThree'
tabsize=-4: 'OneTwoThree'
tabsize=1: 'One Two Three'
When inspecting tab-delimited exports or log files in CLI applications, expandtabs() formats them into neat, readable grids without external dependencies:
def display_tsv(tsv_content, col_width=14):
return tsv_content.expandtabs(col_width)
tsv_data = "Product\tCategory\tCost\tStatus\nLaptop\tHardware\t$999\tIn Stock\nMouse\tAccessories\t$25\tShipped"
print(display_tsv(tsv_data, col_width=16))
Output:
Product Category Cost Status
Laptop Hardware $999 In Stock
Mouse Accessories $25 Shipped
PEP 8 strictly requires 4 spaces per indentation level and forbids mixing tabs with spaces. You can normalize legacy tabbed code:
def pep8_tab_to_spaces(source_code):
return source_code.expandtabs(4)
raw_code = "def process():\n\tx = 10\n\tif x > 5:\n\t\tprint(x)"
clean_code = pep8_tab_to_spaces(raw_code)
print(clean_code)
Output:
def process():
x = 10
if x > 5:
print(x)
The following production script implements a tab-stop calculator, pure-Python simulation checker, and code normalizer with automated self-checking assertions:
class TabularFormatter:
@staticmethod
def calculate_expected_spaces(preceding_chars_len, tabsize=8):
# Formula: tabsize - (col % tabsize)
if tabsize <= 0:
return 0
return tabsize - (preceding_chars_len % tabsize)
@staticmethod
def format_tsv_rows(rows, col_width=12):
# Formats list of tuples into aligned multi-line text
tsv_string = "\n".join("\t".join(str(cell) for cell in row) for row in rows)
return tsv_string.expandtabs(col_width)
# --- Unit Assertions and Verification ---
formatter = TabularFormatter()
# 1. Tab stop spacing calculation checks
assert formatter.calculate_expected_spaces(1, tabsize=8) == 7
assert formatter.calculate_expected_spaces(4, tabsize=8) == 4
assert formatter.calculate_expected_spaces(8, tabsize=8) == 8
assert formatter.calculate_expected_spaces(3, tabsize=4) == 1
assert formatter.calculate_expected_spaces(5, tabsize=0) == 0
# 2. expandtabs() string verification
assert "a\tb".expandtabs(8) == "a b"
assert "abcd\te".expandtabs(8) == "abcd e"
assert "a\tb".expandtabs(4) == "a b"
assert "hello\tworld".expandtabs(0) == "helloworld"
assert "hello\tworld".expandtabs(-5) == "helloworld"
# 3. TSV grid multi-line formatting
rows_data = [
["ID", "Name", "Score"],
[101, "Alice", 95.5],
[102, "Bob", 88.0]
]
grid = formatter.format_tsv_rows(rows_data, col_width=10)
lines = grid.splitlines()
assert len(lines) == 3
assert lines[0].index("Name") == 10
assert lines[1].index("Alice") == 10
print("Formatted TSV Grid:")
print(grid)
print("All str.expandtabs() assertions passed successfully!")
Open in Google Colab View on GitHub
Run all tab stop expansion examples interactively, evaluate custom tab widths, format TSV data grids, and normalize Python source indentation in Google Colab.
Author & Instructor at plus2net
I write and maintain practical tutorials on Python, PHP, SQL, JavaScript, HTML, jQuery, and web development at plus2net. The tutorials focus on clear explanations, working examples, and code that readers can test and adapt while learning.