skip to content

Interpreter Execution Model

What the interpreter does with your code: compiles it into code objects, runs it in frames you can read, then tears the process down in order. Interviewers reach here when behaviour defies the source.

part ofPythonoverview, primer and where to startread it →
on this pageshow

explore

questions

77 · 5 sections

What is the difference between Python's eval() and exec()?

level: juniorimportance: must knowfreq 55%
basics
~10 s

eval() evaluates one expression and returns its value. exec() runs statements - assignments, loops, imports, definitions - and always returns None. Hand eval() a statement and it raises SyntaxError.

open as a page

What is Python's __pycache__ directory and when does CPython reuse a .pyc file?

level: juniorimportance: must knowfreq 55%
basics
~20 s

CPython caches each module's compiled bytecode in a pycache directory next to the source. It reuses that .pyc only when the magic number matches the interpreter and the recorded source timestamp and size still match.

open as a page

What is the difference between ast.NodeVisitor and ast.NodeTransformer in Python?

level: middleimportance: must knowfreq 50%
basics
~10 s

ast.NodeVisitor reads a tree: its visit_<NodeType> methods return whatever they like and the tree is untouched. ast.NodeTransformer rewrites it: whatever a visit method returns replaces the node, and returning None deletes it.

open as a page

Why do two functions from the same def share a code object but not defaults?

level: middleimportance: must knowfreq 40%
basics
~20 s

The code object is compiled once and is immutable, so it can be shared. Default values are ordinary expressions evaluated each time the def statement executes, so they belong to the function object created then, in defaults and kwdefaults.

open as a page

Why does a Python code-checking tool use ast.parse() instead of regular expressions over the source?

level: juniorimportance: should knowfreq 40%
basics
~20 s

ast.parse() turns source text into a tree that mirrors Python's own grammar, so a tool matches real structure - a call, an assignment, a function - instead of characters. Regexes cannot see nesting, strings or comments.

open as a page

Which CPython limit decides when deep recursion raises RecursionError?

level: juniorimportance: must knowfreq 62%
basics
~10 s

CPython counts how many Python frames are stacked on the current thread and raises RecursionError once that count passes the interpreter's recursion limit, which is 1000 by default. sys.getrecursionlimit() reads it; sys.setrecursionlimit() changes it.

open as a page

How can a Python function find out which function called it, using frame objects?

level: juniorimportance: should knowfreq 30%
basics
~10 s

Get a frame object with inspect.currentframe() or sys._getframe(), follow its f_back attribute to the caller's frame, then read f_code.co_name for the caller's name and f_lineno for the call site.

open as a page

Why does assigning into locals() inside a Python function not change the variable?

level: middleimportance: should knowfreq 26%
basics
~20 s

Function locals live in numbered slots on the frame, not in a dictionary. locals() manufactures a snapshot dict of those slots, so writing into it changes only the copy. Since 3.13, a frame's f_locals is a write-through proxy and does update the real variable.

open as a page

Why can raising sys.setrecursionlimit crash the process instead of curing RecursionError?

level: middleimportance: should knowfreq 48%
basics
~20 s

sys.setrecursionlimit only moves CPython's frame counter. The real constraint is the fixed stack the operating system gave the thread, so lifting the counter past what that stack supports turns a catchable RecursionError into a hard crash with no traceback.

open as a page

Why does calling inspect.stack() in a per-document logging helper blow a rebuilder's latency budget?

level: seniorimportance: should knowfreq 34%
basics
~20 s

inspect.stack() walks every frame to the top of the stack, builds a record for each, and by default reads a source line per frame through the line cache. Cost scales with stack depth and touches the filesystem, so it is thousands of times slower than sys._getframe(1).

open as a page

What does getattr(obj, 'x', None) give you that plain obj.x does not?

level: juniorimportance: must knowfreq 70%
basics
~20 s

getattr takes the attribute name as a runtime string, and its optional third argument is returned instead of raising AttributeError when the name is absent. Dotted syntax needs the name written in the source and always raises.

open as a page

Why does a decorated function report the wrapper's __name__ and __doc__, and how does functools.wraps fix it?

level: middleimportance: must knowfreq 65%
basics
~20 s

Decoration rebinds the name to the wrapper, a different function object with its own metadata, so name, qualname and doc are the wrapper's. Applying functools.wraps to the wrapper copies that metadata from the wrapped function and sets wrapped.

open as a page

