AlgoMaster Logo

Code Formatting

Low Priority18 min readUpdated September 21, 2026
Listen to this chapter
Unlock Audio

PEP 8 tells you what well-formatted Python should look like. A code formatter is the program that gets your file there without you thinking about it. This lesson covers the four formatters Python developers commonly use (black, ruff format, autopep8, yapf), how they differ, how to set one up in a project, and the small set of judgment calls every team has to make.

Why Automated Formatting

Formatting by hand has two costs. The first is the time you spend nudging a closing paren onto its own line or rewrapping a long argument list. That's annoying but small. The second is the time the team spends arguing about it in code review. Should this break go before the + or after it? Is 100 columns too wide? Should we keep the trailing comma? Every one of those discussions is a small tax on every pull request, and the outcomes don't make the code any better.

A formatter ends those discussions. You commit a config file, everyone runs the same tool, and every file in the repository follows the same style. The diffs in pull requests stop containing whitespace noise, code review focuses on behavior instead of layout, and onboarding a new contributor takes one less paragraph in the contributing guide.

The formatter sits between "you wrote something" and "anyone else sees it." By the time a reviewer opens the diff, the style question is already settled.

There's a second benefit. Consistent formatting makes reading faster. When every def lines up the same way and every import block has the same structure, you stop spending attention on layout and start spending it on what the code does. Senior Python developers don't pick a formatter because they can't format by hand. They pick one because they'd rather think about something else.

The Major Formatters

Four tools cover almost every Python codebase.

Black

black is the formatter that changed the conversation. Its tagline is "the uncompromising Python code formatter," and the marketing line that gets quoted most often is "any color you like, as long as it's black." The philosophy is deliberate: a tiny number of configurable options, an opinionated style, and a guarantee that every file black produces looks like every other file black produces.

After running black:

black normalizes string quotes to double quotes, fixes the indentation, adds spaces around operators and after commas, wraps the dict body onto multiple lines because the inline version got long, and adds a trailing comma after the last argument so future diffs stay clean.

black's defaults:

  • Line length 88 (you can change this, but most projects keep it).
  • Double-quoted strings.
  • Trailing commas in multi-line constructs.
  • One blank line between methods, two between top-level definitions.

You install and run it:

The command rewrites every .py file under src/ in place. To see what would change without writing anything, use --check:

This is the form you put in CI: it exits with a non-zero code if any file would change, so the build fails when someone forgets to format.

Ruff Format

ruff format is the newest entrant. Ruff started life as a linter (a tool that catches code smells and bugs) written in Rust, designed to be a faster drop-in replacement for flake8. In 2023 the maintainers added a formatter that aims to be a drop-in replacement for black: same default style, same output for nearly every input, but much faster.

The speed difference is real. On a large codebase, ruff format finishes in under a second where black might take 10 or 20. For a single file in your editor that delta is invisible. For a CI step that runs on every push, or for a large monorepo, it adds up.

Ruff also bundles the linter, so a single tool handles both jobs:

For new projects in 2024 and later, ruff is the default recommendation in most Python communities. It's black-compatible, fast, actively maintained, and replaces several tools at once. The one reason to stick with black is inertia: if your project already uses black and the team is happy with it, switching saves nothing.

autopep8

