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
- Python3.14.4
- OSUbuntu 26.04 LTS
- pip25.1.1
- TimeAbout 13 min
- Reviewed23 August 2026
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.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| RUNNER01 | 192.168.0.27 | Ubuntu 26.04 LTS | Python 3.14.4 - the only machine this path needs | 2 Core | 4 GB | 50 GB |
Before you start
- guide 2 - you need somewhere to write a file.
- No Python knowledge. This is the first guide of the syllabus proper.
-
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
.pyfile | | 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.implementationnames 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 result
cpython, and the version tuple.Success conditionYou can name the implementation you are running and the version it reports.
-
Proof one: your code is compiled before it runs
disdisassembles compiled bytecode. Feed it the sourcex = 1 + 2and 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_VALUEExpected result
LOAD_SMALL_INT 3- and no addition instruction anywhere.Success conditionYou have seen the compiler evaluate
1 + 2before the program ran. -
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 finenever printed.Success conditionYou can explain why no output appeared before the error.
-
The other kind of error: found while running
Contrast that with an error the compiler cannot see.
10 / 0is 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 result
this 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.
-
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.pydefines a function,main.pyimports 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 moduleExpected resultTwo
.pyfiles, then the program's output.Success conditionYou have a module and something that imports it.
-
Only imported modules get cached
Now look at the directory again. A
__pycache__appeared - and there is one.pycin it, not two.bash Example session ls -A ~/py/pc && ls ~/py/pc/__pycache____pycache__helper.pymain.pyhelper.cpython-314.pycExpected result
__pycache__containinghelper.cpython-314.pyconly.Success conditionYou can say which file gets cached and which does not.
-
Compiling on purpose
py_compilecompiles a file without running it, which is the only way to get a.pycfor 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.pycExpected resultBoth
.pycfiles now present -helperfrom the import,mainbecause you asked.Success conditionYou can produce bytecode deliberately and know why it normally is not there.
-
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
SyntaxErrorproduces 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 ~/pyExpected 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
A script prints nothing at all, not even its first line.
Why: A
SyntaxErrorsomewhere in the file. Compilation covers the whole file before execution starts.Fix:Read the reported line, then look above it - an unclosed bracket or quote is reported where it opened, not where the compiler noticed.
python3 -m py_compile file.pychecks without running.__pycache__directories appearing in a project.Why: Normal. Every module you import is cached there as bytecode.
Fix:Nothing to fix. Deleting it is safe, and adding it to
.gitignoreis standard. The script you *run* is never cached, only what it imports.No
.pycappears for the script being run.Why: By design - the main module's bytecode is never written to disk.
Fix:Use
python3 -m py_compile script.pyif you actually want one. In practice you do not.Editing a
.pyfile seems to have no effect.Why: Almost never a stale cache - CPython checks the source timestamp and size.
Fix:Check you edited the file that is actually being imported:
python3 -c 'import mod; print(mod.__file__)'. A same-named module earlier onsys.pathis the usual answer.