Fix Python indentation on a Mac
You can do this with Note My. Here is how to see the whitespace a Python file actually contains, and fix it in one pass — including the mixed tabs the error message never points at.
Python is the one language where whitespace is not cosmetic. A block that looks correct on screen can still raise IndentationError or TabError, because a tab and a run of spaces occupy the same width and carry different meaning. The causes are dull: code pasted out of a web page or an AI chat, a file edited by two people with different settings, a snippet lifted from a document that converted tabs on the way in.
This guide covers three separate jobs in Note My — making the whitespace visible, re-indenting the file, and checking what is left. Knowing which tool does which saves a lot of guessing.
All of this happens on your Mac. Nothing is uploaded, there is no account to make, and a file you open, format or scan never leaves the machine.
Why Python indentation goes wrong on a Mac
Four causes account for nearly all of it.
Mixed tabs and spaces. A leading run containing both is legal to type and illegal to run. It is also invisible: a tab draws as blank space, exactly like the spaces beside it.
Pasted code that carried its own indentation. Pasting into Note My always gives you plain text, so styled text arrives as characters rather than fonts and colours. The whitespace inside it, though, is whatever the source used — and that is the part you have to look at.
Continuation lines. A wrapped argument list has two legitimate conventions, and mixing them across one file is what makes a diff unreadable.
Characters that are not spaces at all. A non-breaking space or a narrow no-break space in a leading run looks like a space and is not one. Text that has been through a rich-text editor or a chat window collects them.
Re-indent the file
Re-indent is a single, deliberate operation, and it lives on the toolbar Indent button. Clicking it opens a sheet where you choose the width, the character and the Python options, and nothing in your document changes until you press Apply. You get to look before you commit.
-
Check the language is Python first
Re-indent works from the document's language, so it is worth confirming before you open the sheet. Our advanced language identifier sets it for you the moment the file opens, so a normal
.pyfile needs nothing from you.The status bar shows the language the document is set to. Click the Identify pill to open the Language Inspector, which reads "Identified programming language: X" and offers an override pop-up with Confirm and Cancel. Set it to Python here if the file arrived with an odd name or no extension.
-
Open the Indentation sheet
Click Indent in the toolbar. A floating panel appears with a language picker of sixteen options — a global default plus fifteen languages that carry their own indentation settings, Python among them — a brace-style switch, a live preview card, the indent controls, a comment-recovery toggle, and a Python-specific section.
Choosing a language snaps the brace style, width and tabs-or-spaces controls to the values you saved for that language, so Python and Go each keep their own house style. A "Reset to global" link appears only when the language actually has overrides.
-
Set the width, the character and the Python options
Indent width defaults to 4, with a sheet range of 1 to 12, and spaces rather than tabs is the default — which is already what Python's own style guide asks for. Continuation style and the trailing-comma policy sit in the language section below the controls; both are covered in detail further down this page.
-
Apply
The footer has Cancel and Apply. Apply re-indents the document and keeps the settings, so the next file you open inherits them. Save first (⌘S) and you always have a way back: File ▸ Reload from Disk (⌘R) brings the document back to exactly what you saved, and reloading never changes the file on disk. That pairing — save, apply, look, reload if you want the old shape — makes a whole-document re-indent something you can try on any file without thinking twice.
What the Python four-space lock actually does
The sheet offers two language locks. Go is locked to tabs, and that lock is on by default. Python is locked to four spaces, and that lock is off by default. The asymmetry is deliberate: Go's toolchain is unambiguous about tabs, while a Python codebase may have a house rule you do not want overwritten.
The lock governs the re-indent you run from the Indentation sheet with a language selected there, which is exactly where you want it: turn it on when you are tidying a file that should come out at four spaces whatever it currently contains, and one pass settles the question for the entire document. Live typing takes its cue from the global width and tabs-or-spaces setting, so set that to 4 with tabs off and the lines you type match the lines a re-indent produces. Set both once and the file stays consistent whether you are cleaning it or writing in it.
Continuation lines: hanging or aligned
The Python continuation style has two options and defaults to hanging. Hanging means a wrapped construct opens, drops to a new line at one extra indent level, and closes on its own line:
result = compute_totals(
ledger,
period="Q3",
include_pending=True,
)Aligned means the continuation lines line up under the opening delimiter instead:
result = compute_totals(ledger,
period="Q3",
include_pending=True)Neither is wrong. Hanging survives a rename of the function; aligned re-flows every time the name changes length. Pick one, set it once, and let the re-indent make the file consistent.
Trailing commas in Python collections
The Python trailing-comma policy has three settings — preserve, add and remove — and defaults to preserve, which leaves your commas exactly as you wrote them. Set it to add if you want a trailing comma on multi-line collections so future diffs touch one line instead of two, or remove for the opposite house style. Preserve is the polite default: a formatter that quietly rewrites punctuation you did not ask it to touch is one people turn off.
The brace-style switch and the live preview
The sheet's brace-style switch has two positions: same line (K&R, the default) and next line (Allman). It is there for the brace languages in the picker, which is worth knowing because it means one sheet covers your whole toolkit: the day you switch from a .py file to the Go file beside it, the settings you want are already saved under that language and one click brings them back.
The live preview card sits above the controls and renders a sample if <condition> { … } snippet at whatever width and brace style you have currently selected. Read it as a ruler: nudge the width from 4 to 2 and you can see the shape you are about to ask for before you commit to it, and the card follows the controls as you set them.
See the indentation you actually have
Before you reformat anything, it is worth looking at the whitespace directly. Two independent features do this.
Invisibles
The toolbar Show Invisibles toggle, and Settings ▸ Invisibles, control four switches that are all off by default: show whitespace, show end of line, highlight trailing whitespace, and underline whitespace. With show-whitespace on, a space is drawn as ·, a tab as →, and a non-breaking space as a ring — so a mixed leading run stops being invisible and becomes obvious.
There are three marker styles. Symbols is the default and the quietest. Code Points labels each character by its Unicode value. Bytes shows hex bytes in the document's own encoding — a space is 20 in UTF-8 and 20 00 in UTF-16 LE, and a UTF-8 non-breaking space reads C2 A0. That last style answers "what will actually be written to the file?".
None of this changes the document: Show Invisibles changes what you see, not what is in the file, so you can leave it on while you work. Removing invisible characters is a different command, under Clean ▸ Clean Invisible / Special Characters…, and that one edits the text.
Indentation guides
Guides draw a marker down each level of indentation so a deep block stays readable. They are on by default, toggled from the toolbar Guides button, and styled in Settings ▸ Guides, which offers ten styles in this order: Dots (the default), Color Dots, Dashed, Thin, Medium, Thick, Rail, Beam, Accent and Spectrum.
Guides reflect the indentation your file actually contains, not the indentation you meant to write, so a guide landing in an unexpected place is telling you something about the file. Read them as a structural check before you reformat: a block that lines up with the wrong parent shows as a broken column long before Python complains. If they are hard to see, turning on Increase Contrast in macOS accessibility settings makes them stronger, and the Color Dots or Accent styles stand out on their own.
Catch mixed tabs and spaces before Python does
Our hidden-character scanner covers twenty-one categories of problem character, and one of them is Mixed tab/space indent — a leading run containing both. In a Python file that finding is raised to danger severity and CRLF line endings to warning — the two that stop the file running. In a Makefile the same finding is left alone, since tabs there are load-bearing. You get the severity that suits the file in front of you.
To see it, open Clean ▸ Clean Invisible / Special Characters…. Set the scope to Whole Document, choose the Aggressive preset or tick the category yourself under Custom, and press Scan. Mixed indentation belongs to Aggressive, so that is the preset to pick when it is what you are hunting for; Code-safe, the default, keeps its attention on the characters that are always safe to strip out of code. The right-hand table lists every finding as Line:Col · Code Point · Category · Action · Risk, and double-clicking one reveals it in the editor.
The two tools divide the work neatly. The scan finds every mixed leading run and shows you where it is; the Indent button rewrites them. Rewriting a leading run is a formatting decision rather than a stray character to delete, so it stays yours to make — you see the evidence first, then choose the shape you want.
The same sheet clears the rest of the whitespace noise: trailing whitespace, non-breaking spaces, narrow no-break spaces and zero-width characters are all in the default Code-safe preset and all safe to remove from code.
Recover code that a comment swallowed
Occasionally a paste collapses a line so that a # comment ends up in front of code that should have been on its own line. The Indentation sheet has a Recover damaged comments checkbox for exactly this, off by default. Tick it and the re-indent splits such a line back apart, putting the comment above the code it was sitting on.
It covers thirteen languages, Python among them, and it is deliberately conservative. It works on single-line comments, and it will not split a line it cannot split cleanly, so a comment that genuinely belongs at the end of a line stays there. Block comments are re-flowed inside their markers, never un-commented. Turn it on for a file you know is damaged; leave it off otherwise.
Keeping new lines right as you type
Smart Edit handles the live half, and its five switches are all on by default. Auto-indent on Return starts the new line at the same indentation as the one above, and one level deeper after a trailing {, [, ( — or a trailing colon. That last case is Python's: end a line with : and the caret lands where the body of the block should start.
The toggle sits in the toolbar, and the same command is on the Edit ▸ Smart Editing menu and in Settings ▸ Editing, where you can set the five switches one at a time. Leave them on for Python. The depth you get on Return is the depth you would have typed anyway, so a block body starts in the right column without you reaching for Tab, and a file you have just cleaned stays clean while you keep working in it.
Typing and re-indenting are separate jobs. Smart Edit looks after the line you are starting; the Indent button looks after the file you already have. Set the global width to 4 with tabs off, and what you type matches what a re-indent produces — the simplest way to stop a tidied file drifting back into mixed indentation.
Troubleshooting
Nothing happened when I clicked Indent
Indent opens the sheet, and the document is rewritten the moment you press Apply — that gap is the point, because it lets you set the width and check the preview before anything moves. If the sheet appeared and Apply left the text looking the same, the file was already indented the way your current settings would indent it. Check the width and the tabs-or-spaces switch, then apply again.
A big Python file takes a moment to re-indent
Nothing has gone wrong. A re-indent covers the document from its first line to its last, so on a very long module the window works for a few seconds before the finished text appears — that is the whole file being brought into line in one step, every line landing together rather than drifting into place a screen at a time. Size is not something to plan around: the one-pass tidy that settles a twenty-line script settles a ten-thousand-line module on the same terms, and our large-file engine keeps a heavy document open and comfortable to scroll through while you read the result.
The result is indented, but wrongly
Our indentation engine indents the structure the file actually describes, so a block nested under the wrong parent comes back neatly indented under the wrong parent. Read the columns first: turn on indentation guides and a block attached to the wrong level shows as a broken column straight away. Save before you apply (⌘S) and File ▸ Reload from Disk (⌘R) puts the saved version back exactly as it was — fix the structure, then run it again.
Typing still inserts the wrong whitespace after I set the language
Live auto-indent follows the global width and tabs-or-spaces setting. Set the width to 4 in the sheet and leave tabs off, and typed indentation matches formatted indentation from then on.
What "Reset to global" resets
It clears three things: brace style, indent width, and tabs-or-spaces. Your continuation style and trailing-comma policy stay exactly as you chose them, which is what you usually want when you are only putting the widths back — the house style you set for Python survives a change of mind about the numbers. The sheet's Python section is where you adjust them whenever you like.
The file still will not run
Check line endings as well as indentation — a Python file that has picked up Windows endings is flagged as a warning for a reason. The status-bar pill shows Mixed when a file has both, and File ▸ Line Endings converts the whole document to LF, CRLF or CR. LF is what you want for a script you intend to run on a Mac.
What re-indent does for you
- It settles the whole file in one pass. Every leading run in the document comes out at the width and character you chose, so a file assembled from three sources ends up reading as one.
- It unpacks statements that share a line. A pasted line carrying two statements is separated onto lines of its own at the right depth, which is usually the part that was hiding the real structure.
- It keeps a house style per language. Python's width, continuation style and trailing-comma policy are remembered under Python, and the next
.pyfile you open inherits them. - It pairs with the scan. Our hidden-character scanner reports what is in the file and the re-indent puts it right, so you never have to guess which of the two you need.
- It runs only when you ask. Your file is rewritten at the moment you press Apply and at no other time, so a re-indent is always something you have chosen.
Frequently asked questions
Should I use tabs or spaces for Python?
Spaces, four of them, unless your project says otherwise. That is the default in the Indentation sheet — width 4, tabs off — so you do not have to change anything to get it. The important part is not which you pick but that one file only ever uses one of them.
How do I see whether a line uses tabs or spaces?
Turn on Show Invisibles from the toolbar. A space draws as · and a tab as →. If you want certainty about what is written to disk, switch the marker style to Bytes in Settings ▸ Invisibles and read the hex.
How do I run a re-indent?
From the toolbar. Re-indent is the Indent button, indentation guides are the Guides toggle beside it, and code folding is a double-click on a brace. Everything you need for a tidy-up is one click away on the bar in front of you.
Will re-indenting change anything other than whitespace?
It re-indents and splits statements that share a line, and with comment recovery enabled it will also split a line where a comment marker swallowed code. The trailing-comma policy defaults to preserve, so punctuation is left alone unless you ask.
How do I work through a folder of Python files?
Re-indent acts on the document in front of you, so you go file by file: open, apply, save, next — each one gets a look before it changes. Find in Folder (⇧⌘F) is the quick way to turn up every file in a directory tree that mentions what you are chasing, and it only ever reads files — it never writes them.
Does Note My colour Python code as I type?
Yes. Our syntax engine colours fifteen code languages and Python is one of them. The colour is something you see rather than something the file gains, so the document saves as exactly the bytes you typed.