R is for REPL - Python A to Z
Code in this blog post was written with versions: Python: 3.14, django: 6.1
Python A-Z is a blog series about Python. Each day, I share insights, ideas and examples for different parts of Python development that match with the letter of the day. Blaugust is an annual blogging festival in August where the goal is to write a blog post every day of the month.
Completely by accident, I ended up with a four day streak of acronym titles. ORM, PEP, Q and today REPL. We developers sure love acronyms.
REPL (read-eval-print loop) is one of those things that make programming languages more approachable to me. I love being able to build my code bit-by-bit and have an easy way to test ideas out or double check on syntax without having to build all the infrastructure around them to run.
If you start Python without any arguments, you end up in a REPL:
$ python
Python 3.14.6 (main, Jun 23 2026, 15:46:31) [Clang 22.1.3 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
The >>> tells you it is accepting your
input and whatever you type there will be executed as Python code.
>>> 2+1
3
>>> print('Hello world!')
Hello world!
You can write practically any Python code in the REPL (I’m not sure if there are some special edge cases). You can define functions, run loops, read files and whatever you need.
It’s not only useful for iterating small parts of code that ends up in your source code. It’s also handy for doing interactive stuff: I often start a Python REPL instead of running commands in zsh shell because I’m so much more comfortable with Python than shell scripting.
Python’s REPL is not quite as good as Clojure’s but if you want a convincing argument for why REPLs are great, check out Ykä’s demo from Aurajoki Overflow showing how good Clojure has it. A lot of it applies to Python as well and is very close to the way I work iteratively with code!
Django shell
Starting a regular REPL in your project is nice for general Python stuff but it’s hard to get the data of your program easily into it. That’s not a problem if you’re building with Django.
By running
$ python manage.py shell
29 objects imported automatically (use -v 2 for details).
Python 3.14.6 (main, Jun 23 2026, 15:46:31) [Clang 22.1.3 ] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> Card.objects.first()
<Card: Alakazam (1) <base1-1>>
It loads all of your models and urls and stuff into the REPL session and connects to your database so you can immediately start writing queries to tinker with or explore your data.
I practically live in the Django shell when I’m developing my Django projects. Most of the code, before it lands into a source file, starts its life as experiments in the shell. It’s way faster to iterate over ideas and code as the feedback loops is immediate instead of running the full app or tests every time early in the development of a feature.
Debugging Python
When you use breakpoint() to
debug your code, you land on a REPL with the context, stack and data of your running program
and you can explore it, advance your code and figure out what’s wrong.
To become more efficient in debugging, it’s good to get familiar with REPL usage in general so when you’re in a stressful situation with a bug, you don’t have to focus on learning how to use the interface.
Startup script
Once you get more comfortable with Python, take a look at PYTHONSTARTUP and writing your own startup script. With it, you can run code before the REPL session starts so you can import all the libraries you use all the time (mine starts with ~30 lines of imports for standard library stuff I’m reaching all the time and has a couple of “if installed, import this” imports as well) and set up helper functions.
One super handy tip from mine:
# With these, you can copy-paste JSON into REPL and
# be considered a valid dictionary
null = None
true = True
false = False
For Django, I also import helpers like
Avg, Count, F, Max, Min, Q, Sum and
Value if Django is installed as I never
remember where they are imported.
All in all, mine is about 350 lines, 90% of which is importing stuff if they exist in the environment.
Another cool trick is defining a pretty printer with some extra syntax.
from pprint import PrettyPrinter
class Printer(PrettyPrinter):
def __call__(self, *args, **kwargs):
super().pprint(*args, **kwargs)
def __truediv__(self, other):
super().pprint(other)
def __rtruediv__(self, other):
super().pprint(other)
def __repr__(self):
return repr(pprint)
pp = Printer()
pp.__doc__ = pprint.__doc__
With the __truediv__ , you can do
pp/obj where
obj is anything and it will pretty print
it.
If something above resonated with you, let's start a discussion about it! Email me at juhis@hamatti.org and share your thoughts. This year, I want to have more deeper discussions with people from around the world and I'd love if you'd be part of that.