Python errors fall into three distinct categories — syntax, runtime, and logic — and the right tool for each is different. A syntax checker catches structural problems before the interpreter ever runs a line; a traceback explainer decodes the call chain after an exception surfaces; a static analyser like Flake8 or mypy finds bugs that neither approach can see. This guide maps every Python error category to the best tool for catching it, with workflows for local development, the editor, and CI/CD.
Types of Python errors
Every Python error belongs to one of three categories, and knowing which category you are dealing with tells you immediately which tool to reach for. Conflating them leads to spending ten minutes running a syntax checker on a runtime problem, or wiring up a type checker to solve a pure indentation mistake. The categories are distinct at the interpreter level — each one surfaces at a different stage of execution.
Syntax errors and indentation errors
Python raises a `SyntaxError` or `IndentationError` at parse time — before a single byte of bytecode is generated. The interpreter reads the source file, builds an abstract syntax tree, and stops immediately if the structure violates Python grammar. Common triggers: missing colons after `def`, `class`, `if`, or `for`; unclosed parentheses or brackets; mixing tabs and spaces in the same block; or using a reserved keyword as a variable name. The error message includes the file name, line number, and a caret pointing to the unexpected token.
Runtime exceptions
Runtime exceptions are raised during execution when syntactically valid code attempts an illegal operation. The most common: `TypeError` (calling a non-callable, passing wrong argument types), `AttributeError` (accessing a method or attribute that does not exist on an object), `NameError` (referencing a variable that was never assigned), `KeyError` (accessing a dict key that is not present), and `IndexError` (referencing a list position that is out of range). These require a running environment, a stack trace, or careful static analysis to catch.
Logic errors
Logic errors produce the wrong output without raising any exception. An off-by-one in a range, a mutable default argument that accumulates state across calls, a shallow copy where a deep copy was intended — these are invisible to every syntax checker and most static analysers. They are only found by running the code with representative test data, writing unit tests, or reviewing the logic manually.
- SyntaxError: Bad structure — missing colon, unclosed bracket, invalid token. Caught at parse time.
- IndentationError: Inconsistent whitespace — mixed tabs and spaces, or a block indented to an impossible level.
- TypeError: Wrong type — passing a string where a number is expected, calling an integer.
- NameError: Undefined name — referencing a variable before assignment or misspelling a function name.
- AttributeError: Missing attribute — calling `.split()` on an integer, accessing a deleted attribute.
- Logic bug: Wrong output, no exception — requires tests, a debugger, or careful manual review.
Note
Python syntax checkers
A Python syntax checker validates the structure of your code without executing it and reports every place the source violates Python grammar. It is the fastest and safest first check — results in milliseconds, no side effects, and no dependency on having a working Python environment configured locally.
When to use a syntax checker
Syntax checkers earn their keep in four situations: when you receive Python from a third party (generated code, a snippet from documentation, a file from a collaborator), when you are writing Python in an editor without language server support, when you need a quick sanity check on a heavily edited script before committing, and when you are debugging a script that fails to start with no helpful output in the terminal.
Built-in syntax checking with py_compile
Python ships with a built-in syntax checker that requires no additional installation. Run `python -m py_compile yourfile.py` — if the command exits silently, the syntax is valid. If there is a problem, it prints the file name, line number, and error type. For checking multiple files at once, `python -m compileall src/` walks a directory tree and reports every syntax error it finds.
# Check a single file — exits silently if valid
python -m py_compile myscript.py
# Check all .py files in a directory tree
python -m compileall src/
# Verbose output — shows each file checked
python -m compileall -v src/
# Check without writing .pyc bytecode files
python -m compileall -b src/Browser-based syntax checking
The Python Syntax Validator on Quasar Tools runs entirely in your browser. Paste any Python script — regardless of length — and get line-level diagnostics for indentation errors, unmatched tokens, unterminated strings, and structural issues in under a second. Your code is never uploaded to a server, which makes it safe for proprietary scripts, internal tools, and confidential application code.
| Check | Syntax validator | Flake8 | Pylint | mypy |
|---|---|---|---|---|
| Missing colon / bracket | ✓ Yes | ✓ Yes | ✓ Yes | ✓ Yes |
| IndentationError | ✓ Yes | ✓ Yes | ✓ Yes | ✓ Yes |
| Undefined variable (NameError) | ✗ No | ✓ pyflakes | ✓ Yes | ✓ Yes |
| Unused import | ✗ No | ✓ pyflakes | ✓ Yes | ⚠ Partial |
| Type mismatch | ✗ No | ✗ No | ⚠ Partial | ✓ Yes |
| PEP 8 style violations | ✗ No | ✓ pycodestyle | ✓ Yes | ✗ No |
| Logic / wrong output | ✗ No | ✗ No | ✗ No | ✗ No |
Python Syntax Validator
Check Python scripts for syntax and indentation errors instantly — browser-local, line-level diagnostics, no upload required.
Reading Python tracebacks
A Python traceback is the interpreter's record of how execution reached the point where an exception was raised. Reading it efficiently — rather than panicking at the wall of text — is one of the highest-leverage debugging skills in Python. The traceback tells you exactly where the error originated and every function call that led there.
Anatomy of a Python traceback
A traceback begins with the line `Traceback (most recent call last):` and lists frames from the outermost call at the top to the error site at the bottom. Each frame shows the file path, line number, function name, and the line of source code. The very last two lines show the exception class and its message — that is the actual error. Read from the bottom up: understand the error type first, then trace the call chain upward to find where in your code the bad value originated.
Traceback (most recent call last):
File "main.py", line 42, in <module>
result = process_orders(orders) # outer call — your code
File "orders.py", line 17, in process_orders
total = calculate_total(order) # middle call — your code
File "orders.py", line 31, in calculate_total
return sum(item['price'] for item in order['items']) # origin
KeyError: 'items' # error type + messageIn this example, the error is a `KeyError` for the key `'items'`. The origin is line 31 of `orders.py`. The traceback tells you `order` does not have an `'items'` key — either the data structure is different from what was expected, or the key was never set. Go to `orders.py:31`, check what `order` contains at that point, and add a guard or fix the upstream data.
Common Python exception types and their meaning
- KeyError: Accessing a dict key that does not exist — use `.get(key, default)` or check with `key in d` first.
- AttributeError: Calling a method or accessing a property that does not exist on the object — check the object type.
- TypeError: Wrong type passed to a function, or operating on incompatible types (e.g. `"text" + 5`).
- ValueError: Correct type but invalid value — `int("abc")`, `math.sqrt(-1)`, or a function rejecting an out-of-range argument.
- IndexError: List or tuple index out of range — the list is shorter than assumed.
- ImportError / ModuleNotFoundError: A module is not installed or the import path is wrong.
Tip
Python Traceback Explainer
Paste any Python traceback and get a structured breakdown of the origin frame, call chain, and likely fix — browser-local and completely private.
Flake8, Pylint, and static analysis
Static analysis tools read your Python source code without executing it and apply rule sets that catch problems a syntax checker cannot see — undefined names, unused imports, overly complex functions, and dozens of patterns associated with bugs or poor maintainability. Flake8 and Pylint are the two dominant choices, and they serve different points on the speed-vs-depth tradeoff.
Flake8 — fast, composable, PEP 8 enforcer
Flake8 combines three tools: pyflakes (detects undefined names, unused imports, and redefined variables), pycodestyle (enforces PEP 8 style rules — line length, whitespace around operators, blank lines between functions), and mccabe (flags functions with a cyclomatic complexity above a configurable threshold). It runs quickly, produces compact output, and has a rich plugin ecosystem — plugins add security checks (`flake8-bugbear`), type annotation enforcement (`flake8-annotations`), and Django-specific rules (`flake8-django`).
# Install Flake8
pip install flake8
# Check a single file
flake8 mymodule.py
# Check a directory
flake8 src/
# Ignore specific rules (E501 = line too long)
flake8 src/ --extend-ignore=E501
# Set maximum line length
flake8 src/ --max-line-length=100
# Count errors by code
flake8 src/ --statisticsPylint — deep analysis and scoring
Pylint performs deeper static analysis than Flake8. It builds a full understanding of your module structure, tracks variable types across assignments, checks that method signatures match their calls, and enforces a broader set of conventions. It also produces a numeric quality score from 0 to 10 that you can track across commits. The trade-off is speed — Pylint is significantly slower than Flake8 on large codebases — and verbosity: a fresh Pylint run on an unoptimised project can produce hundreds of messages that need triage.
Start with Flake8 for the fast CI feedback loop. Add Pylint selectively for code reviews and pre-release audits. Run mypy continuously if you use type annotations. Three tools, three different depths.
Configuring Flake8 with setup.cfg
Flake8 reads its configuration from `setup.cfg`, `.flake8`, or `tox.ini`. A minimal configuration that sets the line length and ignores a few noisy rules keeps the output actionable without suppressing important warnings.
[flake8]
max-line-length = 100
extend-ignore = E203, W503
exclude =
.git,
__pycache__,
migrations/,
venv/
per-file-ignores =
tests/*: S101Note
Type checking with mypy
Mypy is a static type checker that reads Python type annotations — `def process(items: list[str]) -> int` — and verifies that every function is called with arguments of the correct type and that return values are used appropriately. It does not run your code; it analyses the structure and infers types from the annotations you provide. Type errors caught by mypy cannot become runtime `TypeError` or `AttributeError` exceptions in production.
What mypy catches that Flake8 misses
- Type mismatches: Passing a `str` to a function that expects `int`, or returning `None` from a function typed as `-> str`.
- Optional safety: Calling a method on a value typed as `Optional[User]` without checking for `None` first.
- Incompatible assignments: Assigning a `list[int]` to a variable declared as `list[str]`.
- Missing return paths: A function with a branch that returns nothing when the return type is not `None`.
- Overload mismatches: Calling a function with the wrong combination of argument types for its overloaded signatures.
Getting started with mypy
Mypy can be adopted incrementally — you do not need to annotate every file before seeing value. Start by running `mypy src/` with the `--ignore-missing-imports` flag to suppress errors from third-party libraries that lack type stubs. Focus on annotating public functions, module-level variables, and function return types first. The `reveal_type(expr)` helper (removed at runtime but processed by mypy) shows mypy's inferred type for any expression — useful when you are not sure why a check is failing.
# Install mypy
pip install mypy
# Basic check — report type errors in src/
mypy src/
# Ignore missing stubs for third-party libraries
mypy src/ --ignore-missing-imports
# Strict mode — enables all optional checks
mypy src/ --strict
# Check a single file
mypy orders.py
# Show error codes (useful for targeted suppression)
mypy src/ --show-error-codesTip
Error checking in CI/CD
Manual error checking during development is good practice but not a guarantee. Automating Python error checking in your CI/CD pipeline ensures that no syntax error, Flake8 violation, or type error can merge into the main branch — regardless of whether a developer ran the checks locally.
A minimal Python quality gate
name: Python Quality
on:
pull_request:
paths: ['src/**/*.py', 'tests/**/*.py']
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- run: pip install flake8 mypy
- name: Syntax check
run: python -m compileall src/
- name: Flake8
run: flake8 src/ --max-line-length=100 --statistics
- name: mypy
run: mypy src/ --ignore-missing-importsThe `compileall` step catches any syntax error that would prevent import; Flake8 catches undefined names, unused imports, and style violations; mypy catches type errors. All three steps exit with a non-zero code on failure, which blocks the pull request from merging. Running the checks on `pull_request` rather than `push` to `main` means feedback arrives while the author can still act on it, not after the merge.
Pre-commit hooks for local enforcement
Pre-commit hooks run the same checks locally before a commit is created. The `pre-commit` framework manages this for Python projects — add a `.pre-commit-config.yaml` that references the official Flake8 and mypy hooks, and every contributor gets the same checks applied automatically on commit without manual setup.
| Tool | What it catches | Speed | Privacy | Setup required |
|---|---|---|---|---|
| Python Syntax Validator (Quasar Tools) | Syntax + indentation errors | Instant | ✓ 100% local | None — browser-based |
| python -m py_compile | Syntax errors | Fast | ✓ Local | Python installed |
| Flake8 | Syntax + undefined names + PEP 8 | Fast | ✓ Local | pip install flake8 |
| Pylint | Deep analysis + scoring | Slow | ✓ Local | pip install pylint |
| mypy | Type errors | Medium | ✓ Local | pip install mypy + annotations |
| Python Traceback Explainer | Runtime exception analysis | Instant | ✓ 100% local | None — browser-based |
Warning
Debugging best practices
Good error-checking habits reduce the time spent debugging significantly. These practices work across scripts, Django applications, data pipelines, and any other Python context — the tools change but the principles stay the same.
Fix the first error, not all errors
Python syntax errors cascade — a missing colon on line 10 can produce three separate reported errors as the parser loses context. Always fix the topmost reported error first, then re-run the checker. What looked like five bugs is often one. The same applies to mypy output: a single unannotated function can generate a cascade of downstream type errors, all of which disappear when the one root annotation is added.
Use type annotations from the start
Annotating function signatures as you write them costs negligible time and pays off immediately: your editor's autocomplete becomes accurate, mypy catches misuse at the call site, and documentation is built into the code. Start with public function signatures — parameters and return types — before moving to internal variables. The `from __future__ import annotations` import enables the postponed evaluation syntax that makes annotations forward-compatible with older Python versions.
Validate external data at the boundary
The majority of production `KeyError`, `TypeError`, and `AttributeError` exceptions come from external data — API responses, database query results, user input, or configuration files — that do not match the expected shape. Use Pydantic models or dataclasses to validate incoming data at the boundary, not deep inside business logic. For checking regex patterns used to parse external text, the Python Regex Tester validates your `re` module patterns live against sample input, preventing regex-related runtime exceptions before they reach production.
- Fix the first error first: Syntax errors cascade — one real problem produces multiple reported ones.
- Enable Flake8 in your editor: Real-time feedback catches errors as you type, not after commit.
- Add mypy progressively: Annotate public APIs first; use `--allow-untyped-defs` while migrating.
- Validate external data: API responses and config files must be checked at the boundary, not assumed correct.
- Write tests for critical paths: Unit tests surface logic errors that no static tool can detect.
- Use the Traceback Explainer for unfamiliar errors: Paste any Python traceback to get an instant structured breakdown.
Tip
Key takeaways
- Syntax errors are caught before execution — use the Python Syntax Validator for instant browser-local checking, or `python -m py_compile` for CLI checking with no extra install.
- Tracebacks show the full call chain to the error — read from the bottom up, identify the first frame in your own code, and use the Python Traceback Explainer for a structured breakdown.
- Flake8 combines syntax checking, undefined name detection, and PEP 8 enforcement in one fast tool — the practical default for most Python projects.
- Pylint performs deeper analysis and produces a quality score, making it most valuable for code reviews and pre-release audits rather than every-commit checking.
- Mypy catches type errors before they become runtime exceptions — adopt it progressively starting with public function signatures.
- Add `python -m compileall`, Flake8, and mypy to your CI/CD pipeline so no syntax or type error can merge without being caught.
- Never upload proprietary Python code to server-side online linters — the Quasar Tools Python Syntax Validator and Traceback Explainer process everything entirely in your browser.