Python Playground
Write, run, and share Python 3 in your browser. Real CPython, compiled to WebAssembly, running on its own thread.
Output
Nothing yet. Press Run, and printed output, errors, and the value of the last line land here.
What is a Python playground?
A Python playground runs Python inside a web page. Nothing is installed, no virtual environment is created, and no file is saved somewhere you will have forgotten by Thursday. That removes more friction for Python than it does for most languages, because setting up Python is never one question. It is which Python, whose Python, and whether that particular one already has the package you were about to reach for.
This one is not an approximation of Python. It runs Pyodide, which is CPython 3.14 compiled to WebAssembly, so what executes is the reference interpreter and its own standard library rather than a subset, a transpiler, or a language that resembles Python. The editor is Monaco, the one out of VS Code. Your code runs in a Web Worker on its own thread, printed output and errors land in the panel below, and a link will carry the whole script when you want to hand it to somebody. Nothing is uploaded, because there is no server here to upload it to.
When to use it
Most visits start with a snippet somebody else wrote. A tutorial shows a comprehension worth watching run, or the itertools docs have a recipe you only half believe, and the honest price of checking is an environment you will throw away five minutes later. Paste it here instead. The same goes for the small arguments that come up while writing code: what a slice does at the edges, whether a dict keeps the order you think it does, what happens when you sort tuples of different lengths.
The second is cutting a bug down to something you can send. A function in a large project behaves strangely and you suspect the function rather than everything around it. Lift it out on its own, hand it the input that broke it, and a few seconds tell you whether the fault travelled with the function or belongs to something around it. A reduction that runs on its own is also what a good Stack Overflow question needs, and this hands you one as a link the person answering can run and edit rather than retype.
Third is a file somebody sent you. A coworker emails a CSV and wants it grouped a particular way. Paste the rows into a string, read them with pandas, and you have the answer before you would have finished deciding where to put a scratch script. The data never leaves your machine, which is the part worth knowing when the CSV has real names in it. Then there is explaining something to another person. A link that opens with a working example already in it lands differently from a code block in a chat message, which is why it works for teaching a data structure, walking through a review comment, or answering a question in a team channel. For JavaScript, the JS Playground is the same tool with a different engine, and the HTML, CSS and JS Playground adds a live preview when what you are building is a page rather than a program.
How this tool works
Python here is Pyodide, a build of CPython compiled to WebAssembly, pinned to the release carrying Python 3.14.2. That is the reference interpreter rather than something written to look like it, so the things that usually break in a browser Python do not: decimal arithmetic, generators, dataclasses, pattern matching, the standard library, and the traceback you get when it goes wrong are all the real ones. Nothing is sent anywhere to be executed. The interpreter is downloaded to your browser and runs there.
It runs in a Web Worker rather than a sandboxed frame, which is the one thing built differently from the other two playgrounds on this site. The reason is the limit the JS Playground documents about itself: an iframe and the page holding it take turns on one thread, so a loop that never yields locks the tab, and the timeout meant to catch it cannot fire either, because a timer also needs a turn. Python invites that kind of code more than JavaScript does. A worker has its own thread, so the page stays responsive while your code spins, the Stop button stays clickable, and terminating the worker actually ends what it was doing. A worker also has no document and no window, so nothing you run can reach the page around it.
The runtime is about 6 MB over the network and 12 MB once unpacked, and none of it downloads until you press Run for the first time. Opening the page and leaving costs you the editor and nothing else. That first load is slow enough to need a progress bar, so there is one, and it counts real bytes rather than pacing: the two large files are fetched and measured before Python starts, which also warms the browser cache Python then reads them from. After that your browser has them, so later runs in the session start straight away and a later visit skips the download.
Output is captured three ways. Anything printed to stdout arrives a line at a time, anything on stderr arrives the same way and is marked as an error, and if the last line of your code is an expression, its repr is printed underneath the way a REPL prints it. A bare name on the final line therefore shows you what it holds, and a script ending in a loop or an assignment prints nothing extra. When something raises, the traceback is shown with Pyodide's own frames removed, because the top four frames of every traceback here belong to the evaluator explaining how your code was compiled, which is noise. What is left points at your line numbers. Output stops after a thousand lines so one runaway loop cannot fill the panel, and the code keeps running until you stop it.
The standard library is bundled and available immediately. Anything past it loads when you import it: the Pyodide distribution carries prebuilt WebAssembly wheels for more than three hundred packages, and importing one fetches it, which is what the pause on a first import of numpy is. numpy, pandas, scipy, matplotlib, scikit-learn, sympy, Pillow, BeautifulSoup, lxml, and SQLAlchemy are all in that set. For anything outside it, micropip installs pure-Python wheels from PyPI while your code runs. What cannot work is a package with a compiled extension nobody has built for WebAssembly, since there is no compiler here to build one with.
Two limits are worth knowing before they surprise you. There is no real filesystem: open() writes into an in-memory filesystem belonging to the interpreter, which disappears when you leave, so a script expecting a path from your machine will not find it. And there are no sockets: a socket call will appear to connect and then hang rather than fail, so it costs you the thirty-second cap before you learn anything. Anything reaching the network should go through the browser. The requests library is patched to work that way and does return real responses, but it inherits the browser's rule rather than Python's: a host sending permissive CORS headers answers, and a host that does not is refused, whatever the URL looks like from Python's side. A run is capped at thirty seconds, longer than the JavaScript playground's five because a numeric loop taking twenty seconds is doing its job. Unlike that cap, this one always fires, since the timer runs on a different thread from the code it is timing.
Sharing is a link and nothing more. Pressing Share encodes the editor into a query parameter and copies the address, and whoever opens it gets the same script in the same editor. No account is involved, no paste is filed away, and no database row exists that anyone could later delete. That cuts both ways: a link you have sent cannot be taken back, and whoever holds it can read the code. The other cost is size, since the script rides in the address. A long one makes an awkward link, and a few chat clients mangle how they display it even though it still works.
Examples
Grouping a pasted CSV with pandas
Inputimport pandas as pd from io import StringIO raw = """region,rep,amount west,ana,1200 east,bo,450 west,cyd,300 east,ana,900 """ df = pd.read_csv(StringIO(raw)) totals = df.groupby("region")["amount"].sum().sort_values(ascending=False) print(totals.to_string()) totals.idxmax(), int(totals.max())OutputLoading numpy, pandas, python-dateutil, pytz, six Loaded numpy, pandas, python-dateutil, pytz, six region west 1500 east 1350 Result: ('west', 1500)The two loading lines are pandas and its dependencies arriving, which happens once per session and took about two seconds here. Nothing was installed and nothing was uploaded: the CSV is a string in the editor and the answer is computed in your browser, which is the difference that matters when the file has real names in it.
The last line prints itself
Inputwords = "the quick brown fox jumps over the lazy dog".split() by_length = {} for w in words: by_length.setdefault(len(w), []).append(w) print({k: by_length[k] for k in sorted(by_length)}) [w.upper() for w in words if len(w) > 4]Output{3: ['the', 'fox', 'the', 'dog'], 4: ['over', 'lazy'], 5: ['quick', 'brown', 'jumps']} Result: ['QUICK', 'BROWN', 'JUMPS']Two different things reached the panel. The dict was printed, so it arrived as output. The comprehension on the last line was never printed at all: it is a bare expression, and its repr is shown because that is what a REPL does with a value nobody asked to see. Ending a script with a name is the quickest way to look inside it.
What a traceback looks like
Inputdef average(values): return sum(values) / len(values) rows = [12, 7, 30] print(average(rows)) print(average([]))Output16.333333333333332 Error: ZeroDivisionError: division by zero Traceback (most recent call last): File "your code", line 7, in <module> File "your code", line 2, in average ZeroDivisionError: division by zeroThe first call printed before the second one failed, so you can see how far it got. The traceback is shorter than the one Pyodide throws: four frames of its evaluator sat above these two, describing how your code was compiled rather than what your code did, and they are dropped. Line 7 and line 2 are line 7 and line 2 in the editor.
Frequently asked questions
Which version of Python is this?
CPython 3.14.2, through Pyodide 314.0.6. It is the reference interpreter compiled to WebAssembly rather than a reimplementation, so language features track whatever that release supports: pattern matching, the walrus operator, exception groups, and the rest behave the way they do on your machine. The version is pinned rather than tracking the newest build, for the same reason the editor is. A release that changes how the runtime loads breaks the page rather than improving it, so upgrades happen deliberately.
Which packages can I import?
The whole standard library, immediately, plus anything in the Pyodide distribution, which is over three hundred prebuilt packages including numpy, pandas, scipy, matplotlib, scikit-learn, sympy, Pillow, BeautifulSoup, lxml, networkx, and SQLAlchemy. You do not install those. Importing one fetches it, which is what the brief pause on a first import is. Past that list, micropip installs pure-Python wheels from PyPI while your code runs. The hard boundary is packages with compiled extensions that have no WebAssembly build, since there is no compiler here to make one.
Why is the first run slow?
Because a Python interpreter has to arrive before it can run anything, and this one is about 6 MB compressed. Nothing downloads while you are reading the page or writing code. It starts when you first press Run, which is why the progress bar appears then rather than on load. Once it is down, your browser caches it, so the rest of the session runs immediately and a later visit skips the download. Importing something large like pandas adds a second or two the first time, and nothing after that.
Where does my code go?
Nowhere, unless you press Share. There is no autosave, no draft kept on our side, and no request that carries what you typed off your machine. The traffic this page does make runs the other way: the interpreter and any packages you import are downloaded to you. Share is the single exception, and even it puts the script in the address bar rather than in storage, which means the link somebody holds is the only copy that exists anywhere but your browser.
Can I use pip?
Not pip, but micropip does the useful part of the job from inside your code. Import micropip, await micropip.install on the package name, and the import that follows will work. It fetches wheels from PyPI at runtime. What it will install is pure-Python wheels, plus the WebAssembly builds Pyodide already ships. A package carrying a C extension with no WebAssembly build will fail, and it will say so rather than half working. There is no install step to run before your code and no environment that persists, so the install line lives in the script alongside everything else.
Can it read files from my computer?
Not in this version. open() works, but it writes into a filesystem living inside the interpreter that disappears when you close the tab, so a path from your machine will not resolve. For now the way in is to paste the data, which is fine for a CSV you want to reshape and awkward for anything large. Everything stays local either way: pasted data is processed in your browser and never sent anywhere, which is the property that makes pasting a work file reasonable in the first place.
Could we run one of these on our own site?
Yes, and the versions worth building are rarely a general playground. What teams ask for is this shape aimed at their own work: a page where a customer can try your API in Python before signing up, where support can run a checked script against a sanitised extract instead of asking an engineer, or where the documentation executes instead of showing a screenshot of something that executed once. Python with no backend also changes the calculation when the data is sensitive, since nothing has to be uploaded to be processed. Zinc Online Solutions builds these. Say who would be using it and what you want them to walk away with, and we will scope it from there.