What do a Python function's __defaults__ and __kwdefaults__ hold, and when are they evaluated?

level: middleimportance: must knowfreq 55%
basics
~20 s

defaults is a tuple of default values for the trailing positional-or-keyword parameters, or None when there are none. kwdefaults is a dict of defaults for keyword-only parameters. Both are evaluated once when the def executes and stored on the function object.

open as a page

What are the five inspect.Parameter kinds, and what does each allow at a call site?

level: middleimportance: must knowfreq 55%
basics
~20 s

inspect.Parameter has five kinds: POSITIONAL_ONLY, POSITIONAL_OR_KEYWORD, VAR_POSITIONAL, KEYWORD_ONLY and VAR_KEYWORD. The kind says how an argument may be supplied — by position, by either, by name only, or swept into a catch-all — and .parameters lists them in that order.

open as a page

What does a Python function's __name__ hold, and how does __qualname__ differ from it?

level: juniorimportance: should knowfreq 45%
basics
~20 s

name is the bare name from the def statement. qualname is the dotted path to the definition inside its module, including enclosing classes and functions, with <locals> marking a function-local definition. module names the defining module.

open as a page

Why does a Python set of strings iterate in a different order on each run?

level: juniorimportance: must knowfreq 45%
basics
~20 s

CPython salts str and bytes hashes with a seed picked at interpreter start-up, so the same strings land in different slots of the set's hash table each run. Set iteration follows slot order, so the order changes.

open as a page

Why does a local file named logging.py shadow the standard library's logging module?

level: juniorimportance: must knowfreq 55%
basics
~10 s

The interpreter prepends the running script's own directory to sys.path, so import searches it before the standard library and takes the first match. Run with -P, or set PYTHONSAFEPATH=1, to drop that prepended entry.

open as a page

What does Python's -O flag actually do to assert statements and __debug__?

level: juniorimportance: must knowfreq 52%
basics
~20 s

Running the interpreter with -O compiles away every assert statement and sets the built-in constant debug to False, so if debug blocks vanish too. It performs no other optimization and does not make code faster.

open as a page

Why compare sys.version_info as a tuple instead of parsing the sys.version string?

level: juniorimportance: must knowfreq 40%
basics
~20 s

sys.version_info is a five-field named tuple, so comparing it against a tuple such as (3, 11) is an element-wise numeric comparison. sys.version is a free-form display string, and text comparison wrongly orders 3.9 after 3.14.

open as a page

What does CPython's `-X dev` development mode switch on at runtime?

level: middleimportance: must knowfreq 38%
basics
~10 s

Running python -X dev (or setting PYTHONDEVMODE=1) enables the default warning filter so DeprecationWarning and ResourceWarning appear, debug hooks in the memory allocator, faulthandler, asyncio debug mode, and stricter encoding-argument checks.

open as a page

What does sys.exit() do, and what process exit code results?

level: juniorimportance: must knowfreq 60%
basics
~20 s

sys.exit() raises SystemExit rather than killing the process outright; if nothing catches it, the interpreter shuts down. No argument or None gives exit status 0, an integer gives that value, and any other object is printed to stderr with status 1.

open as a page

What does sys.argv hold when you run python tools/report.py versus python -m tools.report?

level: juniorimportance: must knowfreq 58%
basics
~10 s

sys.argv[0] is the program name: the script path exactly as typed, or, under -m, the full path of the resolved module file. The program's own options always start at sys.argv[1]; interpreter switches never appear.

open as a page

Why does `except Exception` not catch SystemExit from sys.exit()?

level: middleimportance: must knowfreq 55%
basics
~20 s

SystemExit inherits directly from BaseException, not from Exception, so except Exception deliberately lets it pass. That keeps a broad error handler from swallowing a requested shutdown. A bare except: means except BaseException and does swallow it.

open as a page

Why move an `import` statement into a function body instead of the module top?

level: middleimportance: must knowfreq 48%
basics
~20 s

To keep its cost off the startup path. A module-level import runs while the module body runs, so every process pays it; an import inside a function runs only when that function is called, and never if it is not. It also breaks import cycles.

open as a page

How does python -m pkg.mod differ from running python pkg/mod.py?

level: middleimportance: must knowfreq 48%
basics
~20 s

Python's -m switch resolves a dotted module name through the import system, imports the parent package, and puts the working directory first on sys.path. A file path runs that file with its own directory first and no package context, so relative imports fail.

open as a page