primopy
primopy is a place to learn Python in your web browser. There is nothing to install, no account to create, and nothing to set up. You open the page, you write a few lines, you press a button, and you see what happens.
Most tools only show you what your program printed. primopy also shows you what it did: every step of a run lands on the execution tape, with every variable on screen, and you can travel the tape forwards and backwards, line by line or one operation at a time. If you have never programmed before, that is the feature to get to know first.
There is more around it. A tutor sits beside the editor; it sees your code, your output and the step you are on, and it answers with a question rather than the solution. Teachers can hand out a whole project as a single link, and put quiz questions inside it for the tutor to ask. Drawings and charts appear next to the code, and a guided tour shows you the whole thing the first time you open it.
Contents#
- 1Your first five minutes
- 2Primo, the guided tour
- 3What's in the header
- 4The examples
- 5Writing code in the editor
- 6Running your program
- 7Stepping through your program
- 8Output, plots and drawings
- 9Programs that ask questions:
input() - 10Working with several files
- 11Package management
- 12The Python console, a calculator for trying things
- 13Code completion
- 14Undo
- 15The tutor
- 16Quizzes
- 17Documents and notebooks
- 18Is my work saved?
- 19Changing the layout and theme
- 20Keyboard shortcuts
- 21Things primopy can't do
- 22When something goes wrong
1Your first five minutes#
Open the page. In the tab row under the editor you'll see Downloading Python… for a few seconds, then Ready. Now Run and Record, at the top left, work.
Try this:
- Open the Examples menu in the header and pick 6 · Find the bug.
- Switch Record on, then press Run.
- Press Step ▶ a few times. Watch the Variables box on the right: it shows every variable and its current value, and highlights the ones that just changed.
- Went one step too far? Press ◀ Back.
That program calculates an average, and gets it wrong. Instead of staring at the code trying to work out why, you can simply watch the numbers change until you spot the moment things go astray. This is how you'll want to look at your own programs too.
2Primo, the guided tour#
The coiled snake in the logo is Primo. The first time you open primopy he uncoils, crawls across the screen and stops at each part of the interface to explain it. Press Next (or Enter, or →) to send him on. Skip or Escape ends the tour.
The tour uses a small example project to have something to show. Don't worry about your own work: when the tour is over, your files come back exactly as you left them.
You can call Primo back any time by clicking the logo, pressing the Tour button in the header, or choosing · take the tour · in the Examples menu.
3What's in the header#
The header reads left to right: first the two buttons that start your program, then everything you do to a project.
| Control | What it does |
|---|---|
| primopy logo | Click to start the tour. Hover to see the version and whether your work is being saved |
| Run | Runs your program. It wears the same yellow mark as the file tab it starts from |
| Record | A switch. While it is on, Run records every step, and the button turns yellow and its mark becomes a record dot |
| Examples | Loads a ready-made example program (replacing what's open) |
| Open… | Adds files from your computer |
| Open folder… | Opens a folder on your computer and saves to it as you type. Chromium browsers only, that is Chrome, Edge, Brave and Opera; see A folder on your computer |
| Share | A menu with three ways out: Copy link, Download ZIP, and Download conversations |
| Undo | Takes back your last change. When that change would bring files back, it turns dark blue and reads Restore 2 files |
| Tour | Starts the guided tour |
| Handbook | Opens this handbook in a new tab |
| Tutor | Shows or hides the tutor chat |
| ◐ / ☀ / ☾ | Switches the look: follow the system, light, dark |
The status (Downloading Python → Ready → Running → Finished, or a short error message) is not in the header. It sits in the tab row under the editor, right of Output / Plots / Display / Draw, next to what it is talking about.
4The examples#
The examples are short, complete programs, each about one idea. They're the best way to learn what primopy and Python can do. Pick one, read it, run it, step through it, then change something and run it again.
| # | What it shows |
|---|---|
| 1 | Variables and print |
| 2 | Drawing shapes on a canvas |
| 3 | Lists and loops: adding up numbers, building a new list |
| 4 | Drawing a pattern with a loop |
| 5 | Functions, and how one function calls another |
| 6 | Finding a real bug by stepping through the program |
| 7 | Drawing a whole scene |
| 8 | A program that asks the user a question with input() |
| 9 | A program split over two files |
| 10 | Numbers with numpy and a plot with matplotlib |
| 11 | Organising code in a folder (a "package") |
| 12 | Reading a text file and writing a new one |
| 13 | Adding to a log file every time the program runs |
| 14 | Reading a table from a CSV file and writing a report |
| 15 | Conway's Game of Life, drawn cell by cell |
| 16 | Snake: a game loop that reads the keyboard |
Loading an example replaces your current project, but Undo brings your project straight back if you didn't mean to; it says Restore 2 files when that is what it would do.
The last menu entry, · clear saved work ·, deletes everything you've saved (after asking) and leaves you an empty file.
5Writing code in the editor#
The big text area in the middle is where you write your program. It colours Python code to make it easier to read and helps with a few tedious things:
- Enter starts the new line at the same indentation as the one above, and indents one level further after a line that ends in
:(likeif,for,def). - Tab inserts four spaces, the standard Python indentation. If you're in the middle of typing a name, Tab completes it instead (see Code completion).
- Shift+Tab removes one level of indentation.
- Click a line number to select the whole line; Shift+click selects every line up to that one.
- Click just left of a line number to place a red dot, a breakpoint. With Record off, your program stops when it reaches that line and waits for you (see Stopping at a breakpoint). With Record on, it runs to the end, the tape opens at the first dot it passed, and Continue ▶▶ jumps from one dot to the next. Handy for skipping straight to the interesting part.
If you edit the code after recording a run, the recording is thrown away: it no longer matches what's on screen. Just record again.
Folding a block away#
In a Python file, a small triangle ▾ appears to the right of the line number wherever a block starts: a def, a class, a loop, an if, or a list or dictionary spread over several lines. The triangles are faint until you move the pointer over the line numbers. Click one and the block folds into its first line, with a ⋯ at the end to show that something is hidden; the triangle turns into ▸. Click the triangle or the ⋯ to unfold it again.
Folding only changes what you see. The code is still there and runs exactly as before. If your program stops on a line inside a folded block, at a breakpoint or while you step through a recording, the block unfolds by itself so you can see the line.
From the keyboard, with the cursor on a block's first line (the one with the def, the for or the if): Cmd+Option+[ folds the block and Cmd+Option+] unfolds it on a Mac; elsewhere it's Ctrl+Shift+[ and Ctrl+Shift+]. Ctrl+Alt+[ (Ctrl+Option+[ on a Mac) folds every block in the file at once, and Ctrl+Alt+] unfolds them all.
Finding and replacing#
With the cursor in the editor, press Ctrl/Cmd+F and a search bar opens under the code. Type what you're looking for: every match lights up, and Enter jumps to the next one (Shift+Enter to the previous one). next and previous do the same, and so do F3 or Ctrl/Cmd+G, with Shift to go backwards. Escape or × closes the bar.
Three boxes make the search pickier:
- match case:
Totalno longer findstotal. - by word:
xfinds the variablex, but not thexinsidemaxornext. - regexp: the search text is a regular expression, for the times you know what that is.
The second field is for replacing. Type the new text, then press replace (or Enter in that field) to replace the current match and move on to the next one, or replace all to change every match in the file at once. Ctrl/Cmd+Z takes a replacement back; a replace all comes back in one step.
The search looks through the file that's open in the editor, not the other files of the project. Ctrl/Cmd+F only opens this search bar while you're typing in the editor; anywhere else on the page, it's the browser's own search.
6Running your program#
There is one button that starts your program, Run, and a switch next to it that decides what kind of run it is.
Record off (the switch is grey): Run simply runs your program and shows what it prints. Fast, with no limits. Use this when you just want the result.
Record on (the switch is yellow): Run does the same thing, but writes down everything the program does on the way, so you can walk through it step by step (next section). Slightly slower, and it stops after 5,000 steps, so an accidental endless loop gives you a message rather than running for ever.
You can see which one you are about to get without reading the switch: while Record is on, the Run button itself turns yellow and its play mark becomes a record dot.
What your program prints appears as it is printed. A program that counts to ten slowly shows each line as it arrives, and one that draws shows each shape as it is drawn: you are watching it work, not waiting for it to finish. The page stays yours while it runs: you can keep reading, scrolling and typing.
While a program is running, the Run button becomes Stop, and its play mark becomes a square. Press it and the program stops where it is, even one that would never have ended on its own, and even one sitting in the middle of a long time.sleep(). Nothing is lost: your files stay as they are, and everything the run had worked out is still there for the console to look at.
There is a limit to how much the page will keep: about a million characters. A program that prints without end reaches it, writes ... output truncated, and carries on running; the status line says so, and Stop is the way out.
If your program uses a library such as numpy, matplotlib or pandas, primopy fetches it the first time you use it. The status in the tab row tells you what it's getting (the list can run to a few lines), and then it stays available.
If something goes wrong, the error is shown in red at the end of the output, naming the file and line. For a typing mistake (a syntax error), the editor jumps straight to the line.
Programs written with async and await run too. asyncio.run(main()), asyncio.gather, tasks and asyncio.sleep all work as they do on your own computer, and a recording shows the tasks taking turns: step through it and watch the program switch from one coroutine to the next at each await. The call stack of a task shows only that task, which is exactly what asyncio does. One thing does not work: awaiting something from the browser itself, such as pyfetch. The program stops with a message saying so, rather than waiting for ever.
Running one line at a time#
You don't have to run the whole file. Put the cursor on a line and press Shift+Enter: that line runs, and the cursor moves on to the next one, skipping blank lines and comments. Keep pressing and you walk through the file a line at a time. Select several lines and Shift+Enter runs those.
primopy runs whole statements, so you never cut one in half:
- On a line that starts a block (
for,if,def,while,with,try,class), the whole block runs. - A statement written over several lines, like a long list, runs as a whole wherever the cursor is in it.
- A line inside a function runs on its own. The function's own variables don't exist out here, so a
NameErroris what you'd expect. - Select part of a line, like
x * 2, and you get just its value.
When a line has a value, the way 3 * 7 does in the console, it appears in a small box right under the line. A loop shows one value each time round. An error shows up as a red box under the line that raised it, with the full message in the Output tab. × closes a box, running the same line again replaces it, and changing the line removes it. The boxes are never saved.
Whatever the lines print goes to the Output tab, below what's already there, set off by a thin line (hold the pointer on it to see which lines printed it). Plots and drawings go to their tabs as usual, and input() asks in the Output tab, as in a Run. Stop stops a line that doesn't end, like while True:.
You can also use the mouse. The number of the cursor's line (or of the first selected line) turns into a ▶: click it to run. So clicking a line number once selects that line, and clicking it again runs it. Hold the pointer over the ▶ and the lines it would run light up. While a recording is up, the button is a › instead and takes the tape to that line rather than running anything (see Jumping to a line).
The lines share their variables with the console: x = 5 run from the editor can be looked at in the console, and the other way round. They stay until the next Run, which starts fresh: it forgets them, and clears the boxes and what the lines printed. After that the program's own variables are there to build on, as usual. If you've changed another file that your code imports, the next line you run uses the new version.
Recording a few lines. While Record is on, Shift+Enter and the ▶ record the line or selection instead of just running it; the ▶ turns yellow then, like the Run button. The tape then holds only those lines and opens at their end, showing what they changed, so pressing Shift+Enter line by line works as it does without Record. Start goes back to before they ran, with the variables you had then. While the tape is up, a line's value shows on its Detail steps, like every other value; once the tape goes, the values of the recorded lines stay as boxes under them.
Stopping at a breakpoint#
Click just left of a line number to mark it with a red dot, and a plain Run stops when it gets there. (A Record does not stop: it runs to the end and opens its tape at the dot, where you can step backwards too.) It is not finished, only waiting: the status line says Paused at line 4 in fib(), the editor points at the line, and Variables and Call stack show what the program has in hand at that very moment. Click a function in the call stack to see its variables.
While it waits, the Run button reads Continue and a Stop button appears beside it:
- Continue (or Ctrl/Cmd+Enter) lets the program go on to the next breakpoint, or the end.
- Step ▶, Over ▼ and Out ▲ under the editor move it one line at a time, exactly as they do on a recording. A line that calls a function is one step for Over and a walk inside for Step.
- Stop ends the run where it stands.
The red dots can be changed while the program waits. Click one away and it no longer stops there; click a new one and the next Continue stops at it.
A stopped program can also be talked to. Open the console tab and it offers Attach: from then on what you type runs inside the function the program stopped in, reading and changing its variables, as described in The Python console.
The one place this doesn't work is a page that is not allowed to wait for you, the same setup that makes input() fall back to running the program again; hover the logo to check. There the dots only guide Continue ▶▶ through a recording.
7Stepping through your program#
After a run with Record on, a strip called the tape appears under the editor, together with the step buttons.
The tape#
Each tiny bar is one step of your program. Taller bars mean the program was inside a function (deeper still means a function inside a function). Red bars are errors or breakpoints. A bar with a small notch across its top is a step where you typed into the program from the console; hover it to see what you typed. The yellow line is where you currently are.
Drag along the tape to move through the whole run, like scrubbing through a video.
Jumping to a line#
Put the cursor on a line and its number turns into a ›. Click it, or press Shift+Enter, and the tape jumps to the next time that line runs, counting from where you are: from the start, that is the first time. Press again and it goes to the time after that, and round to the first again after the last: a quick way to watch one line of a loop turn by turn, or one line of a function on each call. A line above the one the tape is on works the other way round, since what you are asking about has already happened: the jump goes to the last time it ran before here, and each further press one time further back. The status line counts along, like Line 6, visit 2 of 3. On a line the program never reached, such as the body of an if that was false, the › is grey and the status line says so.
Nothing runs when you press it, and the recording stays. To run or record a line on its own while a recording is up, first close the recording with Stop or a new Run; then the number is a ▶ again.
The buttons#
| Button | Goes to |
|---|---|
| Start ↻ | The very beginning |
| ◀ Back | One step back |
| ◁ Detail | A smaller step back, inside the line |
| Detail ▷ | A smaller step forward: one piece of the line at a time |
| Step ▶ | One step forward |
| Over ▼ | Forward, but treating a function call as a single step |
| Out ▲ | Forward until the current function has finished |
| Continue ▶▶ | Forward to the next breakpoint, or the end |
The counter next to them reads something like step 24/181 · main.py:6, and inside a line it adds which of the line's small steps you are on, like · detail 3/8.
While a program is stopped at a breakpoint, the four forward buttons drive the program itself rather than a recording, and the others wait until there is a recording to walk. The counter then reads paused · main.py:4 · in fib().
Detail takes a line apart. Press it and the editor lights up the piece Python evaluates next: a name being looked up, a * b being multiplied, a function being called, in the order Python actually applies them. Each piece shows what it came to: in a box under its line, just below the piece, and in a Detail section above Variables, where a list or an object can be opened like any variable. While a recording is up, that section also answers a selection: select an expression in the editor, x * x or sq(4), and its recorded value appears there, with the step it came from (the latest one at or before where you are; if it only ran later, it says later). Select a literal, 0x1F or [1, 2, 3], and its value is read off the text, without running anything. Calls into your own functions are followed inside, and when they come back the call shows what they returned, like f(c) → 7. Where a function leaves, Detail first shows what it hands back, in the same box under the return (or the yield of a generator; a function that runs off its end shows None), and then the rest of the caller's line, so the marker on the tape only ever moves forward, even through a recursion. A return that hands its value straight back is a single step, with those small steps inside it; a separate step marked returns is only there when something ran in between, such as a call from the return line or a recursion, or when the function runs off its end. A few pieces have no value of their own to show, such as x and y, which Python works out by jumping rather than calculating. Step ▶ and ◀ Back skip these small steps, so nothing changes for you unless you ask for the detail.
You can also use the ← and → keys, as long as you're not typing in a text box; with Shift held they take the small Detail steps.
What you see at each step#
Every step shows you the complete state of the program at that moment:
- The editor highlights the line about to run (and switches file if necessary).
- Variables lists every variable with its current value. Anything that changed in the last step gets a yellow marker, the quickest way to understand what a line actually does. A list, a dict, a set or an object of one of your own classes has a ▸ in front: click it to see what it holds, two levels deep, with the first and last few items of a long list. What you open stays open as you step.
- Call stack shows which functions are currently running, the innermost at the top. Click one to see its variables.
- Output shows only what had been printed up to that step, so text appears exactly as you step past the
printthat produced it. - Plots and drawings behave the same way: go back before the line that made them and they disappear again.
8Output, plots and drawings#
The area below the editor has tabs:
- Output: everything your program printed, errors in red.
- Plots: charts made with
matplotlib(see example 10). Up to twelve per run. - Display: pictures your program hands to
display(), one card each. Up to twelve per run. - Draw: a canvas your program can draw on, with
turtleor withdrawfrom the built-inprimopymodule.
Draw#
import turtle works here as it does on a computer: the same commands, the same shapes, the same animation, drawn in the Draw tab instead of a window of its own.
import turtle
turtle.color("red", "yellow")
turtle.begin_fill()
for i in range(36):
turtle.forward(150)
turtle.left(170)
turtle.end_fill()
turtle.done()Three things are different from the desktop. There is no window, so done() and mainloop() return once nothing is left to wait for, and a program that draws and then calls done() simply ends with its drawing on screen; a program that has bound keys, clicks or timers waits for them until bye() is called or you press Stop. exitonclick() waits for a click on the drawing. And image files cannot be turtle shapes or backgrounds, so register_shape("picture.gif") and bgpic() raise an error; polygon shapes, the built-in shapes and compound shapes all work. For the rest, Python's own turtle documentation applies.
draw is the canvas itself, for a program that wants to draw directly:
from primopy import draw
width, height = draw.size()
draw.clear("skyblue")
draw.fill_style = "gold"
draw.fill_circle(width / 2, height / 2, 60)
draw.write(20, 20, "hello", size=24)Quiz: Where things land on the canvas · 3 questionsStart quiz
the canvas coordinates of primopy's draw object: (0, 0), the direction of y, and sizes · first programs; variables, arithmetic and function calls only, no loops yet
- Where on the canvas is the point (0, 0), and which way does y grow?
- What would you change in this program to draw the circle in the bottom-right corner? There is more than one good answer.
- Draw a second, smaller circle so that it just touches the first one from the right. Work it out on the canvas, predicting before each run.
The point (0, 0) is the top-left corner; x grows to the right and y grows downwards (this trips everyone up once). draw is a drawing context, the browser's canvas with Python names: what the canvas documentation calls fillRect is draw.fill_rect here, and fillStyle is draw.fill_style, so anyone who can read those pages can translate them. Styles are set by assignment (draw.fill_style = "gold", draw.line_width = 4), and draw.color(c) sets the fill and the outline colour at once. Every shape call says in its name whether it fills or strokes: fill_rect / stroke_rect and fill_text / stroke_text come from the canvas, and fill_circle / stroke_circle, fill_ellipse / stroke_ellipse, fill_polygon / stroke_polygon, line and write (text) are shortcuts for what the canvas makes you build as a path first; the path calls (begin_path, move_to, line_to, arc, fill, stroke, and the rest) are there too. Angles are in degrees. Drawing calls travel to the page in batches, and time.sleep() and every key or mouse read send what is waiting, so a loop that draws and sleeps needs nothing more; draw.flush() shows the picture right now, for the moment before a long calculation. Colours are the CSS ones, by name, "#rrggbb" code or "rgb(r, g, b)"; a mistyped name gives a friendly error. Beside the keyboard (draw.key(), draw.pressed()) there is the mouse: draw.mouse(), draw.mouse_down() and draw.click(), and on a touch screen a finger is the mouse.
primopy is a module that exists only here, and its draw has its own page with the full reference and more examples: the drawing page.
The canvas is as big as the Draw tab, so make the area taller and run again for a bigger picture. Drawings work with stepping too: record a run and watch the picture build up shape by shape.
Display#
display() comes from IPython.display, and it is how libraries written for Jupyter notebooks show a picture. There is no notebook here, so primopy stands in for one: whatever you display is asked how it draws itself, and the answer appears in this tab. A library that draws its picture once and then redraws it as it goes, the way jupyturtle moves its turtle, gets one card that holds the latest drawing.
from IPython.display import display, Image
from PIL import Image as PImage
import io
picture = PImage.new("RGB", (200, 120), "seagreen")
saved = io.BytesIO()
picture.save(saved, format="PNG")
display(Image(saved.getvalue()))Anything that knows how to draw itself arrives as a picture: PNG, JPEG, GIF (an animated one animates), WebP, a matplotlib figure, or plain image bytes. Anything else arrives as the text Python would print for it, so a list or a dictionary still tells you what it was rather than quietly vanishing. HTML is shown as the markup it is, not rendered as a web page.
There is nothing to install for this, and nothing to write differently. It works whether or not your project uses IPython, and a library that reaches for IPython.display at the top of its own file works too, which is what most of them do.
Display cards replay when you step, the same as plots: record a run, and each card appears at the moment your program displayed it.
9Programs that ask questions: input()#
Example 8 uses input() to ask the user something. The question appears in the Output tab while the program runs. Type your answer, press Enter, and the program continues. Escape stops it.
The program really does wait for you: it stops at the question, and carries on from exactly there once you press Enter. Nothing before the question happens twice, so programs that use random numbers or the clock behave just as they would anywhere else.
10Working with several files#
The row of tabs above the editor lists the files in your project. At the start there is just main.py. You'll want more files when your program grows (example 9) or when it should read data (examples 12–14).
+adds a file. Use.pyfor Python code, and.txt,.csvor.jsonfor data. A name liketools/helpers.pyputs the file in a folder.- Click a tab to open that file.
- Double-click a tab to rename it.
×deletes it.- The play mark at the left of each tab says which file Run starts with: a plain grey triangle on the others, punched out of a filled yellow coin on the chosen one. Click it to choose. Only
.pyfiles can be started.
Data files are shown in green.
Reading and writing files from your program#
Your program sees the tabs as files in its folder, so this works just as on a normal computer:
with open("list.txt") as f: # reads the tab called list.txt
lines = f.readlines()
with open("out.txt", "w") as f: # creates a new tab called out.txt
f.write("done\n")bread
apples
teaQuiz: Reading a file · 3 questionsStart quiz
reading text files with open() and a with block, readlines() and strip() · a few weeks in; loops and lists are known, nothing about encodings or exceptions
- After the first with block, what exactly is in
lines? - Why is
with open(...) as f:better than plainf = open(...)? - The second block writes out.txt. What happens to out.txt if you run the program twice?
A file your program creates shows up as a new tab marked ◦ and stays for the next run, and that's how example 13 keeps adding to its log.
Files from your computer#
Open… lets you pick files from disk; you can also just drag files onto the editor. Files are not uploaded anywhere; they stay in your browser. Very large files (over 400 KB) are refused.
A file that is not text — an image, a PDF, a zip — comes in too. There is nothing to edit in one, so its tab shows the file itself instead of the editor: images are displayed, everything else gets its name and size, and either way there is a Download button to save it back out. Your program can open() it in binary mode like any other file, and a binary file your program writes turns up as a tab of its own. A share link carries these files too: a link holding one is a zip, with the files in it exactly as they are, so a 13 KB workbook makes a link of about 15 KB. Roughly 300 KB of such files fit in one; past that the link is refused and Share → Download ZIP is the way.
A folder on your computer#
In a Chromium browser, which is Chrome, Edge, Brave, Opera and Chromium itself, the editor can work in a folder on your computer directly, the way a desktop editor does. Firefox, Safari and phones cannot hand a page a folder, so there the button is not shown and everything else works as before.
Open folder… asks you for a folder. Its files become your tabs, replacing what was open (Undo brings that back), and from then on every change you make is written to the folder about half a second after you make it, the same moment it is saved in the browser. A file your program writes lands there too. Left out are hidden files and folders, __pycache__, venv, node_modules, files over 400 KB, and everything past the first 200 files; the status line says what was skipped. Line endings stay as the file had them.
While a folder is open, the button shows its name. Edit a file with another program, and the editor picks up the change when you come back to the window or press Run: silently if you had not touched that tab, and with a question if you had. Renaming a tab renames the file; closing a tab leaves the file in the folder. Click the button to stop: the files stay in both places.
The browser forgets its permission when the page closes. On your next visit the button turns yellow and reads Reconnect; one click, and the browser asks once whether the page may use the folder again. Until then your files are on screen from the browser's own copy, and nothing you type is lost: it is written to the folder as soon as you reconnect. If you install primopy as an app, the browser can remember the permission for good.
Save & share → Save to a folder… is the same thing the other way round: your tabs stay, and you choose a folder for them to land in. Files the folder already has join the tabs. If both sides have a file of the same name with different contents, a dialog lists them and asks. Keep both, the default, renames your tab, so main.py becomes main-2.py and the folder's main.py comes in beside it. Replace the folder's writes your tab over the file. Cancel leaves everything as it was.
Nothing is uploaded either way: the folder is on your computer, the page reads and writes it there, and the copy in the browser stays as a fallback.
Share → Download ZIP downloads all your files in one archive, useful for handing in work, moving to another computer, or keeping a backup. To come back later, drag the zip itself onto the editor: the whole project returns, the file that runs and your breakpoints with it. That is what the hidden .primopy.json in the archive is for, and it is the only thing in there you did not write. Unzipping first and dragging the files in works too, and gets you the files without the rest.
Share → Download conversations downloads every talk you have had with the tutor as a ZIP of text files, one per conversation, named by the day and by the question it started with. It is the same archive History searches, taken out of the browser to keep, to hand in, or to read somewhere else. Nothing goes back in this way: a conversation is reopened from History, which is where it lives.
Share → Copy link copies a link to your clipboard. Paste it into a message and whoever opens it sees your project exactly as you left it: every tab, the file that runs, the caret where you had it, and your breakpoints. The project travels inside the link itself: nothing is uploaded, so there is nothing to sign up for and nothing to delete afterwards. Very large projects don't fit in a link; use Download ZIP for those. The packages your project uses travel too, and opening the link installs them there: the project is on screen straight away and the packages arrive a moment later. Opening a link replaces what's on screen, so Undo brings your own files back.
11Package management#
A package is Python code someone else has written that your program can import. Python already comes with a great deal of it — math, random, json, datetime, csv — and those need nothing from you: import math simply works. This section is about the rest.
The packages tab, the small icon to the left of the console tab, is where they live. It is quiet most of the time: a lone icon when there is nothing to say, a number once there is something to count, and the word packages in red if something failed.
Most of the time you do nothing#
Running your program installs what it imports. Write import pandas and press Run, and pandas is fetched before your first line executes. You do not have to ask for it, and you do not have to know it was missing.
The packages tab is for the times you want to look, or to decide: to see what your project is actually using, to pick a particular version, to take something out again, or to add a package before you have written the line that imports it.
Adding one#
Type a name into the field at the top and press Install or Enter. As you type, the line underneath tells you what will happen:
- "rich is in this Python already — 14.3.3, no download." It comes with primopy's Python and lands instantly, even with no connection.
- "Not built in. Installing it fetches it from PyPI, once." It is downloaded from the internet the first time and then belongs to your project.
- "PIL comes from a package called pillow. Install pillow." The name you
importis not always the name you install. This is the single most confusing thing about packages, and the field answers it rather than refusing you.
If you cannot think of one, the empty panel offers five to try.
Versions#
Each installed package has a small menu showing which versions can be used here; newest is the default. You can also type a version yourself: cowsay==6.1 for exactly that one, or cowsay<6 for anything older than 6.
If you ask for a version that contradicts the one you have, the panel says so — "This project has cowsay 6.1, which is not <6. Installing this replaces it." — and installing replaces it.
Dependencies#
Packages need other packages. Installing pandas quietly brings numpy, pytz and several more, and none of those are your doing. They are folded away behind the Dependencies button, which tells you how many there are and shows them if you want to look. Each says what pulled it in.
Taking one out#
The bin at the end of a row removes a package. Two things are worth knowing:
- Anything your program has already imported keeps working until Python restarts. A running Python cannot forget a module it has loaded. The row says so and stays on screen until you restart, rather than pretending the package has vanished.
- A package your program imports comes back when you run it. Removing
pandaswhileimport pandasis still in your code asks for two opposite things, and the run wins. Delete the import first.
Restart#
Restart, at the top right of the panel, starts Python again from scratch: variables, imports and anything half-removed are all cleared. Your packages are fetched back afterwards, so restarting does not cost you the downloads. Your files are untouched — this restarts Python, not primopy.
The list, and requirements.txt#
Your packages are remembered with your project. Reload the page and they are still listed; open the project from a share link and they arrive there too.
Python projects outside primopy usually keep this list in a file called requirements.txt, and if you want one, the button under the list writes it: Write package list into an explicit requirements.txt. It appears as an ordinary tab you can read and edit by hand, one package per line. From then on primopy keeps it up to date. You never need this file to use primopy; it is for handing your project to tools that expect it. If your project already has one — because somebody sent you theirs, or you wrote it yourself — primopy installs from it when you run.
What will not install#
Some packages are written partly in C and have to be compiled for the machine they run on. Nothing compiled runs inside a browser, so those cannot be installed here at all, and the panel says which case it is:
uvloop has to be compiled, and only packages written in pure Python can be installed in a browser. There is no version of it that runs here.
Some packages publish only their source on PyPI and no ready-made wheel, which is the form primopy installs. ColabTurtle is one. When the source is pure Python, primopy builds the wheel itself, in the browser, and installs that; the row says so:
PyPI has no wheel for ColabTurtle, only its source, so it was built here.
Often it is not the package you asked for that has to be compiled but something it asks for in turn. jupyturtle, the turtle for notebooks, lists jupyter among its needs, and jupyter is a whole notebook server with compiled parts under it. primopy then installs the package on its own, fetches each of its needs separately, and keeps what it can:
jupyturtle is here, without jupyter: it has to be compiled, and nothing compiled runs in a browser. jupyturtle works as long as it never actually reaches for it.
The turtle draws without a notebook server, so that is the whole story for it. A package that really does reach for what was left out fails on the import, with the usual ModuleNotFoundError.
That is a real limit rather than something to work around; the pure-Python world is large, and the several hundred packages that come with primopy's Python include numpy, pandas, matplotlib, scipy and scikit-learn, every one of them compiled, every one of them working, because they were built for this browser in advance.
12The Python console#
The console tab at the right of the file tabs turns the editor into an interactive Python prompt. You type one line, it answers immediately. It's a calculator for trying out ideas without writing a whole program:
>>> 3 * 7
21
>>> round(2.675, 2)
2.67
>>> "hello".upper()
'HELLO'
>>> help(round)- You don't need
print()here; the value is shown automatically. help(something)explains what a function does. Try it on anything you're unsure about.- ↑ / ↓ bring back earlier lines.
- After you've run your program, all its variables are available in the console. Run example 12, then type
itemsto look at the list it made. This is a great way to poke at your results. - While your program is stopped at a breakpoint, the console can step inside it. A bar at the top says where it stopped; press Attach and the prompt changes to the function's name, like
fib() >>>. Nowa + buses theaandbof that function at that moment,print()goes to the console rather than the program's output, anda = 100really changesa: press Continue and the program carries on with the new value. Packages can't be installed while the program waits. Continue or a step lets go again, and the prompt goes back to>>>. Shift+Enter in the editor then runs its line in there too. - Lines run from the editor with Shift+Enter share the console's variables (see Running one line at a time). The console shows a line like
▸ ran main.py:5–6for each; click it to go back to those lines. - Multi-line things like
forloops work: the prompt turns into..., and an empty line finishes the block. - Clear wipes the screen (your variables remain).
%pip install richworks here too, the line you may already know from notebooks. It does the same thing as the packages tab, and what it installs shows up there. A line copied from a README works as it is, quotes and all:%pip install "plotly[express]"installs plotly with its express extra.- More lines from notebooks work here:
%time sum(xs)runs the statement once and says how long it took;%timeit sum(xs)runs it many times and reports how long one run takes (-n 100fixes the number of loops,-r 3the number of runs);%run main.pyruns a file of your project in the console, so that its variables are there to look at afterwards, with anything after the name insys.argv;%prun sorted(xs)profiles the statement and lists where the time went (-s cumulativesorts differently,-l 10shows only the top lines). %wholists the console's variables, and%whoslists them with their type and a glimpse of their value (%who listshows only the lists).%resetforgets them all and leaves the screen alone, the opposite of Clear.%historyprints the lines you have typed, oldest first, ready to copy into a file (-n 10for the last ten).- A question mark in front asks what something is, as in R or a notebook:
?math.sqrtshows its type, how to call it and its docstring;?xs.appendand?d["k"]work too. Two question marks,??f, also show the source code, for a function of your own or of a library; where there is none, because the function is written in C, it says so. A?on its own lists what the console answers.
13Code completion#
While typing a name, press Tab (or Ctrl/Cmd+Space) and a list of matching names pops up. Typing a . after a name, like math., opens the list automatically, showing what's available.
Move with ↑ / ↓, accept with Tab or Enter, close with Escape. When available, a one-line description of the chosen item is shown underneath.
Completion works out its answers in the same Python that runs your programs, so while a program is running it has to wait its turn. Ask for the list then and it will say so instead of opening; it comes back by itself as soon as the run ends.
14Undo#
Ctrl/Cmd+Z undoes your typing, just like in any text editor, and Ctrl/Cmd+Shift+Z redoes it. Each file remembers its own history, even if you switch between files.
The Undo button in the header goes further: it also takes back things like deleting or renaming a file, loading an example, or clearing your saved work. When the next press would bring files back, the button turns dark blue and says so: Restore 2 files. So if you ever load an example and see your project vanish, the button is already telling you the way back. Hover it to see exactly what the next press will undo.
The only thing that can't be undone is a file that your own program overwrote.
15The tutor#
Tutor opens a chat with an AI tutor in the bottom right. It's set up to teach rather than to hand over answers: expect questions back, hints, and nudges in the right direction. It's a good place to ask "why is this wrong?" or "what does this line do?"
It is a language model, not an oracle. Ask the same thing twice and the wording will differ; sometimes it will answer straight out instead of asking back, and sometimes it will simply be wrong. When what it says and what your program does disagree, your program is right. Run it and step through it.
Type your question and press Enter (Shift+Enter for a new line). While the tutor is answering, Ask turns into Stop.
Two checkboxes under the input:
- send my code (on by default): sends the tutor the file you're editing, where your cursor is, your last output or error, the last few lines you typed in the console with what they printed, and, if you're paused in a step, the current line and its variables. With this on, "why is this wrong?" is a complete question.
- thinking: lets you see the tutor's reasoning, shown dimmed above its answer.
Share, in the tutor's title bar, copies a link that carries the conversation as well as the project, a way to show someone what you asked and what came back, without a screenshot. Only what was said travels: nothing is uploaded, but do read the conversation before you send the link on, the same as you would a screenshot.
New starts a fresh conversation, and History brings back an earlier one: nothing you asked is thrown away when you start again. Share → Download conversations saves the whole archive to your computer as text files. Quiz, between them, is for a project that comes with questions; that is section 16. Your tutor messages are the one thing that leaves your browser; they go to the tutor server, where they are used to write the answer and then dropped. Nothing you type is filed away, no record of how you are doing follows you from one day to the next, and nothing you ask is passed to your teacher. Everything else stays on your computer.
The tutor needs the page to be served from a web server; it doesn't work if you opened the page as a file from your disk.
A link can also carry a quiz for the tutor to ask you about the project. That is the next section.
16Quizzes#
A quiz is a set of questions a teacher has written about a project, and a link carries it the same way it carries the files. Open such a link and the tutor's pane opens with the quiz waiting in it: Start quiz, beside + New at the top of the pane, lit gold until you press it. Nothing is asked until you do. The tutor knows the questions, and where the teacher wrote one, it knows the reference answer too.
Press it and the first question is there in the conversation, where your answer goes under it. Answer in your own words in the chat, or write code in the editor and say so; the tutor sees your files as always. It helps you along rather than marking you: it says whether you are close, gives a hint when you ask for one, and keeps the reference answer to itself until you press I'm done, check my answer, when it compares what you said with what the teacher wrote, or I give up, show me the answer. Some questions are meant to be worked out rather than answered, and for those the tutor is meant to give you the next step instead of the finished thing, even when you give up. Nothing is scored and nothing is reported to anyone: the quiz is for you.
One question at a time, and the way is forward. Next question → appears above the box once you have said you are done with the one you are on, or given it up, so that a question is answered before the next one is seen. They stay in the conversation as you go, with your answers between them, to scroll back through. When every question is done, Finish: how did I do? asks the tutor to sum up: what held up, and what to go back to.
Some quizzes have no fixed questions at all, only a topic and a level. The tutor then makes them up as it goes, one per turn, and asks the first as soon as you start; Next question → asks for the next, once you are done with the one you have.
Taking a quiz is a conversation like any other, so it is left the way any conversation is left: + New starts an ordinary one, and History keeps the quiz, marked there with the quiz's own icon. Open it there and you are back in the quiz where you left off, as long as the project still holds the same questions; if it no longer does, the conversation is still there to read, but not to go on with: the box under it is shut, because an answer typed there would be about the project you have now. + New opens one you can ask in. Restart quiz — the same button, while you are in one — begins again at the first question, and what you did before stays in History. Loading an example or opening another link takes the quiz away with the project it came with, and the run you were in stays on the pane the same way: readable, and closed to further questions. A quiz survives a reload, and Undo after opening a quiz link brings your previous project back, quiz and all.
Try one here. This quiz has no files of its own, so the editor opens with an empty main.py to try things in:
Quiz: Loops, one at a time · the tutor asksStart quiz
for loops over range() and over lists; while loops · first weeks of Python, before functions; no comprehensions
Writing a quiz#
Anybody can write a quiz and share it with others. The pencil beside the quiz button, at the top of the tutor's pane, opens a sheet over the pane where you write the quiz as plain text, one line per thing. The editor stays beside it, so you can look at the code you are asking about while you write the questions:
Title: Reading a file
Topic: reading text files with open() and a with block
Depth: second week; open(), readlines(), strip(); nothing about encodings
Note: Insist on the with block. Question 3 is to be worked out with the student; never hand over the finished loop.
Q: What does f.readlines() give you, exactly?
A: A list with one string per line, each still ending in its newline character (except possibly the last).
Hint: Print it and look at the quotes.
Q: Why is `with open(...) as f:` better than `f = open(...)`?
Q: Write a loop that prints only the lines longer than five characters, without the trailing newline. Work it out step by step.- Title, Topic, Depth and Note each take a line, or several: a line without a key continues the one above it.
- Each Q: is a question. An A: under it is the reference answer, and a Hint: is what the tutor should say first when asked for one. Both are optional: without a reference the tutor assesses with what it knows, and a question with several good answers is treated as one.
- A question can run over several lines, so it can be a piece of code with gaps in it: write
for i in ____(10):and the blanks are what the student supplies. Mark a gap however you like. The code around it is given, and the tutor neither fills a gap in early nor rewrites the lines you wrote. - Topic and Depth are in your own words: what the questions are about, and what the student can be assumed to know or must not be asked for. Depth is a ceiling as well as a floor: "before functions" means the tutor will neither ask for one nor explain with one.
- Note is for the tutor alone: the class, what to insist on, which questions are to be worked out rather than answered.
- A quiz with only a Topic and a Depth is complete. The tutor then asks its own questions.
Return keeps the quiz with the project; there is nothing else to press, and Esc does the same. The button at the top of the pane then reads Start quiz, a reload keeps it, and every link made from the project carries it — including a link you make while the sheet is still open, which carries what is on the sheet at that moment. Editing a quiz you are in the middle of taking changes the questions without ending the conversation.
The sheet is silent while what you have written can be a quiz, and says what is wrong when it cannot: returning then leaves the sheet where it is, with the reason beside the button, and a link waits until the quiz is finished. Remove, beside the sheet's name, takes the quiz out of the project again, and so does emptying the box; it is also the way out of writing you have decided against, finished or not. Undo puts back the quiz you had before writing, editing or removing.
Note the link is the whole quiz, reference answers included: anyone who can read a link can read them, so a reference answer is there to help the student check themselves, not to be kept from them.
17Documents and notebooks#
A link, or a Markdown file, Jupyter notebook or Quarto file you drop on the editor, can open as a document: text to read on a tab of its own, in place of the editor, with examples you run where they stand with Run here. The cells share their names with the console, a cell first runs the ones above it that have not run yet, and the files a document brings become tabs among yours. Teachers write documents in Markdown and hand them out as a link.
All of it is on its own page: documents and notebooks, which you can also open in the editor as a document and try it out there.
18Is my work saved?#
Yes, automatically, in your browser, about half a second after every change. Reopen the page and everything is as you left it: all files, breakpoints, theme, layout, console history and the recent tutor conversation. You'll see Your files are back.
Two things to keep in mind:
- It's saved in this browser on this computer. Another browser or another computer starts empty. To move work, use Share → Download ZIP, or, in a Chromium browser, keep the project in a folder on your computer.
- Some browser setups block saving, for instance when the page is opened directly from a file, or inside another site. Hover the logo to check: it says either files saved in this browser or files not saved. If it's the latter, use Share → Download ZIP before closing the page.
Nothing you write is sent anywhere, apart from messages to the tutor.
19Changing the layout and theme#
Every boundary between two areas can be dragged: the sidebar's width, the height of the tape, the console, the call stack, and the tutor. On a narrow screen the areas stack on top of each other instead.
The ◐ button at the right of the header switches the look: it steps from auto (follow your system) to light (☀) to dark (☾).
20Keyboard shortcuts#
Anywhere#
| Key | Action |
|---|---|
| Ctrl/Cmd+Enter | Run (recording, if Record is on); Continue, while stopped at a breakpoint |
| Ctrl/Cmd+Shift+Enter | Run and record, whether Record is on or not |
| ← / → | Step back / forward (when not typing) |
| Shift+← / Shift+→ | A smaller step back / forward, inside the line |
| Ctrl/Cmd+Z | Undo |
Editor#
| Key | Action |
|---|---|
| Tab | Indent, or complete the name being typed |
| Shift+Tab | Un-indent |
| Enter | New line at the same indentation |
| Shift+Enter | Run the line the cursor is on, or the selected lines (record them while Record is on); while a recording is up, take the tape to that line, and to its next visit on each further press |
| Ctrl/Cmd+Space | Open the completion list |
| Ctrl/Cmd+F | Find and replace in this file |
| Enter / Shift+Enter in the search field | Next / previous match |
| F3 or Ctrl/Cmd+G | Next match (with Shift: previous) |
| Escape | Close the search bar |
| Cmd+Option+[ / ] (Mac), Ctrl+Shift+[ / ] | Fold / unfold the block that starts on the cursor's line |
| Ctrl+Alt+[ / ] (Mac: Ctrl+Option) | Fold / unfold every block in the file |
Console#
| Key | Action |
|---|---|
| Enter | Run the line |
| Shift+Enter | Add a line without running |
| ↑ / ↓ | Earlier lines |
| Tab | Complete |
| Escape | Abandon an unfinished block |
Tutor#
| Key | Action |
|---|---|
| Enter | Send |
| Shift+Enter | New line |
Guided tour#
| Key | Action |
|---|---|
| Enter or → | Next stop |
| Escape | End the tour |
21Things primopy can't do#
- Recording stops after 5,000 steps. For long-running programs switch Record off.
- The Variables box shows at most 26 variables, and long values are shortened. Large
numpyarrays andpandastables are summarised by their size rather than shown in full. - No internet from your program. Downloading web pages or data from inside Python won't work; a browser safety rule.
- No windows or graphical programs beyond plots and the Draw canvas, which is also where
turtledraws.tkinterandpygameare not available. - Everything lives in one browser. Use Share → Download ZIP to move work elsewhere, or a folder on your computer in a Chromium browser such as Chrome, Edge, Brave or Opera.
- The tutor can be wrong. It is a language model: its answers are not fixed, and it can be mistaken about your code. Check what it tells you by running the program.
22When something goes wrong#
It never gets past "Downloading Python". Python is fetched from the internet the first time. Check your connection and reload.
My files are gone after reloading. Saving is blocked in this browser setup; hover the logo to check. Ask whoever gave you the page for a proper web address, and use Share → Download ZIP in the meantime.
open("data.txt") says FileNotFoundError. The name must match a tab exactly, capital letters and folder names included.
import helpers doesn't work. The file must be called helpers.py, and you write import helpers without the .py. For a folder, it needs a file called __init__.py inside (see example 11).
The tutor says it can't reach the server. The page has to be opened from a web address, not from a file on your disk.
My program is stuck, or prints for ever. Press Stop; while a program runs, that is what the Run button becomes. An endless loop stops there and then, with your files and everything the run had built up untouched; there is no need to reload the page. With Record on, a run also stops itself after 5,000 steps.