CertGrid CertGrid
Concepts·Certified Entry-Level Python Programmer

Python Execution Model

PCEP objective 1.1 is the terminology objective: interpreter and compiler, source and bytecode, lexis, syntax and semantics. It is usually taught as a list of definitions to memorise, which is why so many people get the one real question wrong - Python compiles your whole file before it runs any of it, and you can prove that in two commands. This guide does the experiments rather than the definitions.

Language Fundamentals Guide 4 of 26 Beginner

Written against the versions above. The bytecode listing below is from **CPython 3.14**. Bytecode is an implementation detail and changes between releases - `LOAD_SMALL_INT` is new in 3.13. The *point* it demonstrates does not change, and no exam question asks you to read bytecode.

One machine, and any shell with Python 3 will do - these exams test the language, not a distribution.
Server NameIP AddressOSRolesCPURAMHDD
RUNNER01192.168.0.27Ubuntu 26.04 LTSPython 3.14.4 - the only machine this path needs2 Core4 GB50 GB

Before you start

  1. The vocabulary, and what actually runs your file

    Objective 1.1 names a specific set of terms. Here they are with the meaning the exam uses:

    | Term | What it means here | |---|---| | source code | the text you write - the .py file | | compilation | translating source into something a machine can execute, all at once, before running | | interpretation | executing source (or bytecode) a step at a time | | bytecode | the intermediate instructions CPython actually executes | | lexis | the vocabulary of the language - which words and symbols exist | | syntax | the rules for arranging them | | semantics | what a correctly arranged program *means* | | CPython | the reference implementation, written in C - the thing you just ran |

    sys.implementation names which Python you have. There are others - PyPy, Jython, IronPython, MicroPython - and the exam may name them, but every output in this path is CPython.

    bash Example session
    python3 -c 'import sys; print(sys.implementation.name, sys.version_info)'cpython sys.version_info(major=3, minor=14, micro=4, releaselevel='final', serial=0)

    Expected resultcpython, and the version tuple.

    Success conditionYou can name the implementation you are running and the version it reports.

  2. Proof one: your code is compiled before it runs

    dis disassembles compiled bytecode. Feed it the source x = 1 + 2 and look carefully at what comes back.

    bash Example session
    python3 -c 'import dis; dis.dis(compile("x = 1 + 2", "<demo>", "exec"))'  0           RESUME                   0   1           LOAD_SMALL_INT           3              STORE_NAME               0 (x)              LOAD_CONST               1 (None)              RETURN_VALUE

    Expected resultLOAD_SMALL_INT 3 - and no addition instruction anywhere.

    Success conditionYou have seen the compiler evaluate 1 + 2 before the program ran.

  3. Proof two: a syntax error stops the whole file, not just its line

    This is the experiment that settles the compiled-or-interpreted question, and it is the one worth remembering. The file below has a perfectly good first line and an unclosed bracket on the second.

    If Python were a line-at-a-time interpreter, line 1 would print and *then* the error would appear.

    bash Example session
    cat > ~/py/lexical.py <<'PY'print("this line is fine")print("this one is not"PYpython3 ~/py/lexical.py  File "/home/sysadmin/py/lexical.py", line 2    print("this one is not"         ^SyntaxError: '(' was never closed[exit 1]

    Expected resultThe error, and nothing else. this line is fine never printed.

    Success conditionYou can explain why no output appeared before the error.

  4. The other kind of error: found while running

    Contrast that with an error the compiler cannot see. 10 / 0 is perfectly legal syntax - the problem is what it *means*, which is a semantic error and only discoverable by trying it.

    bash Example session
    cat > ~/py/runtime.py <<'PY'print("this line runs")print(10 / 0)print("this line never runs")PYpython3 ~/py/runtime.pythis line runsTraceback (most recent call last):  File "/home/sysadmin/py/runtime.py", line 2, in <module>    print(10 / 0)          ~~~^~~ZeroDivisionError: division by zero[exit 1]

    Expected resultthis line runs, then a traceback, and the third line never reached.

    Success conditionYou can tell a compile-time error from a runtime one by what printed before it.

  5. Where the bytecode goes - and where it does not

    Compilation happens every time you run a script, which would be wasteful for modules you import repeatedly. So CPython caches the bytecode.

    Two files here: helper.py defines a function, main.py imports it. Before running, the directory holds exactly those two files.

    bash Example session
    cat > ~/py/pc/helper.py <<'PY'def hello():    return "from the helper module"PYcat > ~/py/pc/main.py <<'PY'import helper print(helper.hello())PYls -A ~/py/pchelper.pymain.pycd ~/py/pc && python3 main.pyfrom the helper module

    Expected resultTwo .py files, then the program's output.

    Success conditionYou have a module and something that imports it.

  6. Only imported modules get cached

    Now look at the directory again. A __pycache__ appeared - and there is one .pyc in it, not two.

    bash Example session
    ls -A ~/py/pc && ls ~/py/pc/__pycache____pycache__helper.pymain.pyhelper.cpython-314.pyc

    Expected result__pycache__ containing helper.cpython-314.pyc only.

    Success conditionYou can say which file gets cached and which does not.

  7. Compiling on purpose

    py_compile compiles a file without running it, which is the only way to get a .pyc for a script you never import. It is also the fastest way to syntax-check a file: it reports errors and produces no output on success.

    bash Example session
    cd ~/py/pc && python3 -m py_compile main.py && ls ~/py/pc/__pycache__helper.cpython-314.pycmain.cpython-314.pyc

    Expected resultBoth .pyc files now present - helper from the import, main because you asked.

    Success conditionYou can produce bytecode deliberately and know why it normally is not there.

  8. What the exam asks from this objective

    Objective 1.1 is worth roughly one or two questions on PCEP, and they are vocabulary questions rather than experiments. The useful things to have straight:

    Python is compiled to bytecode and the bytecode is interpreted. If a question forces a single word, "interpreted" is the expected answer - Python is conventionally called an interpreted language because there is no separate build step you run yourself.

    Lexis, syntax, semantics, in that order of scope. Lexis is which words exist, syntax is how they may be arranged, semantics is what the arrangement means. A misspelled keyword is lexical; a missing colon is syntactic; dividing by zero is semantic.

    A SyntaxError produces no program output at all. This is the one that appears as a "what does this print?" question, and the answer is "nothing".

    CPython is the reference implementation. Others exist and may be named in a question; none of them changes an answer about the language itself.

    guide 5 is next, and it turns the lexis half of that list into a list you can actually print.

    bash
    rm -rf ~/py/pc ~/py/lexical.py ~/py/runtime.py && ls -A ~/py

    Expected resultAn empty scratch directory - every guide in this path leaves it that way.

    Success conditionYou can define the objective's terms and demonstrate the two error classes.

Troubleshooting

Official sources