Low-Level Compiled Languages and Performance
The answers by @hpaulj and @jeevcat are correct.
But the story of whether Python is compiled is more complex.
First, it is true that well written code in C++ is far faster than well written Python code. And that compiled code generally allows for faster calculations.
But the reason is not because the code is compiled, per se. It's because these compiled languages are typically also lower level languages that let you manipulate memory directly, avoid garbage collection, etc. Moreover, to allow for Python dynamicism and simplicity, everything is an object. So a Python list, for instance, is an object with a list of references to other objects "scattered" throughout memory. This is (obviously) less computationally efficient than a memory block with all values in the list next to each other.
And, as the others mentioned, the Python code just calls (talks to) this other, more efficient C code.
Is Python Compiled?
But there is a more interesting question. Is Python compiled or not? A few people may unwittingly claim that it is not compiled. This is not strictly true. Any time you import a package or module, it will invisibly be compiled and saved if it has not already been compiled. (You will likely not even notice any compilation happening.)
You can see this happen: any .pyc
file (a file ending in .pyc
instead of .py
) is a compiled Python file. Try to open a .pyc
file in an editor or via cat
. You'll see that it is a binary file and will look like gibberish.
Looking at the Invisible Creation of Compiled Python Code
How to create compiled Python code?
Let's say that you have the following folder structure:
❯ tree -L 1
.
├── __pypackages__ # This is a folder, the rest are files
├── addressbook.proto
├── addressbook_pb2.py
├── pdm.lock
├── protobuf-python-3.17.3.tar.gz
├── pyproject.toml
└── readme.txt
(The above structure above contains the Python Google Protocol Buffer example, using the modern PDM package manager structure.)
We can see that the only Python module (file) is addressbook_pb2
. So, let's import that file:
❯ python
Python 3.9.7 (default, Oct 13 2021, 06:45:31)
[Clang 13.0.0 (clang-1300.0.29.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import addressbook_pb2
>>> [exit out of Python]
❯
I did nothing except for quickly import the file (module) addressbook_pb2.py
. But just that simple import created an entire "compiled code folder" called __pycache__
with the compiled module in it:
❯ tree -L 1
.
├── __pypackages__
├── __pycache__ # This is the folder that was auto-generated
├── addressbook.proto
├── addressbook_pb2.py
├── pdm.lock
├── protobuf-python-3.17.3.tar.gz
├── pyproject.toml
└── readme.txt
Now we'll look to see what is in that __pycache__
folder:
❯ ll __pycache__ # `ll` is my shortcut for `ls -al`, it's a common shortcut
total 8
drwxr-xr-x 3 mikewilliamson staff 96B Oct 30 21:43 .
drwxr-xr-x 34 mikewilliamson staff 1.1K Oct 30 21:43 ..
-rw-r--r-- 1 mikewilliamson staff 3.2K Oct 30 21:43 addressbook_pb2.cpython-39.pyc
❯
Notice that the file addressbook_pb2.cpython-39.pyc
is in there. The stem is the name of the module (addressbook_pb2
). But it also has the .cpython-39.pyc
extension. This tells us a few things:
- It is compiled code... that's what the
.pyc
on the end means
- It is compiled using
cpython-39
, meaning that it is the CPython "flavor" of Python (the most ubiquitous), version 3.9.