Python Under the Hood

Cpython consists of many things:

CPython executable
├── parser
├── compiler: source code -> bytecode
├── bytecode interpreter / VM: runs bytecode
└── runtime: objects, memory, imports, exceptions, GC, etc.

When we run our python program,

your .py file

[compiler part of Python]

bytecode

[Python virtual machine / runtime]

executes bytecode
  • Python Bytecode is not machine code, it’s instructions for the Python interpreter.

What happens when in my python file / REPL I import other files?

Like as soon as I import, does that python program with all its defined functions get compiled to bytecode?

  • Yes, the function bodies are compiled, but not run yet

Python Errors

Errors happen at either:

  • compile time
  • execution time

Most other errors happen at runtime.

syntax/indentation errors → compile time
name/type/value errors → execution time

Compile-Time Errors

Caught before anything runs. Usually syntax/indentation errors.

x =
# SyntaxError
def f():
print("hi")
 
IndentationError
 

Execution-Time Errors

Code compiles, then fails while running.

print("before")
 
x = 1 / 0
 
print("after")

Output:

before
ZeroDivisionError

Common runtime errors:

NameError
TypeError
ValueError
ZeroDivisionError
IndexError
KeyError
AttributeError

Python footgun

Python footgun: top-level imports run immediately

In Python, import module executes all top-level code in that module once.

So a global import can crash your program even if you never call the code that uses it.

# renderer.py
 
import pango  # runs immediately when renderer.py is imported
 
def render_text():
    ...
# main.py
 
import renderer  # can fail here because pango import runs now

Better:

# renderer.py
 
def render_text():
    import pango  # only required when this function is called
    ...

Key idea:

Top-level import = dependency required to load the whole module.
Local import = dependency required only for that specific code path.