A Python Interpreter Written in Python

An exploration of Byterun, a compact Python interpreter implemented in Python, which reveals the inner workings of CPython, bytecode execution, and stack machine architecture.
Allison is an engineer at Dropbox, where she helps maintain one of the largest networks of Python clients in the world. Before Dropbox, she was a facilitator at the Recurse Center, a writers retreat for programmers in New York. She's spoken at PyCon North America about Python internals and loves weird bugs. She blogs at akaptur.com.
Byterun is a Python interpreter implemented in Python. Through my work on Byterun, I was surprised and delighted to discover that the fundamental structure of the Python interpreter fits easily into the 500-line size restriction. This chapter will walk through the structure of the interpreter and give you enough context to explore it further. The goal is not to explain everything there is to know about interpreters—like so many interesting areas of programming and computer science, you could devote years to developing a deep understanding of the topic.
Byterun was written by Ned Batchelder and myself, building on the work of Paul Swartz. Its structure is similar to the primary implementation of Python, CPython, so understanding Byterun will help you understand interpreters in general and the CPython interpreter in particular. Despite its short length, Byterun is capable of running most simple Python programs.
Before we begin, let's narrow down what we mean by "a Python interpreter". The word "interpreter" can be used in a variety of different ways when discussing Python. Sometimes interpreter refers to the Python REPL, the interactive prompt you get by typing python at the command line. In this chapter, "interpreter" has a more narrow meaning: it's the last step in the process of executing a Python program.
Before the interpreter takes over, Python performs three other steps: lexing, parsing, and compiling. Together, these steps transform the programmer's source code from lines of text into structured code objects containing instructions that the interpreter can understand. The interpreter's job is to take these code objects and follow the instructions.
You may be surprised to hear that compiling is a step in executing Python code at all. Python is often called an "interpreted" language, as opposed to a "compiled" language like C or Rust. However, this terminology isn't as precise as it may seem. Most interpreted languages, including Python, do involve a compilation step. The reason Python is called "interpreted" is that the compilation step does relatively less work than in a compiled language.
Byterun is a Python interpreter written in Python. This may strike you as odd, but it's no more odd than writing a C compiler in C. Writing a Python interpreter in Python has both advantages and disadvantages. The biggest disadvantage is speed. However, Byterun was designed originally as a learning exercise. The biggest advantage to using Python is that we can more easily implement just the interpreter, and not the rest of the Python run-time.
The Python interpreter is a virtual machine, meaning that it is software that emulates a physical computer. This particular virtual machine is a stack machine: it manipulates several stacks to perform its operations. The Python interpreter is a bytecode interpreter: its input is instruction sets called bytecode. Bytecode is an intermediate representation of Python code.
To make this concrete, let's start with a very minimal interpreter. This interpreter can only add numbers, and it understands just three instructions: LOAD_VALUE, ADD_TWO_VALUES, and PRINT_ANSWER. Suppose that 7 + 5 produces this instruction set:
what_to_execute = {
"instructions": [("LOAD_VALUE", 0),
("LOAD_VALUE", 1),
("ADD_TWO_VALUES", None),
("PRINT_ANSWER", None)],
"numbers": [7, 5]
}
The interpreter will begin by executing the first instruction, LOAD_VALUE, and pushing the first number onto the stack. Next it will push the second number onto the stack. For the third instruction, ADD_TWO_VALUES, it will pop both numbers off, add them together, and push the result onto the stack. Finally, it will pop the answer back off the stack and print it.
Source: Hacker News















