Writing Code

How to edit a Python file on a Mac

You can do this with Note My. A script, a scraper, a one-off data job — here is what you get while writing Python without a project file or a development environment.

Most Python you write on a Mac is not a project. It is a script in a folder, a file someone sent you, a job that runs once a week from a cron entry. Opening a full development environment for it is a lot of ceremony for forty lines. What you actually want is a fast editor that colours the file correctly, finishes identifiers, tells you what arguments a call takes, and does not silently reformat anything.

This guide covers editing a .py file in Note My: opening it, confirming the language, what the colouring covers, what the suggestion list gives you, how to move around the file once it is open, and where the script goes when you are done writing it.

Note

Everything here happens on your Mac. Nothing about a file you open is uploaded, there is no account to make, and the file never leaves the machine.

Open the file and confirm the language

  1. Open the .py file

    Press ⌘O and choose it, drag it from the Finder onto the tab bar, or right-click it in the Finder and choose Note My ▸ Open. Several files selected at once each arrive in their own tab.

  2. Check the status bar

    Our advanced language identifier does this part for you, so a .py file arrives already coloured as Python and you can start typing. The status bar names the language — glance at it once when the file opens.

  3. Set the language yourself if you need to

    Click the Identify pill in the status bar to open the Language Inspector. It states "Identified programming language: X", offers an override pop-up, and asks you to Confirm or Cancel, so you can look before you change anything. Choose Python and the colouring, the suggestions and the call tips all follow. The file itself is not altered.

Paste code into a brand-new, empty tab and it is coloured for you straight away, with no sheet to dismiss — a convenience for a snippet lifted out of a chat window or a bug report. Once you have chosen a language for a tab yourself, that choice stands until you change it.

What Python highlighting covers

