For Python code, the landscape of formatting tools is different from JavaScript’s. Python has its own style guide (PEP 8) and historically relied on manual linting or basic tools to enforce style. However, in recent years auto-formatters for Python have become popular, notably Black. In this section, we’ll compare and evaluate several Python formatting tools:
- Black
- autopep8
- isort
- Ruff
Overview
Python’s dynamic nature and significant whitespace make consistent formatting very important. The venerable style guide, PEP 8, gives recommendations (like 4-space indents, line length ~80, etc.), but different projects historically formatted code differently. To automate PEP 8 compliance and consistency, these tools emerged:
- autopep8: A formatter that walks through your code and fixes deviations from PEP 8. It’s essentially a tool to apply
pep8(pycodestyle) suggestions automatically. It will adjust spacing, line breaks, and so on only where PEP 8 is violated, trying not to alter already-compliant code. It’s configurable to enable or disable certain transformations. Think of autopep8 as “make my code pass PEP 8 rules.” - Black: Branded as “the uncompromising Python code formatter,” Black takes a more aggressive approach. Instead of just fixing PEP 8 violations, Black reformats your entire codebase to a consistent style (which largely adheres to PEP 8, but with some specific choices). Black is opinionated and deliberately offers almost no configuration, to avoid bikeshedding. If you run Black on a file, it will re-layout the code (adding/removing line breaks, adjusting spacing) to fit a standard style, even if the code was already PEP 8 compliant but just formatted differently. It’s similar in spirit to Prettier, but for Python.
- isort: A specialized tool focused only on sorting and grouping import statements in Python. Python imports can get messy across files; isort allows you to define sections (like “standard library imports, third-party imports, first-party imports, local imports”) and will reorder the import lines accordingly, alphabetically within their sections. It can also wrap long import lists, handle
from x import ...statements nicely, and remove unused imports (with additional options). isort complements Black, because Black intentionally does not reorder imports (to avoid changing code behavior). Typically, one runs isort to sort imports and Black to format code. - Ruff: A newer entrant, Ruff is actually more than a formatter – it’s a fast Python linter written in Rust that has recently added formatting capabilities. Ruff’s goal is to be a one-stop tool replacing Flake8 (linting), Black (formatting), isort (import sorting), and more. As a formatter, Ruff aims to be compatible with Black’s style (so you can switch from Black to Ruff without code churn) and extremely fast. It’s an all-in-one tool: by running
ruff, you can both lint (catch errors, stylistic issues) and format the code in one go. Because Ruff’s formatter is new (as of late 2023), it’s basically a reimplementation of Black’s formatting with a few extensions and performance improvements.
Comparison
| Tool | Purpose & Scope | Style Configurability | Speed | Notable Features/Notes |
|---|---|---|---|---|
| Black | Full code formatter (whole file) | Very minimal config (opinionated style; essentially just line length, quote normalization toggle) | Moderate (written in Python, optimized, but slower than Ruff) | Highly popular; ensures uniform style; does not reorder imports or change code meaning. “Uncompromising” – one style for all. |
| autopep8 | PEP 8 compliance fixer (formats only areas not meeting PEP 8) | Many options to enable/disable specific fixes; can preserve a lot of existing formatting | Moderate (Python tool; faster than doing manually, but not as fast as Black/Ruff on large code) | Easy to adopt for incremental cleanup; however, can reorder imports (which may break code). More granular than Black, but less widely used in teams. |
| isort | Import statement sorter/organizer | Highly configurable (custom sections, order, formatting of imports) | Fast (pure Python but narrow scope; handles thousands of imports quickly) | Used alongside Black for complete formatting solution. Has a Black compatibility mode to avoid conflicts. Doesn’t touch other code, only imports. |
| Ruff | Linter + Formatter (Black-compatible) | Some configuration (largely Black-like style by default; a few extra options e.g., select rules, quote style) | Very fast (Rust-based, ~30x faster than Black) | All-in-one tool (replaces Black, isort, Flake8, etc.). Formats code nearly identically to Black (>99.9% same on Black-formatted code. New but rapidly evolving; import sorting is equivalent to isort’s basic behavior. |
As seen above, Black and Ruff take the approach of reformatting the entire file for consistency (with Black being essentially non-configurable, and Ruff aiming to mirror Black’s style with high performance), whereas autopep8 is more of a gentle fixer that only alters what’s necessary to meet PEP 8. isort is a complementary tool rather than either/or; you often “mix and match” isort with one of the formatters.
Now, let’s discuss each tool in a bit more detail, including pros/cons, and then see how to combine them in practice.
autopep8
autopep8 is an older tool (it predates Black) designed to automatically fix issues reported by pycodestyle/PEP8 guidelines. For example, pycodestyle might say “E302: expected 2 blank lines between top-level definitions”. autopep8 can insert those blank lines for you. Or if a line is over 80 characters, autopep8 will try to break it. Essentially, autopep8 is like an auto-correct for PEP 8 compliance.
Pros of autopep8:
- PEP 8 Compliance with Flexibility: autopep8 can be configured to apply or skip specific PEP8 rules. You can fine-tune which kinds of changes it makes. This is useful if, say, you disagree with a certain PEP 8 rule; you can have autopep8 ignore it. Black, by contrast, won’t let you selectively enforce rules – it’s all or nothing. So autopep8 gives a bit more control.
- Preserves Some of Your Formatting: Unlike Black which reformats entire sections, autopep8 tries to not change code that already looks good. It will only change what’s necessary to meet PEP8. In theory, this means less drastic diffs – it won’t re-layout everything, only the parts that violate style. For teams that are slowly adopting formatting or want minimal diff churn, this could be seen as a positive (it’s more incremental).
- Simple Integration: autopep8 can be run via command line or configured in an editor like VSCode (as a formatting provider, though Black has become the recommended one in recent years). It’s a single Python script that can be installed via pip, so pretty easy to integrate in CI or pre-commit.
- Customizable Aggressiveness: autopep8 has an “aggressive” level you can tune (-a, -aa flags) which determine how boldly it refactors code. At the highest levels, it might do things beyond basic PEP8 (like it can sometimes change quotes or other stylistic points). You choose how far to go.
Cons of autopep8:
- May Alter Code Behavior (Import Reordering): One of the biggest criticisms is that autopep8 will reorder imports alphabetically by default (to satisfy PEP8’s recommendation about grouping imports). This can potentially change program behavior in Python. For example, if you were intentionally inserting something into sys.path before importing a module, autopep8 might swap the order of those imports, which could break the code (the example from a blog post showed exactly this scenario). Black explicitly does not do this kind of reordering, to avoid such issues. While import sorting is generally good, doing it blindly as part of formatting can be dangerous. autopep8 can be told not to reorder in specific instances (using
# nopep8comments to disable fixes on a line), but it’s clunky. This behavior is a major reason many prefer Black + isort (so that import sorting is done by a dedicated tool with awareness of side-effects and the ability to customize). - Inconsistent Indentation Handling: autopep8 might not format indentation uniformly in all cases. A cited issue is that if you set a non-standard indent size, autopep8 might not apply it uniformly to continuation lines. Black, conversely, has a fixed indent (4 spaces) everywhere, so you never get mismatched indent levels. In general, because autopep8 is applying targeted fixes, it might leave some style decisions untouched, leading to slightly inconsistent style across the code (only because it didn’t deem them as “violations”). Black, by reformatting everything, ensures a consistent look everywhere.
- Less Opinionated = Less Standardization: autopep8 aims just for PEP8 compliance, which is a baseline, but it doesn’t enforce one true way beyond that. Two codes that are both PEP8-compliant can still have stylistic differences (PEP8 often allows multiple options). Black would make them identical; autopep8 might leave them as-is if both are technically okay. This can result in lingering style differences. For example, if one file uses single quotes and another uses double quotes consistently (both are fine by PEP8), autopep8 won’t change them, so the project has two styles. Black would unify them.
- Popularity and Community: autopep8 is less widely used nowadays compared to Black. That means fewer people are familiar with its quirks and fewer integrations explicitly target it. It’s still maintained, but not with the same energy as Black or Ruff. If you run into an autopep8 issue, you might find less community discussion about it.
- Performance: autopep8 is also pure Python and can be a bit slow on large files (similar ballpark to Black). Performance likely isn’t a distinguishing factor here, but just noting it’s not particularly optimized beyond using pycodestyle’s logic.
In summary, autopep8 can be suitable if you want to enforce PEP8 gradually or allow more configurability. But the risk of it doing things like reordering imports (which can be turned off or mitigated) and its less comprehensive style coverage make it a second choice to Black for many teams. One might use autopep8 in legacy codebases to fix easy issues, then transition to Black for the uniform style.
Example usage: You could configure VSCode to use autopep8 as the formatter ("python.formatting.provider": "autopep8" in settings) and/or run autopep8 --in-place --aggressive . on your codebase. In a pyproject.toml, autopep8 doesn’t use that for config (you’d pass flags or use a setup.cfg/pep8 section to configure it if needed).
Black
Black has become the standard formatter in many Python projects. You can think of Black as doing for Python what Prettier does for JS: it imposes a consistent style and doesn’t let you tweak much. By default, Black uses a line length of 88 characters (a slight deviation from the classic 80, chosen to improve diff readability while still fitting in GitHub review windows). It always uses 4 spaces for indentation (as per PEP 8) and ignores any existing formatting – it “owns” the formatting.
Pros of Black:
- Consistent, Opinionated Style: Black will make all your Python code follow the same conventions. It will, for example, consistently place closing parentheses, break long lists into one item per line if they exceed length, add or remove commas as needed, etc. This consistency extends even to things like string quotes – Black by default will normalize string quotes to double quotes or single quotes depending on what minimises escapes (though in recent versions you can choose to prefer single quotes). The key point: you run Black on all files and you get a uniform style.
- No Code Semantics Changes: Black’s philosophy includes not altering the meaning of code. A notable design choice: it does not sort imports or change your code order. It won’t, for instance, move a line or change anything except whitespace and equivalent syntax (like quotes or adding trailing commas). This means you can trust Black on large codebases – it won’t inadvertently break logic. The creators explicitly leave tasks like import sorting to separate tools like isort. Black also won’t change line breaks inside strings or alter comments.
- Saves Developer Effort: As with any formatter, Black frees you from worrying about minor formatting details. Developers can write code in a somewhat rough format and rely on Black to clean it up. It’s especially great when working in teams – you avoid arguments over formatting and can automatically format before commits or in CI.
- Widely Adopted & Supported: Black is very popular (millions of downloads). Many editors have Black integration (e.g., VS Code’s Python extension can format on save with Black, PyCharm has Black support, etc.). The tooling ecosystem (pre-commit hooks, CI actions, etc.) has plenty of Black hooks. Adopting Black is usually trivial. Many open-source projects require contributions to be Black-formatted.
- Stable, Predictable Output: Black has been around a few years and has refined its formatting style. It’s quite stable – you won’t see massive changes between versions (except some improvements). Once your project is formatted with Black, future Black runs typically only affect lines you change (Black is mostly idempotent; running it twice yields the same output). This stability is important for CI (Black has a
--checkmode to verify formatting, which your code will consistently pass once formatted). - PEP 8 Compliance: Black’s output is PEP 8 compliant by design (except where it intentionally chooses a different standard, like the 88 char line). So you’ll automatically follow the style guide. It also handles tricky formatting like complex nested dicts or list comprehensions in a readable way.
Cons of Black:
- Very Limited Configuration: Black’s motto of being “uncompromising” means you can hardly configure it. If your team doesn’t like a certain stylistic choice Black makes, you’re out of luck (or you write a custom patch, which is not practical). For example, Black always uses double quotes for strings if it can (unless a string contains a double quote, then it uses single quotes to avoid escape). If your project wanted to enforce single quotes for consistency, Black (until recently) wouldn’t allow it. This lack of flexibility can be a turn-off if the mandated style deviates from what the team wants.
- Aggressive Line Wrapping: Black will break lines to ensure code fits within the line length. Sometimes this means a single logical line of code becomes several lines. While usually for the better, there are cases (especially with many nested parentheses or long argument lists) where the code might become a bit harder to read after Black’s reformat due to lots of line breaks. Similar to Prettier’s wrapping issues, this can also create diffs where a small change causes a big rewrap. Some have complained that Black’s output is not always the most compact or human-optimized format, especially around long arithmetic or data structures (Black might choose to put each element on its own line which uses more vertical space).
- Learning Curve for Some Patterns: If developers are used to hand-formatting, they might be surprised by how Black formats certain constructs. For example, Black might transform a one-liner lambda into a multi-line function if it gets too long, or it might move a trailing comma which results in a list being broken into multiple lines. New users might find the changes “weird” until they get accustomed to Black’s style.
- Speed: Black is fairly fast for a Python program (it even caches ASTs to avoid reformatting unchanged files), but on extremely large projects or when formatting hundreds of files at once, it’s not as fast as tools written in lower-level languages (like Ruff). Typically this is not a big issue (formatting usually done on save or in CI), but if you were formatting tens of thousands of files, Black could be slow. (This is where Ruff’s formatter aims to shine.)
In practice, Black’s pros have led to widespread adoption. Its strong stance of not messing with code semantics (no import reordering, etc.) is appreciated because it separates formatting from program behavior concerns (contrast with autopep8 below).
Example Black usage: Add a pyproject.toml with:
1[tool.black]
2line-length = 88
3target-version = ["py39"] # for example, your target Python version(s)and run black . to format your project. Black finds the configuration in pyproject and formats accordingly. There aren’t many more options – by design.
isort
isort specifically targets import statements. In Python, you often want to organize imports into sections:
- Standard library imports (built-in Python modules).
- Third-party imports (installed packages).
- Local application imports (your own modules).
PEP8 suggests grouping and ordering imports, but doesn’t strictly enforce alphabetical order or exact rules. isort fills that gap. It analyzes your imports, figures out which are stdlib vs third-party vs local (by default using heuristics, which can be refined with config), and then sorts them within each section.
Pros of isort:
- Organized Imports: After running isort, all your files will have neat import ordering. This improves readability at the top of files – you can quickly see what external libraries are used versus internal modules. It also makes it easy to spot duplicates (isort by default will combine duplicates) and unused imports (though removal of unused is another tool’s job, isort can work with flake8 or Ruff to remove them).
- Highly Configurable: isort has a plethora of options. You can define custom sections (say, treat anything starting with
myproject.as first-party), whether to sort withinfrom ... import ...statements or leave as-is, whether to add blank lines between certain sections, how to wrap long import lines, etc. You can tailor it to almost any import style. But importantly, isort also provides convenient presets. The most notable preset isprofile = "black"which adjusts isort’s settings to play nicely with Black’s formatting style. For instance, Black likes to put a trailing comma in multi-line imports which forces each import on its own line; isort with profile=black will do the same so that reordering doesn’t conflict with Black. - Integrations: isort can be used as a standalone CLI, but it’s also often included in pre-commit hooks or run inside IDEs (some IDEs have organize imports feature that is similar). It also now integrates somewhat with Ruff (Ruff can perform import sorting in a compatible way, see below). Many projects combine “black + isort” in a single pre-commit configuration so that both run on each commit.
- No Impact on Runtime (if used carefully): isort’s algorithm tries to be smart about not moving imports past each other if they have dependency (though it can’t catch dynamic side effects). Generally, if you stick to the rule “don’t have import side-effects that depend on ordering”, isort is safe. For cautious projects, you can mark certain lines to ignore or configure isort not to sort within a specific block. But typically, isort’s grouping won’t break anything if your imports are just imports.
Cons of isort:
- Single Responsibility (needs combination): isort only handles imports. So you still need a formatter like Black or autopep8 for the rest of the code. This means two tools (and two configurations) to maintain. It’s not too bad, but it’s extra moving parts. If not configured in harmony, Black and isort might “fight” (e.g., Black may wrap an import differently than isort expects, leading isort to make a change, then Black changes it back – causing an infinite ping-pong). This is solved by using
profile = "black"in isort config, as mentioned, to ensure compatibility. - Over-configuration: While configurability is a strength, it can also be a weakness. Teams might bikeshed on the exact ordering rules (Should third-party be sorted before first-party? Should there be two blank lines or one?). If you adopt isort, it’s often best to just use the default or black profile to avoid too much debate. The goal is consistency, not catering to every preference.
- Learning/Remembering to Run It: Without an integrated workflow (like pre-commit), developers might forget to run isort. Unlike a full formatter that also covers imports, isort is a separate step. So it’s crucial to automate it (via pre-commit or an IDE save action). If not, you’ll still get PR comments about import order until it’s automated.
- Complex Cases: If your project does dynamic import magic or conditional imports, isort might not know how to handle those perfectly. You may need to tell it to skip certain blocks. But those are edge cases.
In general, isort’s cons are minor. It’s a very useful tool, and when used with Black, it gives you a completely ordered and formatted codebase (Black handles code layout, isort handles imports).
Example isort usage: In pyproject.toml:
1[tool.isort]
2profile = "black"
3line_length = 88then run isort . to sort imports. The profile = "black" ensures isort wraps and aligns imports in a way Black won’t reformat. Specifically, Black will put a trailing comma if you have a multi-line import with multiple names, forcing each name on its own line. isort with black profile mimics that behavior, so they agree on output.
If not using Black, you can configure isort differently (e.g., set your desired line_length and multi-line output style).
Ruff
Ruff is a very interesting tool because it’s not just a formatter – it’s primarily a linter that happens to also include a formatter now. Created in Rust, it emphasizes performance and convenience by consolidating many Python tooling needs.
For formatting, Ruff’s promise is to give you Black-like formatting (so you don’t need Black) and isort-like import sorting (so you don’t need isort), and to do it super fast. Ruff’s formatter was released in beta around October 2023. By 2025, it’s likely stable and widely used in many projects.
Pros of Ruff:
- Blazing Fast: Ruff is extremely fast. Benchmarks have shown it can format large codebases 30x faster than Black and even 100x faster than older formatters like YAPF. In practical terms, this means formatting happens nearly instantly, even on entire projects. This is great for CI times and for developer experience (instant feedback on save, even if file is large).
- All-in-One (Linter + Formatter): With Ruff, you potentially only need one tool in your Python toolchain. It can replace Flake8 (and dozens of its plugins), isort, Black, even things like pyupgrade and more. For formatting specifically, you run
ruff format(for Black-equivalent formatting) and you can also configure Ruff to enforce import sorting as part of its lint checks (Ruff’s ruleI001covers import order similar to isort’s logic). This means one configuration file (ruff.tomlorpyproject.tomlunder[tool.ruff]) can control both lint and format rules. - Black-Compatible Style: Ruff’s formatter is designed to produce nearly identical output to Black. Tests showed >99.9% compatibility on big projects – meaning if you switch an existing Black-formatted project to Ruff, only a tiny handful of lines would differ (and those differences are usually not stylistically significant or are acceptable deviations). This was a conscious design goal to ease adoption: you don’t have to “reformat” your whole codebase again; Ruff will respect the Black style you likely already use.
- Some Extended Configuration: While Black is rigid, Ruff’s formatter introduced a few optional settings that Black users often requested. For example, Ruff allows configuring whether you want single quotes or double quotes for strings (something Black decided for you). It also might allow different indentation styles or other minor toggles Black doesn’t. So you get Black’s consistency with a little more flexibility if needed. Essentially, Ruff is opinionated by default (Black style) but doesn’t completely shut the door on configuration.
- Unified Toolchain and Simplicity: Using Ruff means fewer dependencies. Instead of installing black, isort, flake8, etc., you install
ruff. This can simplify your dev setup and CI configuration. It also means one less integration point – for example, your pre-commit can just runruff --fixto do lint fixes and format in one go. This reduces the maintenance overhead and potential version compatibility issues between separate tools.
Cons of Ruff:
- Relative Newcomer: As of 2024/2025, Ruff’s formatter is newer than Black, so it hasn’t been tested by time as thoroughly. While it’s gaining trust quickly, some teams might be cautious to fully replace Black immediately. Early versions might have had a few minor formatting bugs (Black has had years to iron those out). However, given Ruff’s rapid development, this is becoming less of an issue.
- Import Sorting Config Limitations: Ruff’s import sorting is designed to be near-equivalent to isort with
profile=black. However, it doesn’t yet support all the intricate configuration that isort does (like custom sections or some of isort’s more advanced settings). If a project has a very custom import order (beyond the standard first-party/third-party/local grouping), Ruff might not be able to replicate that yet. Most projects will be fine (as they often stick to the default grouping), but very custom isort configurations could be a challenge. - All-in-One (Learning Curve): Using Ruff means also adopting it as a linter. This is mostly a pro (one tool), but if a team is used to Flake8 or PyLint, they may need to learn Ruff’s rule codes, etc. It’s quite straightforward (and well-documented) but there’s a minor adjustment. In the context of formatting though, Ruff can be used just as a formatter if desired (you can opt to use its formatter independently of the linter).
- Rapid Changes: Ruff is under active development, adding new lint rules and capabilities quickly. There’s a chance configurations might need updates as features evolve (though they do follow semantic versioning fairly well). With Black, you rarely change config and updates are infrequent. Ruff is adding new things (which can be exciting or just something to keep up with).
On balance, Ruff is very appealing for new projects. Many maintainers are considering or have started migrating to it to simplify their Python toolchain. A blog post from early 2024 describes it as “the ultimate Swiss Army knife” and celebrates dropping Black, Flake8, isort in favor of Ruff. The performance gains in CI (especially on large monorepos) can save minutes on each run. With hardware and cloud time being money, that’s a tangible benefit.
Example Ruff usage: In pyproject.toml:
1[tool.ruff]
2line-length = 88
3select = ["ALL"] # or use default, which is all lint rules except explicitly disabled
4# Optionally, to enable import sorting (if not default):
5extend-select = ["I"] # "I" for isort rulesThen you can use Ruff in two ways:
- As a linter+formatter combo: Run
ruff check . --fix– this will apply autofixes for both lint issues and formatting. If formatting is enabled (it might be auto if you have--fixand the formatter turned on by default in recent versions), it will format the code and also sort imports (with theIrules selected) in one go. - Using the dedicated formatter command: Ruff has
ruff format .which just formats (like Black). You might incorporate this separately. But most likely, you’d use the first method for simplicity.
If you are transitioning from Black+isort, you might do:
- Remove Black and isort from dev dependencies.
- Add Ruff, configure it to cover the same behavior.
- Run Ruff and ensure it doesn’t introduce unexpected changes (should be minimal if configured with Black profile).
- Update documentation to tell devs to use Ruff (and maybe set up an editor plugin like the Ruff VSCode extension which can format on save via Ruff).
Recommended Formatting Tool Combinations for Python
Considering the above tools, here are some best-practice combinations commonly adopted in Python projects:
- Black + isort – The classic duo. Black handles overall code formatting, isort handles import sorting. This combination is very popular and has become a standard in many Python codebases. It gives you fully formatted code. To make them work together smoothly, set isort’s profile to "black" (Black Compatibility - isort), so that isort uses 88 char lines and compatible wrapping. You can run them in either order (some prefer running isort then Black, or vice versa – but if configured correctly, either order yields a fixed point). Many use a pre-commit config where both tools run.
- Pros: Stable, well-supported, predictable. Black is rock-solid for code, isort is excellent for imports. Both are easy to set up. Developers are likely already familiar with Black.
- Cons: Two tools instead of one (slightly more setup). Black’s inflexibility might dissatisfy a few edge cases. Speed is decent but not as fast as Ruff (if that matters).
- Use this if: You want a proven solution with clear separation of concerns. Also if your project is already using Black, adding isort is a minimal incremental improvement.
- Ruff (formatter + linter) – The emerging all-in-one solution. Use Ruff’s formatter (Black style) and import sorting, plus its lint checks. This basically replaces Black + isort + flake8.
- Pros: One config, one tool. Super fast execution, which is especially nice in CI and for large projects. Good default behaviors. Less dependency bloat. As of its current state, Ruff covers nearly all Flake8 rules, does formatting, and even extra fixes (like removing unused imports automatically, which Black/isort wouldn’t do by themselves).
- Cons: Newer tool – small risk of undiscovered formatting edge cases. Might not cover extremely custom import order preferences yet. Team members need to install Ruff (but it’s easy via pip and getting popular).
- Use this if: You are starting a new project or looking to simplify an existing setup. If you highly value performance or just want fewer moving parts. Also if you want the convenience of one tool doing it all (which includes getting things like docstring linting, modern Python syntax upgrades, etc., in addition to formatting).
- Black + isort + Ruff (lint only) – Some projects are choosing a hybrid: use Ruff for linting (because it’s faster and more comprehensive than Flake8) but still use Black for formatting. This is often because Black was already in use and maybe they trust Black’s formatting slightly more at the moment. Ruff is configured to not format (or just not called in format mode). isort might be kept or one could let Ruff handle import sorting (Ruff can apply isort’s logic as part of lint fixes by selecting rule I001).
- This is a transitional setup. Long-term, one might drop Black and let Ruff do both, but doing it in stages can help ensure consistency.
- If you do this, ensure that if Ruff’s import sorting is on, you don’t also run isort to avoid double handling (unless you turn off Ruff’s
Irules). Coordination is key.
- autopep8 (+ isort) – Using autopep8 as an alternative to Black. If for some reason Black’s style or performance is an issue, one might use autopep8 to format mostly PEP8 issues, and still use isort for imports.
- Pros: More configurable, might be less “destructive” on code layout than Black.
- Cons: Not as consistent or foolproof. Risk of import reorder issues unless carefully configured. Less common in modern use.
- Use this if: You have an existing codebase that you want to gradually clean up to PEP8 without completely restyling everything at once. Or if Black’s strictness doesn’t work for you and Ruff is not an option, though that’s a narrow scenario.
- Even in this case, one might consider using Blue (a fork of Black with some more configurability) or YAPF (an older formatter by Google that is more configurable than Black). Those weren’t in the main list, but they exist. However, Black or Ruff are generally better choices today.
Recommendation: For most teams, Black + isort is a safe and effective choice, and Ruff is an excellent newer choice that can simplify your toolchain. If starting fresh in 2025, trying Ruff is highly encouraged given its advantages in speed and consolidation. If sticking with a well-known path, Black+isort will serve you very well.
Example Configurations for Python Formatters
Let’s provide a couple of example configuration snippets to illustrate how you might set up these tools in a project. Python tooling often uses pyproject.toml to centralize configuration.
1. Using Black + isort (with pre-commit):
pyproject.toml:
1[tool.black]
2line-length = 88
3target-version = ["py310"] # adjust to your Python version(s)
4skip-string-normalization = false # Black will normalize quotes to double by default
5
6[tool.isort]
7profile = "black"
8line_length = 88
9# You can add known_third_party or known_first_party sections if needed,
10# but profile=black sets sensible defaults.Here, Black and isort are configured to be compatible (both using 88 line length, etc.). The profile = "black" in isort automatically sets:
- multi-line output mode compatible with Black,
- include trailing commas where Black would,
- ensure parentheses wrapping similar to Black’s style.
Now, typically you would also have a pre-commit configuration to run these. For example, in a .pre-commit-config.yaml:
1repos:
2 - repo: https://github.com/psf/black
3 rev: 23.9.1 # use latest Black release
4 hooks:
5 - id: black
6 language_version: python3.10 # or whichever version you're using
7
8 - repo: https://github.com/PyCQA/isort
9 rev: 5.12.0
10 hooks:
11 - id: isortThis will run Black and isort on every commit (fixing issues in place). The order in the config is Black then isort here, but with profile=black it actually doesn’t matter much. Some prefer isort then Black to let Black handle any final wrapping. Either is fine as long as you remain consistent.
2. Using Ruff for everything:
pyproject.toml:
1[tool.ruff]
2line-length = 88
3target-version = "py310"
4extend-select = ["I"] # include import sorting checks/fixes
5# Optionally, if you want single quotes instead of double:
6# single-quotes = true
7
8# You can also configure specific lint rules, e.g.,
9# ignore = ["E501", "W503"] # ignoring line length (if you want Ruff to not enforce on its own because Black handles it) and line break before binary operator if needed.If you set extend-select = ["I"], Ruff will apply import sorting (analogous to isort) when you run ruff --fix. If you don’t include "I", then Ruff’s default might not sort imports (depending on default config; I believe by default Ruff’s linter includes all sorts of rules including I001 now, but the documentation should be checked – adding it explicitly ensures it).
To format, you have two approaches:
- Run
ruff format .to do Black-style formatting on the whole project. - Or rely on
ruff --fixwith the linter to both format and sort incrementally.
If using pre-commit, you could use the official Ruff hook:
1- repo: https://github.com/charliermarsh/ruff-pre-commit
2 rev: 0.0.282 # use latest Ruff version
3 hooks:
4 - id: ruff
5 args: ['--fix']This will lint and fix (including formatting) in one go. Ensure args: ["--fix"] is there so that it actually applies changes, not just reports them.
3. Using autopep8 + isort:
pyproject.toml (or setup.cfg): For autopep8, configuration is not done in pyproject under a [tool.autopep8] section (autopep8 doesn’t natively read pyproject as of now). Instead, you might configure options via a config file for pycodestyle (since autopep8 basically uses pycodestyle under the hood). For simplicity, one might just rely on command-line flags.
However, you can put some settings in a setup.cfg or tox.ini under [pep8] or [pycodestyle] sections to influence what gets flagged (and thus fixed). This is a bit clunky. For brevity, let’s say you run it with flags directly.
Example command to format:
autopep8 --in-place --max-line-length 88 --ignore E24,W503 -r .
Flags explained:
--max-line-length 88to adhere to 88 char lines (like Black).--ignore E24,W503for example to ignore certain PEP8 rules (E24 is about missing blank lines, W503 line break before binary operator which conflicts with W504).-r .to recurse through project files.--in-placeto modify files (you might use--diffto just preview changes).
Then run isort . after, with a similar config as above (88 line length, etc.). Or integrate in pre-commit:
1- repo: local
2 hooks:
3 - id: autopep8
4 name: autopep8
5 entry: autopep8
6 language: system
7 args:
8 ['--in-place', '--max-line-length=88', '--ignore=E24,W503', '-r', '.']
9 stages: [commit] # or manual pre-commit hook(This is a bit clumsy; using Black or Ruff with pre-commit is usually simpler than autopep8 because they have official hooks.)
All of these tools share the primary benefit: automatic enforcement of style, leading to a more consistent and clean codebase. The choice often comes down to team preference and how much existing inertia there is (e.g., if a project already uses Black, switching to Ruff is a consideration but not urgent unless the benefits are needed).