Python Formatter

Paste a module and get a four space grid, spacing around the operators PEP 8 asks about, and two blank lines between top level definitions. Every string, docstring and comment comes back the way you wrote it, character for character.

Python formatting workspace

  • Nothing leaves the tab
  • Format with Ctrl + Enter
  • Drop a .py file on the left
Indent
Line limit
Quotes
Your Python0 lines
Formattedwaiting for input
Lines out0
Lines rewritten0
Blank lines added0
Tab indents0
Trailing spaces0
Over limit0

Python is the one mainstream language where whitespace decides what your code does

A stray space in JavaScript costs nothing. In Python, indentation is syntax. Mix one tab with four spaces inside the same block and the interpreter raises TabError before a line runs. Paste a method one level too deep and the program still starts, quietly attached to the wrong parent. Formatting Python is two jobs wearing one name. Operator spacing is cosmetic. Indentation is structural, and a formatter with a loose grip on the difference will break your file.

This page keeps the two apart. Indentation is rebuilt from the block depth of your file rather than by swapping tab characters for spaces, so a module written with tabs, one written with three spaces, and one where two people disagreed all land on the same grid. Operator spacing runs over code only. Strings, docstrings and comments are lifted out of every line before a single space moves.

What changes, line by line

You wroteYou getRule behind it
total=0total = 0E225, spaces around an assignment
fetch(url,retries = 3)fetch(url, retries=3)E251, a keyword argument stays tight
def run(delay: float=1.0)def run(delay: float = 1.0)An annotated default takes spaces
def area ( w,h ) :def area(w, h):E211 and E203, no padding before brackets or colons
if code==404 :if code == 404:E225 on comparison operators
hits[k]+=1 #tallyhits[k] += 1 # tallyE262, two spaces before an inline comment
d = { 'a' :1 }d = {'a': 1}E201 and E231 inside a dict literal
→ one tab of indentfour spacesW191, tabs are remapped by level
five blank linestwoE303, blank line runs are capped
def next to the one abovetwo blank lines betweenE302 around top level definitions

The equals sign follows two rules, and mixing them up is the giveaway

Most formatters written in an afternoon apply one rule to = and produce output no Python team accepts. PEP 8 asks for spaces around assignment at statement level, no spaces around a keyword argument or a bare default, and spaces again once that default carries an annotation. All three appear in a single line of ordinary code.

One signature, two answers

def send(url, retries=3, backoff: float = 1.5):

retries=3 is a bare default sitting inside brackets, so it stays tight. backoff carries a type, and the annotation brings the spaces back. Move down into the function body, write timeout = 5, and the spaces are required a third time. This page tracks bracket depth character by character while reading each line, which is how one symbol gets three different answers in the same function.

Indentation is rebuilt from depth, not from a find and replace

Swapping every tab for four spaces works until somebody used two spaces on one line and a tab on the next. The formatter measures the leading whitespace of each statement, compares it against a stack of the levels seen so far, and emits your chosen unit multiplied by the depth. A file indented with three spaces comes out at four. A file with a mixed block comes out consistent, and the TabError goes with it.

Two things stay untouched on purpose. Continuation lines inside an open bracket keep their own alignment, because a hanging indent carries meaning a depth counter has no way to read. Closing a docstring on its own line keeps the whitespace in front of the quotes, since those spaces belong to the string rather than to the code.

Long lines get counted, never broken

Pick 79 for the PEP 8 letter, 88 to match Black, or 120 for teams who settled there years ago. Lines past the limit are reported by number under the editor with nothing rewritten. Breaking a long call means deciding where arguments belong and whether a trailing comma is coming, and a wrong guess there rewrites the diff on every future edit of the same block. Line numbers point you at the work. The judgment stays yours.

Left alone on purpose

Arithmetic operators keep whatever spacing you gave them. pycodestyle ignores E226 by default because PEP 8 recommends tightening around the higher priority operator, so dist = x*x + y*y reads better than a version with identical spacing everywhere. Install Black when you want every operator normalized without argument.

