You've used an interpreter already when we navigated through folders in the terminal (command line) to run the python file by typing in python testfile.py. When you did that, you ran a command through an interpreter. The terminal window is sometimes called the command line, DOS window or another implementation of a powerful interpreter in Windows is called PowerShell. In all cases, you enter a command, possible some switches or arguments, and "something" happens - through the interpreter.
windows terminal- commands are interpreted
Python is often described as an interpreted language—one in which your source code is translated into native CPU instructions as the program runs—but this is only partially correct. Python, like many interpreted languages, actually compiles source code to a set of instructions for a virtual machine, and the Python interpreter is an implementation of that virtual machine. This intermediate format is called "bytecode."
So those .pyc files Python leaves lying around aren't just some "faster" or "optimized" version of your source code; they're the bytecode instructions that will be executed by Python's virtual machine as your program runs.
Let's look at an example. Here's a classic "Hello, World!" written in Python:
A view of the Disassembler for Python bytecode
The function dis.dis() will disassemble a function, method, class, module, compiled Python code object, or string literal containing source code and print a human-readable version. Another handy function in the dis module is distb(). You can pass it a Python traceback object or call it after an exception has been raised, and it will disassemble the topmost function on the call stack at the time of the exception, print its bytecode, and insert a pointer to the instruction that raised the exception.
It's also useful to look at the compiled code objects Python builds for every function since executing a function makes use of attributes of those code objects.
If you replace that hello() function and run it, you can see what bytecode is generated. It looks a bit odd- reminiscent of assembly perhaps Setting aside curiosity for curiosity's sake, understanding Python bytecode is useful in a few ways.
First, understanding Python's execution model helps you reason about your code. People like to joke about C being a kind of "portable assembler," where you can make good guesses about what machine instructions a particular chunk of C source code will turn into. Understanding bytecode will give you the same ability with Python—if you can anticipate what bytecode your Python source code turns into, you can make better decisions about how to write and optimize it.
Second, understanding bytecode is a useful way to answer questions about Python. For example, I often see newer Python programmers wondering why certain constructs are faster than others (like why {} is faster than dict()). Knowing how to access and read Python bytecode lets you work out the answers (try it: dis.dis("{}") versus dis.dis("dict()")).
Finally, understanding bytecode and how Python executes it gives a useful perspective on a particular kind of programming that Python programmers don't often engage in: stack-oriented programming. If you've ever used a stack-oriented language like FORTH or Factor, this may be old news, but if you're not familiar with this approach, learning about Python bytecode and understanding how its stack-oriented programming model works is a neat way to broaden your programming knowledge.