Python uses indentation to define blocks, so mixed tabs and spaces can produce errors that are difficult to see. A file may look aligned in one editor but form different levels in another because tab width is configurable. The Python tabnanny module scans files and directories for ambiguous indentation before it causes failures or inconsistent interpretation.
This guide explains command-line usage, recursive project checks, check(), process_tokens(), and a dependable whitespace policy. It complements our articles about Python tokenize, IndentationError, TabError, bytecode with dis, and Python coding practices.
Why tabs and spaces cause problems
A tab character does not represent a fixed number of visual spaces. Editors may display it with a width of two, four, or eight columns. Two lines that appear aligned may contain different whitespace sequences.
if active:
process()
finish()Depending on effective columns, the interpreter may raise TabError, IndentationError, or interpret the blocks differently from the author’s intention.
What tabnanny detects
Tabnanny looks for indentation whose meaning depends on tab width. It is not a formatter and does not rewrite a file. Its purpose is to identify ambiguous lines so the original source can be corrected.
The official tabnanny documentation says the module is primarily intended to be called as a script, although IDEs may import it.
Check one file
Run the module with -m and provide a path.
python -m tabnanny program.pyWhen no problem is found, the command normally prints nothing. An ambiguity produces a diagnostic with the filename, line, and whitespace details.
Check a directory recursively
When the argument is a directory, tabnanny recursively visits its tree and checks .py files.
python -m tabnanny srcDirectories that are symbolic links are not traversed as ordinary directories. Large projects should still choose a narrow root to avoid virtual environments, copied dependencies, and generated output.
Verbose output
The -v option increases progress messages.
python -m tabnanny -v srcRepeating the option can increment internal verbosity. It is useful during local diagnosis, while continuous integration usually benefits from shorter output.
Filename-only mode
The -q option prints only filenames that contain problems.
python -m tabnanny -q srcThis output can feed another script, but it omits line details. Run the check again without -q when fixing the source.
Call check() from Python
tabnanny.check() accepts a file or directory.
import tabnanny
tabnanny.check("src")Diagnostics are written to standard output through print(). The function was not designed as a modern structured API, so callers must redirect output or carefully use lower-level pieces.
Capture output
from contextlib import redirect_stdout
from io import StringIO
import tabnanny
output = StringIO()
with redirect_stdout(output):
tabnanny.check("src")
report = output.getvalue()
print(report)Process-wide stdout redirection is unsafe in a multithreaded server. Concurrent tools should execute tabnanny in a subprocess.
Process tokens directly
process_tokens() accepts tokens produced by tokenize.
import tabnanny
import tokenize
with open("program.py", "rb") as file:
tokens = tokenize.tokenize(file.readline)
tabnanny.process_tokens(tokens)When ambiguity is detected, it raises NannyNag. The higher-level check() function catches this exception and prints a diagnostic.
Catch NannyNag
try:
with open("program.py", "rb") as file:
tokens = tokenize.tokenize(file.readline)
tabnanny.process_tokens(tokens)
except tabnanny.NannyNag as error:
print("Ambiguous indentation:", error)The exception contains information used by the report, but this surface may change. The documentation warns that the programmatic API may not remain backward compatible.
An API that may change
Tabnanny is an older command-oriented utility. Tools that depend on internal details should pin Python versions, maintain compatibility tests, and offer a fallback.
For a product that requires stable structured data, consider invoking python -m tabnanny in a subprocess or implementing a rule over INDENT and DEDENT tokens.
tabnanny versus TabError
TabError is raised when the interpreter encounters inconsistent tabs and spaces during compilation. Tabnanny can inspect an entire source tree without importing or executing its modules.
It therefore acts as a preventive check in commits, builds, and editors.
tabnanny versus IndentationError
IndentationError covers broader issues such as a missing suite, unexpected indentation, or unmatched dedent.
if active:
print("missing indentation")Tabnanny has the narrower purpose of whitespace ambiguity. Projects should also compile or parse source to detect other syntax errors.
Continuous integration
A simple CI job can scan project folders.
python -m tabnanny src testsConfirm the exit-code behavior of the selected Python version. Since the utility is designed around printed diagnostics, a reliable pipeline may wrap it and fail when output is produced.
import subprocess
import sys
result = subprocess.run(
[sys.executable, "-m", "tabnanny", "src"],
capture_output=True,
text=True,
)
if result.stdout.strip() or result.stderr.strip():
print(result.stdout)
print(result.stderr)
raise SystemExit(1)Test the wrapper with a deliberately ambiguous file.
Pre-commit checks
A Git hook can run the check on changed Python files before commit.
python -m tabnanny file1.py file2.pyThe default interface accepts paths, so a wrapper may invoke it once per file. Exclude virtual environments and generated artifacts.
Fix ambiguous whitespace
The safest correction is to convert indentation to spaces, usually four per level, without altering tabs inside strings or data.
Use the editor’s indentation conversion command, inspect the diff, and run tests. A global replacement of every \t may corrupt intentional string content.
Configure the editor
Enable:
- spaces when pressing Tab;
- a visual width of four spaces;
- visible whitespace characters;
- trailing-whitespace cleanup;
- per-file indentation detection.
An .editorconfig file helps share policy among different IDEs.
Formatters and linters
Formatters such as Black generally normalize indentation for valid code. Linters can detect tabs and additional style issues. Tabnanny remains useful because it ships with Python and requires no dependency.
A reasonable order is: detect syntax errors, fix ambiguous whitespace, format, lint, and run tests.
Generated files and dependencies
Do not automatically edit third-party code inside a virtual environment. Exclude directories such as .venv, build, dist, and caches by selecting the correct scan root.
If generated source fails the check, correct the generator rather than only patching its output.
Encoding considerations
Tabnanny relies on tokenize, which respects a UTF-8 BOM and encoding cookies. An encoding problem may appear before indentation analysis.
Use UTF-8 for modern projects and keep fixtures for legacy encodings that must remain supported.
Temporarily incomplete source
While a developer types, a file may contain an unfinished string or open delimiter. Tokenization can raise TokenError. IDEs should debounce analysis or wait for a save.
Do not turn every intermediate state into a persistent warning.
An isolated checker
from pathlib import Path
import subprocess
import sys
def check(path: Path) -> list[str]:
result = subprocess.run(
[sys.executable, "-m", "tabnanny", str(path)],
capture_output=True,
text=True,
timeout=30,
)
lines = result.stdout.splitlines() + result.stderr.splitlines()
return [line for line in lines if line.strip()]The timeout limits unexpected scans of very large trees.
Security
The utility reads source without executing it, which is safer than importing modules. Untrusted paths can still refer to enormous trees, special files, or locations outside an allowed project.
Resolve the permitted root, reject traversal outside it, limit file count and size, and run with minimal permissions.
Common mistakes
- Expecting tabnanny to format the file.
- Scanning an entire virtual environment.
- Redirecting global stdout inside a multithreaded server.
- Depending on internal details without version tests.
- Replacing tabs inside string literals.
- Calling every IndentationError a tab ambiguity.
- Patching generated files instead of the generator.
- Scanning an unbounded path.
Best practices
- Standardize on four spaces.
- Run tabnanny on changed files and in CI.
- Combine it with parsing, formatting, linting, and tests.
- Exclude dependencies and generated artifacts.
- Use a subprocess for concurrent integrations.
- Test compatibility for supported Python releases.
- Fix generators that emit ambiguous whitespace.
- Protect the scan root and limit resources.
Conclusion
The Python tabnanny module detects ambiguous indentation caused by mixing tabs and spaces. It can inspect one file or recursively scan a directory, making it a lightweight check for editors, hooks, and pipelines.
Its scope is specific and its programmatic API may change, but its purpose is clear: find whitespace problems before execution. With a four-space policy, visible whitespace, a formatter, and automated tests, tabnanny helps keep Python blocks predictable in every editor.







