Everything is an Object
This isn't just a slogan — it's literally true. In CPython (the standard Python implementation), every value is a C struct on the heap. Integers, strings, functions, classes, modules, even None and True — they're all objects with an identity (id()), a type (type()), and a reference count.
x = 42
print(f"Value: {x}")
print(f"Type: {type(x)}")
print(f"ID: {id(x)}") # Memory address
print(f"Size: {x.__sizeof__()} bytes")
# Functions are objects too
def greet():
"""Say hello."""
return "Hi!"
print(f"\nFunction type: {type(greet)}")
print(f"Function name: {greet.__name__}")
print(f"Function doc: {greet.__doc__}")
print(f"Function code: {greet.__code__.co_varnames}")
Value: 42 Type: <class 'int'> ID: 140234866048336 Size: 28 bytes Function type: <class 'function'> Function name: greet Function doc: Say hello. Function code: ()
The GIL (Global Interpreter Lock)
The GIL is CPython's most misunderstood feature. It's a mutex that allows only one thread to execute Python bytecode at a time. This exists because CPython's memory management (reference counting) is not thread-safe.
What GIL Does
Prevents multiple threads from running Python code simultaneously. Only one thread holds the GIL at any time.
What GIL Doesn't Do
It does NOT prevent concurrency. Threads release the GIL during I/O (network, disk), so I/O-bound code benefits from threading.
Why It Exists
Simplifies CPython's C API and makes reference counting safe. Removing it would slow down single-threaded code.
How to Work Around It
Use multiprocessing for CPU-bound parallelism. Use asyncio or threading for I/O-bound concurrency.
import threading
import time
counter = 0
lock = threading.Lock()
def increment():
global counter
for _ in range(100_000):
with lock: # Need this for thread safety!
counter += 1
# Even with GIL, compound operations like += aren't atomic
t1 = threading.Thread(target=increment)
t2 = threading.Thread(target=increment)
t1.start(); t2.start()
t1.join(); t2.join()
print(f"Counter: {counter}") # 200000 (correct with lock)
Counter: 200000
GIL-Free Python?
Python 3.13 (2024) introduces an experimental "free-threaded" mode that removes the GIL. This is a huge development but still experimental. Other implementations like PyPy, Jython, and GraalPy don't have a GIL.
Memory Management
CPython uses two mechanisms for memory management: reference counting (primary) and garbage collection (for cycles).
Reference Counting
Every object has a reference count — the number of variables/containers pointing to it. When the count drops to zero, the memory is freed immediately:
import sys
a = [1, 2, 3]
print(f"After creation: {sys.getrefcount(a)}") # 2 (a + getrefcount arg)
b = a # Another reference to the same list
print(f"After b = a: {sys.getrefcount(a)}") # 3
c = [a, a] # Two more references
print(f"After c = [a,a]: {sys.getrefcount(a)}") # 5
del b # Remove one reference
print(f"After del b: {sys.getrefcount(a)}") # 4
# When refcount hits 0, memory is freed immediately
# (no need to wait for garbage collector)
After creation: 2 After b = a: 3 After c = [a,a]: 5 After del b: 4
Garbage Collection for Cycles
Reference counting can't handle circular references (A points to B, B points to A). Python's garbage collector periodically scans for and breaks these cycles:
import gc
# Create a circular reference
a = []
b = [a]
a.append(b) # a -> b -> a (cycle!)
# Reference counting alone can't free these
del a, b # Refcounts don't reach zero due to the cycle
# The garbage collector handles it
collected = gc.collect()
print(f"Collected {collected} objects")
Collected 2 objects
is vs == — Identity vs Equality
a = [1, 2, 3]
b = [1, 2, 3]
c = a
# == checks VALUE equality
print(f"a == b: {a == b}") # True (same content)
# is checks IDENTITY (same object in memory)
print(f"a is b: {a is b}") # False (different objects)
print(f"a is c: {a is c}") # True (same object)
# Small integer caching (-5 to 256)
x = 256
y = 256
print(f"\n256 is 256: {x is y}") # True (cached)
x = 257
y = 257
print(f"257 is 257: {x is y}") # False (not cached!)
# Always use == for comparison, is only for None
print(f"\nNone is None: {None is None}") # True (singleton)
a == b: True a is b: False a is c: True 256 is 256: True 257 is 257: False None is None: True
== to compare values. Use is only for None checks (x is None). Never use is to compare integers, strings, or other values — caching behavior is an implementation detail that can change.Bytecode — What Python Actually Runs
When you run a Python file, it's first compiled to bytecode — a low-level instruction set for Python's virtual machine. You can inspect it with the dis module:
import dis
def add(a, b):
return a + b
dis.dis(add)
2 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 BINARY_ADD
6 RETURN_VALUEEach .py file is compiled to bytecode and cached in __pycache__/*.pyc files. This is why the first import is slower (compilation) but subsequent imports are fast (cached bytecode).
How Imports Work
import sys
# Python searches these directories for modules:
for i, path in enumerate(sys.path[:5]):
print(f" {i}: {path}")
# A module is only loaded ONCE — subsequent imports return the cached version
print(f"\nLoaded modules: {len(sys.modules)}")
print(f"'os' loaded? {'os' in sys.modules}")
# You can see where a module was loaded from
import json
print(f"\njson location: {json.__file__}")
🔍 Deep Dive: CPython vs Other Implementations
CPython is the reference implementation, written in C. But there are others: PyPy uses a JIT compiler and can be 10-100x faster for some workloads. Jython runs on the JVM. GraalPy runs on GraalVM. MicroPython runs on microcontrollers. Cinder (Meta) adds lazy imports and other optimizations. When we talk about "Python internals," we usually mean CPython specifically.
⚠️ Common Mistake: Relying on Implementation Details
Wrong:
if x is 42: # Relies on integer caching!
print("Works in CPython, breaks in PyPy")
Why: Integer caching, string interning, and other optimizations are CPython implementation details. They can change between versions and don't exist in other Python implementations.
Instead:
if x == 42: # Always correct
print("Works everywhere")