[{"data":1,"prerenderedAt":66},["ShallowReactive",2],{"qa-\u002Fpython\u002Finternals\u002Fcpython-model":3},{"page":4,"siblings":57,"blog":48},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"related":48,"seo":49,"seoDescription":50,"stem":51,"subtopic":52,"topic":53,"topicSlug":54,"updated":55,"__hash__":56},"qa\u002Fpython\u002Finternals\u002Fcpython-model.md","Cpython Model",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"hard","md","Python","python",{},true,3,"\u002Fpython\u002Finternals\u002Fcpython-model",[23,27,31,36,40,44],{"id":24,"difficulty":14,"q":25,"a":26},"source-to-bytecode","How does CPython go from source to running code?","CPython first **compiles** your `.py` source into **bytecode** — a compact,\nplatform-independent instruction set for the Python **virtual machine**. That\nbytecode is then executed by the **interpreter loop** (a big evaluation loop,\nhistorically a giant `switch`), which runs one bytecode instruction at a time.\nCompiled bytecode is cached as **`.pyc`** files in `__pycache__`.\n\n```python\n# mymod.py  ->  compiled to  __pycache__\u002Fmymod.cpython-3xx.pyc\n# The .pyc is reused if the source is unchanged (matched by hash\u002Ftimestamp),\n# so imports skip recompiling. It is NOT machine code — still bytecode.\ndef add(a, b):\n    return a + b\n```\n\nThe key points: bytecode is an **intermediate** representation (not native\nmachine code), `.pyc` is just a **cache** to skip recompilation on import, and the\nVM interprets it at runtime. Rule of thumb: source -> bytecode -> interpreter\nloop, with `.pyc` caching the middle step.\n",{"id":28,"difficulty":14,"q":29,"a":30},"cpython-vs-others","What is CPython, and how does it differ from PyPy and Jython?","**CPython** is the **reference implementation** written in C — it's what you get\nfrom python.org and what most people mean by \"Python.\" The **language** is a\nspec; an **implementation** runs it. Alternatives include **PyPy** (with a\n**JIT** that often runs much faster), **Jython** (runs on the **JVM**), and\n**IronPython** (on **.NET**).\n\n```python\nimport platform\nprint(platform.python_implementation())   # 'CPython', 'PyPy', 'Jython', ...\n```\n\nThey differ in performance and integration: PyPy speeds up long-running pure\nPython via JIT, Jython\u002FIronPython interoperate with Java\u002F.NET libraries, and\nCPython has the widest C-extension ecosystem (NumPy, etc.) plus the **GIL**. Rule\nof thumb: \"Python\" is the language; CPython is the dominant implementation, and\nothers trade ecosystem reach for speed or platform integration.\n",{"id":32,"difficulty":33,"q":34,"a":35},"dis-module","medium","How do you inspect bytecode with the dis module?","The **`dis`** (\"disassemble\") module shows the **bytecode instructions** a\nfunction compiles to — useful for understanding what Python actually does under\nthe hood and for comparing the cost of two approaches.\n\n```python\nimport dis\n\ndef add(a, b):\n    return a + b\n\ndis.dis(add)\n# Example output (abbreviated):\n#   LOAD_FAST   a\n#   LOAD_FAST   b\n#   BINARY_OP   +        # add the two\n#   RETURN_VALUE\n```\n\nEach line is one VM instruction the interpreter loop executes. `dis` is great for\nanswering \"is this comprehension really faster?\" or seeing how the compiler\ndesugars a construct. Rule of thumb: when you want to know what the interpreter\n*literally* runs, disassemble it with `dis`.\n",{"id":37,"difficulty":14,"q":38,"a":39},"frames-call-stack","What are frames and the call stack?","Every time a function is called, CPython creates a **frame object** — a record\nholding that call's **local variables**, the current **instruction pointer**, and\na reference to the caller. Frames are pushed onto the **call stack** as functions\ncall each other and popped as they return. This is the structure a **traceback**\nwalks when printing an error.\n\n```python\nimport inspect\n\ndef inner():\n    frame = inspect.currentframe()\n    print(frame.f_code.co_name)        # 'inner'\n    print(frame.f_back.f_code.co_name) # 'outer' — the caller's frame\n\ndef outer():\n    inner()\n\nouter()\n```\n\nFrames are why each call has **isolated locals** and why exceptions can report the\nfull chain of calls. Deep\u002Finfinite recursion piles up frames until\n`RecursionError` (the stack limit). Rule of thumb: one frame per active call, all\nchained together as the call stack.\n",{"id":41,"difficulty":33,"q":42,"a":43},"compiled-or-interpreted","Is Python compiled or interpreted?","**Both.** Python source is first **compiled** to **bytecode** (a real compilation\nstep), and that bytecode is then **interpreted** by the CPython virtual machine at\nruntime. So the common \"Python is interpreted\" is only half the story — there's a\ncompile phase, just to bytecode rather than to native machine code.\n\n```python\nimport py_compile\npy_compile.compile(\"mymod.py\")   # explicitly produces the .pyc bytecode\n\n# At runtime, the VM's interpreter loop executes that bytecode.\n```\n\nThe distinction that matters: Python compiles to **portable bytecode**, not to\nCPU-specific machine code (the way C does ahead-of-time). PyPy adds a **JIT** that\n*does* compile hot bytecode to machine code at runtime. Rule of thumb: Python is\ncompiled to bytecode and then interpreted.\n",{"id":45,"difficulty":14,"q":46,"a":47},"gil-role","What role does the GIL play in CPython's execution model?","The **GIL** (Global Interpreter Lock) is a single mutex that lets **only one\nthread execute Python bytecode at a time** in a CPython process. The interpreter\nloop holds it while running instructions and periodically releases it (and during\nblocking I\u002FO), so threads take turns rather than running Python code in parallel.\n\n```python\n# Two threads doing CPU-bound Python work do NOT run in parallel:\n# the GIL serializes their bytecode execution on one core.\n# threads -> good for I\u002FO-bound (GIL released during I\u002FO)\n# processes -> needed for CPU-bound parallelism (each has its own GIL)\n```\n\nIt exists largely to make CPython's memory management (reference counting)\nsimpler and C extensions safer. The consequence is that **threads don't give CPU\nparallelism** — that's what multiprocessing is for (see the Concurrency &\nParallelism topic). Rule of thumb: the GIL means one-bytecode-at-a-time per\nprocess, so use processes for CPU-bound parallelism.\n",null,{"description":11},"Python interview questions on the CPython execution model: source to bytecode to .pyc, the interpreter loop, the dis module, frames and the call stack, and whether Python is compiled or interpreted.","python\u002Finternals\u002Fcpython-model","The CPython Execution Model","Memory & Internals","internals","2026-06-18","BTfRbdwE60ns7SueJvUjjVuR4jxN9D_oqBK90FfzmXg",[58,62,65],{"subtopic":59,"path":60,"order":61},"Garbage Collection & Reference Counting","\u002Fpython\u002Finternals\u002Fgarbage-collection",1,{"subtopic":63,"path":64,"order":12},"Identity, is vs ==, & Interning","\u002Fpython\u002Finternals\u002Fidentity-interning",{"subtopic":52,"path":21,"order":20},1781808681340]