A Python syntax checker validates that your code is grammatically correct Python and will parse before you ever run it, and the fastest one is already built into the interpreter you have installed. If you want to confirm a file has no syntax errors, or you are wiring a gate into CI so broken code cannot merge, you have options ranging from a one-line interpreter check to full linters that also catch style problems and security issues. This guide covers the practical tools, from the simplest Python syntax checker to the ones you will actually run in a pipeline.
Python is interpreted, which means a syntax error in a rarely executed branch can sit undetected until that branch runs in production. A checker turns that runtime surprise into a build-time failure.
The built-in checker: compile without running
You do not need to install anything to check syntax. The interpreter can compile a file without executing it:
python -m py_compile myscript.py
If the file parses, this produces no output and exits with code 0. If there is a syntax error, it prints the error with the line number and exits non-zero, which makes it usable as a CI gate on its own. To check a whole tree at once, the compileall module walks directories:
python -m compileall src/
This is the purest form of a syntax check: it answers only "does this parse?" and nothing about style or logic. It is fast and has zero dependencies, which makes it a reasonable first gate.
There is also a quick way to check a snippet without a file, using the built-in ast module to parse a string:
import ast
try:
ast.parse(source_code)
print("Syntax OK")
except SyntaxError as e:
print(f"Syntax error on line {e.lineno}: {e.msg}")
ast.parse builds the abstract syntax tree without running the code, so it is safe to point at untrusted source for a parse check.
Linters catch more than syntax
A syntax checker tells you the code parses. A linter tells you the code parses and follows sensible rules: unused imports, undefined names, shadowed variables, and stylistic issues that syntax alone permits but that cause bugs.
The tool most teams reach for in 2026 is Ruff, a fast linter that also does formatting and reimplements a large set of checks from older tools:
pip install ruff
ruff check src/
Ruff runs quickly enough to use on every save in an editor and as a pre-commit gate. It reports far more than syntax: it flags a variable used before assignment, an import that is never referenced, and comparison mistakes that are legal syntax but almost certainly bugs.
The older and still widely used combination is flake8 (which wraps pyflakes and pycodestyle) and pylint for deeper analysis. pyflakes in particular is close to a syntax-plus-obvious-errors checker: it will catch an undefined name that py_compile cannot, because a name error is not a syntax error, but it is still something you want to know before shipping.
Type checkers add another layer
Syntax and linting do not verify that you are calling functions with the right argument types. Static type checkers like mypy and pyright do, when your code carries type hints:
pip install mypy
mypy src/
A type checker catches a category of bug that no syntax checker can: passing a string where an integer is expected, or forgetting that a function can return None. These are logic errors, but they surface at analysis time rather than runtime, which is the whole point of the exercise.
Where security scanning enters
Once you are running static analysis in CI, adding a security-focused checker is a small step with a large payoff. bandit scans Python code for common security issues: use of eval, hardcoded passwords, insecure use of subprocess with shell=True, weak cryptographic calls, and similar patterns.
pip install bandit
bandit -r src/
This is static application security testing scoped to your own code. It complements, rather than replaces, dependency scanning, because most exploitable risk in a modern application lives in third-party packages rather than your own source. An SCA tool such as Safeguard covers that dependency dimension, and the two together give you both "is my code safe?" and "are my dependencies safe?". Our academy has a track on building a layered Python security pipeline, and the SCA product page explains the dependency side.
Wiring it into CI and pre-commit
The most effective setup runs the fast checks locally and the full suite in CI. A pre-commit configuration runs Ruff and a syntax check on every commit so problems never reach the shared branch:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.0
hooks:
- id: ruff
- id: ruff-format
In CI, run the same tools plus mypy and bandit as required, blocking gates. The rule of thumb: a syntax or lint failure should fail the build, not produce a warning nobody reads. Advisory-only checks decay into noise within a sprint or two.
Choosing the right tool for the job
If you only need to confirm code parses, python -m py_compile or ast.parse is enough and needs nothing installed. If you want to catch real bugs (undefined names, unused imports), reach for Ruff or pyflakes. If you use type hints, add mypy or pyright. If you care about security, add bandit for your code and dependency scanning for everything you pull in. Most mature projects run all four layers, because each catches a class of problem the others cannot.
FAQ
How do I check Python syntax without running the code?
Use python -m py_compile yourfile.py, which compiles the file without executing it and reports any syntax error with its line number. For a whole directory, use python -m compileall src/. To check a string in code, use ast.parse() inside a try/except for SyntaxError.
What is the difference between a syntax checker and a linter?
A syntax checker only confirms the code parses as valid Python. A linter does that and more: it flags unused imports, undefined names, likely bugs, and style problems that are syntactically legal but undesirable. Ruff and pyflakes are common linters; py_compile is a pure syntax checker.
Is Ruff a good Python syntax checker?
Ruff is more than a syntax checker; it is a fast linter and formatter that catches syntax errors along with a large set of bug and style issues. If you want a single tool for a CI gate, Ruff is a strong default in 2026 because of its speed and broad rule coverage.
Can a syntax checker find security vulnerabilities?
Not by itself. A syntax checker only verifies that code parses. For security issues in your own code, use a tool like bandit, and for vulnerabilities in third-party dependencies, use software composition analysis. Layer them rather than relying on any single one.