Python gets the full treatment from our syntax engine: keywords, types, builtins, numbers, strings, comments and function names each carry their own colour. The parts that usually go wrong in a lightweight editor are covered too.

  • Triple-quoted strings. Both """ and ''' hold their colour across every line they span, so a long docstring stays one calm block instead of dragging the rest of the file in.
  • Escape sequences stand out against the string colour, so \n in a literal is visibly a different thing from the text around it.
  • An unbalanced quote stays a local problem. One stray ' colours the line it is on, not every line below it, so the mistake is easy to spot.
  • Decorators and SCREAMING_SNAKE constants get their own treatment, so the shape of a module comes through when you are scrolling.

None of that colour is written into the file. It does not mark the document as edited, it does not follow the text into a copy, and it clears when you switch the language. What you save is the characters you typed.

Suggestions while you type

As you type an identifier a floating list appears, grouped into labelled sections so you can see what kind of thing each row is:

The kinds of suggestion offered in a Python file and what each one gives you
SectionWhat it gives you
Auto-importA symbol plus the import line to add. Python is one of the languages that gets this.
PathsFile paths and the folders above them, once you have typed a /.
BuiltinsFunctions and types, with their signatures in the row.
Document symbolsThe functions and classes already declared in this file.
KeywordsThe language's own keywords.
WordsOther names in the document, so one you used forty lines up is a keystroke away.

The list never steals your keystrokes. Whatever is on screen, what you type goes into the document, so you can ignore the popup and keep writing. ⌃Space summons it on demand even when suggest-while-typing is switched off, which is the setup a lot of people settle on. ⇥ and ↩ both accept, and each can be switched off on its own in Settings ▸ Code Suggestions.

You do not have to type the start of a name: typing pri still reaches printf-shaped names. Names you accept often work their way up the list. Paths you reuse can be pinned with ⌃M and are then offered in every other document — worth doing for the data directory a batch of scripts point at.

Imports added when you accept

Auto-import is offered in Swift, Python, JavaScript and TypeScript. In a Python file, an auto-import row is a symbol that carries its own import line, and the row says so before you accept. Accept it and two things happen: the symbol goes in where you are typing, and the import line appears at the top of the file. Your place on screen is kept.

The symbol and the import arrive as two separate edits, and once the import is there it is an ordinary line of text like any other — yours to move, keep or delete. Glance up there before you carry on, and the top of the file always says exactly which modules the script is pulling in.

Import rows turn up a few letters into a name rather than on the first keystroke, so they stay out of the way while you are still deciding what to call something. The feature is governed by the "Add missing imports" row in Settings ▸ Code Suggestions. Turn it off and every other kind of suggestion carries on unaffected.

Call tips and placeholders

While you are writing a call, a small panel shows the signature with the argument you are currently on highlighted, so you can see what to type next without leaving the line. Press ⌃⇧Space to summon it. Python is covered: you get a signature for the builtins and for the functions declared in the file in front of you, which between them cover most of what a script calls. Where there is nothing real to show, nothing is shown — a signature is never invented, so a tip you see is one you can act on.

Accepting a suggestion that has a template drops in the call with its first argument selected — accepting print gives you print(objects) with objects already highlighted, so typing replaces it. ⇥ jumps to the next placeholder and selects its default. Stepping stops once you pass the last placeholder, or as soon as you click somewhere else in the file, so you are never held inside the call against your will.

Starting a new Python file

Our template library saves the retyping, reachable from File ▸ New from Template or the toolbar Templates pull-down. Three entries are directly useful here: main.py in the Languages group, and pyproject.toml and .gitignore in the Config group. Start from main.py and the file is already Python and already coloured.

Template bodies can carry smart tokens — {{DATE}}, {{AUTHOR}}, {{FILENAME}}, {{YEAR}} and {{CURSOR}} among them — which are filled in as the file is created, so a new script arrives dated and headed without you touching the header. {{CURSOR}} is the one worth using in your own: it sets where you land when the file opens. If you keep beginning scripts from the same shape, Save Current Document as Template… puts your version in a "My Templates" group.

Typing help you can leave on

Smart editing has five switches, all on by default. The one that matters most in Python is auto-indent on Return. Press Return and the new line starts at the same indent as the line you left, and one level deeper after a colon — the shape of every Python block.

python
def load(path):
    with open(path) as handle:
        return handle.read()

Brackets and quotes auto-pair: () [] {} "" '' and backticks. An apostrophe typed straight after a letter or digit is left alone, so writing don't in a comment does not leave you with a stray quote to delete. Typing a closing bracket over one Note My put there just moves you past it; the text is not changed.

Tip

No text-mutating assist is ever enabled, in any mode. No auto-correct, no smart quotes, no dash substitution, no data detection. A quote you type in a Python string stays a straight quote.

Moving around the file

Double-click an identifier and every other use of it lights up. Matches are whole names only, so value does not light up inside values_list — which is what you want when you are checking where a variable is used. The status bar's Occ counter is stricter still: select a single plain word and it counts them; select anything else and it shows zero rather than a number you cannot trust.

⌘L goes to a line number, pre-filled with the line you are on and selected, so it also answers "where am I?". An out-of-range number is refused rather than quietly clamped to the end of the file. For places you want to come back to, ⌘F2 toggles a bookmark, F2 and ⇧F2 step between them, and ⌥⌘B bookmarks every match of the current search — which turns a search for TODO into a set of stops you can walk.

Folding long blocks out of the way

Folding works on bracket pairs: {}, () and []. Double-click a bracket and everything between it and its partner collapses; double-click it again, or click where the folded text was, and it comes back. The gutter shows a marker beside every foldable block.

In Python that covers the parts of a script that take up the most room: a long dictionary literal, a multi-line call, a list of constants that runs down half the screen. The configuration dictionary at the top of a script is the classic case — fold it and the function underneath fits on one screen. The table of embedded data at the bottom is the other; fold that and the code you are actually working on is all you can see. Folding never changes the document: a folded file is not a modified file, and saving it writes the whole text.

Large or generated Python files

Some Python files are not written by hand: a generated module, a fixture full of embedded data, a script with thousands of lines of constants at the bottom. These are the files that make a light editor stall or refuse to open. Our large-file engine lets you open one and get on with the job, and the status bar tells you when you are in that territory.

Colouring stays on, so the file still reads as Python rather than a wall of grey. You can scroll it, search it, jump to a line and edit it, and typing stays responsive. Our auto-recovery safety net looks after your work as well, so an unexpected quit does not cost you the edits you made.

The suggestion list is there for the asking: press ⌃Space and it appears, so you can call it up for a name you half remember and then type on undisturbed through the rest of the line. When you want to know how often a name appears in a file of that size, the magnifying-glass button in the toolbar opens our find and replace, which keeps a live count of the matches as you sharpen the query, and ⌥⌘B drops a bookmark on every one of those matches so you can walk them with F2 rather than scrolling for them.

One last thought: if a script generates the file, your edit goes the next time it runs. The fix belongs in the generator.

Troubleshooting

The file opened as Plain Text

The status bar will tell you so. Click the Identify pill and choose Python; the colouring, the suggestions and the call tips all arrive together. A script saved without a .py extension is the usual case.

A different row is selected from the one I expected

The list is grouped into labelled sections, so an auto-import row can sit above the plain name you were reaching for. The label on each row tells you what kind of thing it is, so a glance is usually enough: look before you press ↩, or arrow down one row to the one you want. Names you accept work their way up the list, so the order settles around the way you write.

I want the list to appear as I type

Look in Settings ▸ Code Suggestions, where each kind of suggestion has its own switch and suggest-while-typing has one of its own. ⌃Space summons the list whatever those switches say, so if the summoned popup appears, you are one switch away from having it offered as you type as well.

Putting the import below a shebang line

The import line goes to the very top of the file. In a script that opens with a shebang or an encoding declaration, move it down a line or two so it sits under the header where Python expects it — ⌘X on the line, click below the header, ⌘V. Paste always arrives as plain text, so the line lands exactly as it left, and the rest of the file is untouched.

Tabs and spaces are mixed in the file

Turn on the invisibles you need in order to see it, then use the toolbar Indent button, which opens the Indentation sheet. Nothing changes until you press Apply, so you can open it and back out. Python is one of two languages with an indentation lock: Python → 4 spaces, though unlike the Go lock it is off by default, so switch it on if you want it enforced as you type. Our cleanup rules flag mixed indentation in a Python file too, so you can settle the file on one style before a stray tab turns into an error you have to hunt for.

A re-indent on a very large file is taking a while

Re-indenting a generated file means real work on tens of thousands of lines, so give it a moment to finish rather than pressing Apply a second time. What comes back is the whole file at the depths Python implies, consistent from top to bottom, with any statements that were sharing a line unpacked onto rows of their own. Press ⌘S before you press Apply, as always, so the copy on disk is the one you would want back.

I applied a re-indent and want the old formatting back

The file on disk is your escape hatch. Save before you press Apply, and if the result is not the shape you wanted, File ▸ Reload from Disk (⌘R) brings the saved version straight back into the tab. Reloading never changes the file on disk, so you can go out and back as many times as it takes to settle on one style — and because the Indentation sheet changes nothing until you press Apply, you can open it, look, and back out at no cost.

Taking the script further

Writing the lines is rarely the whole job. A script gets run, renamed, compared against last week's copy and tidied before it goes anywhere, and all of that sits a keystroke away from the tab you are already in.

  • Run it where it lives. The tab's right-click menu offers Open in Terminal and Save and Open in Terminal. The second writes your changes and puts you in the script's own directory with the shell ready, so python3 script.py is simply the next thing you type. No hunting for the path, no dragging the file into a window.
  • Rename across the file. Double-click the identifier and every use of it lights up, whole names only, so you can see the true count before you touch anything — the status bar's Occ figure gives you the number. Then hand the change to our find and replace and step through the matches one at a time.
  • See what changed. Our comparison view sets two files beside each other and shows you where they differ, which is the quickest way to answer "what did I actually alter this morning?" before you overwrite the copy that was working.
  • Search a whole folder of scripts. ⇧⌘F looks through every text file under a directory at once, so a helper you wrote months ago is findable by the string you half remember rather than by filename.
  • Repeat a fiddly edit. Our macro recorder captures a sequence of edits once and plays it back, which turns a tedious pass down a file of constants into a single command you repeat.
  • Tidy before you ship it. Our hidden-character scanner surfaces the non-breaking space that came in with a pasted line and the stray tab sitting among spaces — the characters that read as invisible and behave as errors. Our cleanup rules settle the rest.

Several scripts at once is the normal case, and tabs handle it: a job, its config file and the helper module it imports can sit side by side, each holding the language you gave it. Nothing here needs a project file, a workspace or a folder laid out a particular way — a file in a folder is enough, which is what most Python on a Mac actually looks like.

Frequently asked questions

Does editing a Python file here change its indentation or quotes on save?

What you save is exactly the characters you typed. No text-mutating assist is enabled in any mode — no auto-correct, no smart quotes, no dash substitution — and re-indentation happens only when you open the Indentation sheet from the toolbar and press Apply.

Can I open a Python file with no extension?

Yes. Our advanced language identifier gets most extensionless scripts coloured for you the moment they open. If the status bar reads Plain Text, the Identify pill lets you set Python in two clicks, and the file on disk is untouched either way.

What can I fold in a Python file?

Anything held between matching brackets — {}, () and []. Double-click either bracket of the pair and the block collapses; double-click again, or click where the folded text was, and it is back. A configuration dictionary, a multi-line call and a long list all fold down to a single line, and the gutter marks every block you can fold.

Will it add the import for a module I use?

For the symbols it offers, yes, and the row tells you so before you accept. The import line then appears at the top of the file while the symbol goes in where you are typing, and your place on screen is kept. Turn the behaviour off from the "Add missing imports" row in Settings ▸ Code Suggestions and every other kind of suggestion carries on unaffected.

How do I search across a whole folder of Python files?

⇧⌘F opens Find in Folder, which searches every text file under a directory and lists the results grouped by file as they arrive. It reads files and never writes them; the skip list starts at .git, node_modules, build, .build and DerivedData, and you can narrow it further with glob masks.

Is my code sent anywhere for the suggestions to work?

It all happens on your Mac. Nothing is uploaded, there is no account to sign in to, and the files you open never leave the machine — suggestions work the same on a plane as at your desk.