autopep8 was the original Python formatter, around long before black. Its goal was narrower: read the actual rules in PEP 8, and rewrite code to comply with them. Where black is opinionated about everything (including some things PEP 8 doesn't mention), autopep8 only touches what the style guide explicitly covers.

The result is less invasive than black. autopep8 will fix whitespace, indentation, and import order, but it won't rewrap long lines aggressively or normalize string quotes. That's a feature for codebases that care about minimal diffs and a problem for teams that want everything to look the same.

autopep8 shows up in two places today: legacy projects that adopted it years ago and haven't switched, and editor setups where someone wants light-touch PEP 8 fixes without black's full rewrite. It's not a strong choice for a new project.

yapf

yapf (Yet Another Python Formatter) was built at Google and takes a different approach. Instead of a fixed style, it has knobs. You can pick a base style (pep8, google, chromium, facebook) and override individual settings. The result is configurable in a way black deliberately isn't.

yapf made sense in 2017 when projects had strong style preferences and wanted control. It made less sense once black showed that "remove the knobs" can be a feature. Today yapf mostly survives inside Google's own codebases and a few projects that adopted it early. New projects rarely pick it.

Comparison Table

A side-by-side view of the four:

FormatterFirst releasedSpeedConfigurabilityQuotesDefault line lengthTypical use today
black2018FastVery limited (by design)Double88Most open-source Python projects
ruff format2023Very fast (Rust)black-compatibleDouble88New projects, large codebases
autopep82011MediumSome flagsPreserves input79Legacy projects, light touch
yapf2015MediumHighly configurablePreserves input79Google-internal, some legacy

The practical recommendation for new code: pick ruff format if you're starting fresh, black if you want the most well-known option and don't need the speed. The other two only make sense if you have an existing reason to keep them.

Setting Up a Formatter

Once you've picked a formatter, the setup looks the same: a config file in the project root, an editor integration, and a pre-commit hook so nothing slips past CI.

pyproject.toml

pyproject.toml is the standard place for Python project metadata, and it's where modern tools read their config from. A minimal section for ruff:

For black the equivalent is:

target-version matters because it tells the formatter which Python syntax it can emit. If you target Python 3.11, the formatter knows it can use match statements, walrus expressions, and other newer features when wrapping. If you target Python 3.7, it stays compatible with older syntax.

Once the config is committed, running the formatter is a single command:

The tool walks the project, finds every .py file, and reformats them. Run it once on the whole repo to bring everything into line, then commit the result. Future commits stay clean as long as everyone has the editor integration or the pre-commit hook.

Pre-commit Hooks

A pre-commit hook is a script that runs before git commit finishes. The pre-commit framework is the standard way to set them up: a YAML file lists which checks to run, and each developer installs the hooks once.

After committing the file, each developer runs:

From that point, every git commit runs ruff and ruff format on the changed files. If a file isn't formatted, the commit aborts, the formatter rewrites the file, and the developer re-stages and commits again. CI runs the same hooks to catch anything that bypassed them locally.

The shape is the same regardless of which formatter you pick. Edit, stage, commit, the hook either passes or rewrites the file. The rewrite happens before the code leaves your machine, so the version history never contains badly formatted commits.

Editor Integration

Running the formatter on save is the cleanest setup. As soon as you hit save, the file gets reformatted, and the version on disk is always clean.

VS Code. Install the Ruff extension (or the Black extension if you're on black). In settings:

The formatOnSave flag triggers the formatter every time you save. source.organizeImports adds import sorting on save too. Both settings are per-file-type so they only fire for Python.

PyCharm. PyCharm ships with its own formatter built in, and many developers stick with it. To use ruff or black instead, install the BlackConnect plugin (for black) or configure ruff as an external tool. PyCharm's Settings > Tools > Black panel takes care of most setup; for ruff, point an external tool at ruff format $FilePath$ and bind it to a keyboard shortcut.

Neovim, Sublime, etc. Every modern editor has either a built-in LSP client or a plugin that can call out to ruff or black. The setup is always the same shape: tell the editor which command to run, and tell it to run on save.

The end state is that the formatter runs without you invoking it directly. You write code, you save, the file is formatted. The pre-commit hook is a safety net for the rare case where you bypass the editor (committing from the command line, editing a remote file). CI is a second safety net for the rarer case where someone bypasses the pre-commit hook.

Line Length: 79, 88, or 100?

This is the most-discussed setting in any formatter config. The original PEP 8 limit was 79 for code and 72 for comments. The rationale was practical: 80-column terminals, two files fit side-by-side on a small screen, email clients wrap long lines. None of those constraints bind a modern developer the way they did in 2001.

The three numbers in common use:

LimitWhere it comes fromWhen to pick it
79Original PEP 8Standard library, projects that follow the original guide strictly
88black's default since 2018Most modern open-source Python, balanced choice
100Google Python style, many companiesInternal codebases, teams that want longer signatures on one line

The argument for 79 is tradition and screen-friendliness. Two files at 79 columns each fit on a 1080p screen with room for line numbers and a sidebar. Code review tools often show side-by-side diffs that get awkward past 80.

The argument for 88 is the original black reasoning: 10% more horizontal room lets almost every reasonable function signature fit on one line, which keeps code denser without sacrificing readability. On a modern laptop screen at a normal font size, 88 columns is comfortable.

The argument for 100 is that wider monitors are common, that 100 lets even long signatures stay on one line, and that wrapping is itself a small cost (wrapped code is harder to grep, harder to read out loud, and adds vertical space). Google's internal style guide picks 100 for this reason.

The right answer is whatever your team picks, as long as the config file enforces it. Inconsistency between files is the one choice that's clearly wrong.

A longer line limit doesn't mean every line should fill it. The limit is a ceiling, not a target. Lines that go beyond what fits in a glance still hurt readability, regardless of what the config allows.

Formatting Diffs and git blame

One concern that comes up when adopting a formatter on an existing codebase: running the formatter on every file rewrites a lot of lines at once, which clutters git blame. When someone runs git blame to find who wrote a line, they get the formatting commit instead of the original author.

git has a feature for this. You record the formatting commit's hash in a file called .git-blame-ignore-revs:

Then configure git to use it:

GitHub respects this file automatically when you click "Blame" on the website. git blame on the command line uses it once the config is set. The result is that the formatting commit becomes invisible to blame, and the original author shows up as expected.

Without this, teams sometimes delay adopting a formatter because they don't want to lose the blame history. With it, the migration is a one-time commit and the rest of the workflow is unaffected.

Picking a Formatter

A decision tree for most situations:

For a new project in 2024 or later, ruff format is the default recommendation. It's fast, black-compatible, actively maintained, and bundles linting in the same binary so you have one less tool to install.

For an existing project on black, there's no urgent reason to switch. black is stable and well-maintained, and the output is nearly identical to ruff format. Migrate when you have a reason: CI is slow, you want to consolidate tools, or the team is replacing other parts of the toolchain anyway.

For an existing project on autopep8 or yapf, the migration is more involved. The output style differs, so any switch will produce a large formatting diff. Plan it as a dedicated change: format everything in one commit, add the hash to .git-blame-ignore-revs, and update the contributing guide.

What matters most isn't which tool you pick. It's that you pick one, commit the config, set up the pre-commit hook, and stop thinking about it. Formatting is solved. The energy you save is energy you can spend on the parts of the code where judgment matters.

Quiz

Code Formatting Quiz

10 quizzes