Three passes, in this order

  1. Each line is split into code, strings and comments. Triple quoted blocks are tracked across line breaks, so a docstring holding x=1 or a SQL query with commas comes through untouched. Prefixed literals such as f"…", rb'…' and raw regex patterns are recognized as strings, not code.
  2. Only the code fragments are rewritten. Runs of spaces collapse, comparison and augmented assignment operators get their spacing, brackets lose their inner padding, commas gain a following space, and the two equals rules are applied against live bracket depth.
  3. The vertical layout is rebuilt last. Statements are reindented by level, blank line runs are capped at two at the top level and one inside a block, PEP 8 gaps are inserted around definitions, trailing whitespace is stripped, and a comment sitting directly above a function keeps its place attached to that function.

Everything happens in your browser tab

No upload, no API call, no server log. Cut your connection after the page loads and the formatter keeps working, because the parser and the rewriter are both plain JavaScript running in front of you. Python pasted into web tools routinely carries connection strings, internal hostnames and API keys sitting in os.environ.get defaults, so a round trip to somebody else's server buys nothing worth the risk.

Where this formatter stops

Questions people ask while formatting Python

Indentation, the equals rules, and what a browser based formatter refuses to touch.

Will formatting change how my code behaves?

Operator spacing and blank lines have no effect on behaviour. Indentation does, which is why levels are rebuilt from block depth instead of by replacing tab characters, and why text inside strings and docstrings is never touched. The one visible risk sits with input already holding a syntax error, since there is no parse step to catch it. Run the output through Python once before you commit.

Why does f(a=1) keep no spaces while a = 1 gets them?

PEP 8 treats the two differently. An assignment at statement level takes spaces around the equals sign. A keyword argument or a bare default inside brackets stays tight, so f(a=1) is correct and f(a = 1) trips E251 in pycodestyle. The exception is an annotated parameter such as delay: float = 1.0, where the annotation brings the spaces back. This page tracks bracket depth while reading each line, so all three cases land right in the same statement.

My file mixes tabs and spaces. What comes out?

One consistent grid in whichever unit you picked in the toolbar. Leading whitespace on every statement is measured, compared against the levels already seen, and re-emitted as depth multiplied by your unit. That removes the TabError a mixed file raises on Python 3. Whitespace inside a multiline string is left alone, including the spaces before a closing docstring quote, since those characters are part of the string.

Does this wrap lines that run past 79 characters?

No. Lines past your chosen limit are listed by number under the editor and left intact. Wrapping a call means choosing where each argument goes and whether a trailing comma follows, and that choice affects every future diff on the block. Reporting the lines gives you the list without making the decision for you. Switch the limit between 79, 88 and 120 to match your project.

Is my code sent anywhere?

No. Tokenizing, rewriting and rendering all run in your browser, and the page makes no network request once loaded. Turn off your connection after the page appears and every feature keeps working. This matters more for Python than for markup, since pasted scripts often carry database URLs, internal hostnames and keys sitting in environment variable defaults.

How is this different from Black or autopep8?

Black parses your file into a syntax tree and prints a fresh one, which lets it wrap arguments, normalize quotes and add trailing commas. This page rewrites text line by line with no parse step, so it fixes spacing, indentation and blank lines while leaving structure alone. For a repository under CI, install Black or Ruff. For a snippet somebody pasted into a chat thread, opening a page beats setting up a virtualenv.

Are my strings and comments safe?

Yes. Every line is split into code, string and comment segments before any rewriting starts, and only the code segments are modified. A comment reading #tally becomes # tally and an inline comment gets two spaces in front of it, which are the two PEP 8 rules for comment formatting. The text after the hash mark is otherwise untouched, and string contents never change unless you switch quote normalization on yourself.

What does the quote setting do to escaped strings?

Double or single rewrites a literal only when the swap is free. A string holding the target quote character, or any backslash escape, is skipped entirely, so 'it\'s fine' and 'say "hi"' stay exactly as they are. Triple quoted blocks are never converted. Keep is the default because quote churn creates noisy diffs on a codebase nobody agreed to normalize.

Why did nothing change when I pasted my file?

The status line reports a match when spacing, indentation and blank lines already follow the rules on this page. That result is common on code already passing Black or Ruff. Check the over limit counter as well, since long lines are the one problem reported rather than fixed, and they stay in the file after every other rule has been satisfied.

How do I load a file instead of pasting?

Open file reads a .py from your machine, and dragging one onto the left pane does the same thing. The file is read by the browser through FileReader and never uploaded. Download writes the formatted output back out as formatted.py with a closing newline. Files above 2 MB are refused, because a browser editor holding that much text stops being usable.