Q&A library Python

How do I keep Python code consistently formatted and free of obvious errors?

Python Intermediate formattinglintingruff

Short answer

Use a formatter to standardise layout automatically and a linter to catch likely bugs before running. Running both in a pre-commit hook stops style debates and catches mistakes early.

The short version

Use a formatter to impose a consistent layout automatically, and a linter to flag likely bugs and dead code. ruff does both and is fast; black plus flake8 is the older combination. Wire them into a pre-commit hook so they run before code is committed rather than being remembered by hand.

Formatting

A formatter rewrites your code to a single canonical style, so nobody argues about line breaks or quote characters.

ruff format .

The value is not that the chosen style is optimal; it is that it is decided. Every file looks the same, diffs show only real changes rather than reformatting noise, and code review stops being about whitespace.

Linting

A linter reads the code without running it and reports problems: an unused import, a variable assigned but never used, a bare except, a mutable default argument.

ruff check .

Many of these are exactly the bugs discussed elsewhere in this library. A linter catches the mutable default argument trap automatically, before it ever bites you, which is a strong argument for running one.

Fixing automatically

Many findings can be corrected without human input.

ruff check --fix .

Always review the changes rather than trusting them blindly, but for imports and simple issues this saves a great deal of tedium.

Configuration lives in pyproject.toml

Put the settings with the project so everyone gets the same behaviour.

[tool.ruff]
line-length = 100

[tool.ruff.lint]
select = ["E", "F", "I"]     # errors, pyflakes, import sorting

Keeping this in the project file, not in someone's editor settings, is what makes the standard shared rather than personal.

Run it automatically

A pre-commit hook runs the tools on changed files before each commit, so nothing unformatted or obviously broken gets in.

pip install pre-commit
pre-commit install

with a small .pre-commit-config.yaml naming the tools. The point is that the check is not optional and not something anyone has to remember.

What a linter cannot do

It cannot tell you the code is correct, only that it does not contain certain recognisable mistakes. It will not catch a wrong join, a misread column, or a statistical error. Linting is cheap insurance against a class of small bugs, not a substitute for tests or for thinking.

A type checker on top

For a further layer, mypy checks your type hints and reports mismatches, catching a category of bug that a linter does not. It is worth adding to a codebase that others depend on, once formatting and linting are in place.

The pay-off

Consistent formatting makes code readable and diffs meaningful. Linting removes a family of silly bugs. Together they cost almost nothing to set up and remove a surprising amount of friction from working with other people, which is exactly the sort of thing worth automating once and then forgetting about.

Need this solved on your own data?

The library is free and always will be. When the problem is bigger than an answer, we take on the work.

Start a project