Module 10: Runtime Governance and Mastery Review¶
Module Position¶
flowchart TD
family["Python Programming"] --> program["Python Metaprogramming"]
program --> module["Module 10: Runtime Governance and Mastery Review"]
module --> lessons["Lesson pages and worked examples"]
module --> checkpoints["Exercises and closing criteria"]
module --> capstone["Related capstone evidence"]
flowchart TD
purpose["Start with the module purpose and main questions"] --> lesson_map["Use the lesson map to choose reading order"]
lesson_map --> study["Read the lessons and examples with one review question in mind"]
study --> proof["Test the idea with exercises and capstone checkpoints"]
proof --> close["Move on only when the closing criteria feel concrete"]
Read the first diagram as a placement map: this page sits between the course promise, the lesson pages listed below, and the capstone surfaces that pressure-test the module. Read the second diagram as the study route for this page, so the diagrams point you toward the Lesson map, Exercises, and Closing criteria instead of acting like decoration.
Keep These Pages Open¶
Use these support surfaces while reading so the final module turns mechanism knowledge into durable review judgment instead of into one more list of dangerous tools:
- Mastery Map for the late-course review route
- Boundary Review Prompts for keep/change/reject decisions under governance pressure
- Review Checklist for the stable engineering bar
- Capstone Proof Checklist for the final claim-to-proof route through the runtime
Carry this question into the module:
Which runtime powers are still defensible once debugging cost, observability, and team trust become the real review criteria?
Table of Contents¶
- Introduction
- Visual: Responsibility Ladder
- Visual: Non-Negotiables (Red Lines)
- Core 1: Dynamic Execution —
compile/eval/execWithout Lying to Yourself - Core 2: ABCs, Protocols,
__subclasshook__— Interfaces With Controlled Semantics - Core 3: Responsible Metaprogramming — Tracebacks, Performance, Globals, Monkey-Patching
- Core 4: Import Hooks & AST Transforms — Tooling-Grade, Not App-Grade
- Capstone: Plugin Architectures — Decorator vs Metaclass vs Import Hook
- Learning outcomes
- Common failure modes
- Exercises
- Self-test
- Closing criteria
- Power Ladder Checkpoint
- Glossary (Module 10)
Introduction¶
This module closes the mechanism ladder by turning it into review policy. No new hook in this chapter is more important than the judgment it teaches. Dynamic execution, import hooks, monkey-patching, and interface tricks only become defensible when the team can still observe, test, and reverse the behavior without folklore.
Non-negotiable thesis:
If your metaprogramming makes failures harder to debug than the boring alternative, it is a liability.
The practical goal here is simple: leave the course with red lines you can actually use in code review. Earlier modules taught what the runtime can do. This module decides what the runtime should be allowed to do in real engineering work.
Why this module matters in the course¶
This is the module that turns runtime power into engineering judgment. Without it, the course would teach mechanisms but not the boundaries that keep those mechanisms from damaging debuggability, observability, and team trust.
It matters because metaprogramming becomes dangerous exactly when it starts working well enough to hide its own cost.
Questions this module should answer¶
By the end of the module, you should be able to answer:
- Which runtime hooks are too dangerous for ordinary application code?
- Which red lines are about security, and which are about maintainability or team trust?
- How do you add power without making failures harder to observe and reverse?
- What should a reviewer ask before approving dynamic execution, monkey-patching, or import-hook work?
If this module feels optional, the earlier modules have not been learned responsibly yet.
What to inspect in the capstone¶
Keep the capstone open while reading this module and inspect:
- how manifest export avoids executing plugin actions
- how registration stays deterministic and resettable in tests
- where runtime behavior remains introspectable instead of hidden behind magic
The capstone should make one final point concrete: metaprogramming is only defensible when the runtime stays observable.
Use this module when¶
- the mechanics are clear but the approval standard is still fuzzy
- a dynamic design needs explicit red lines around safety, debuggability, or reviewability
- you need to turn mechanism knowledge into code-review judgment
Closing bar¶
Before finishing the course, you should be able to explain:
- which runtime powers belong only in tooling or exceptional cases
- what makes a dynamic design reversible, observable, and reviewable
- why the capstone remains defensible because its runtime facts stay visible from the public surface
Visual: Responsibility Ladder¶
graph TD
five["5. Import hooks or AST transforms<br/>global semantics"]
four["4. Metaclasses<br/>class creation pipeline"]
three["3. Class decorators<br/>post-creation rewrite"]
two["2. Descriptors or `@property`<br/>per-attribute semantics"]
one["1. Plain code<br/>explicit and predictable"]
five --> four --> three --> two --> one
Caption: Choose the lowest-power tool that solves the problem, then add guardrails.
Visual: Non-Negotiables (Red Lines)¶
graph TD
untrusted["1. Never run `eval` or `exec` on untrusted input in-process<br/>restricted globals and AST filtering are not sandboxes<br/>use process isolation"]
patching["2. Never monkey-patch builtins or stdlib types in production<br/>patch your own symbols, user-defined types, or tests instead"]
guardrails["3. Never introduce magic without a kill switch, deterministic behavior, cleanup tests, and preserved introspection"]
untrusted --> patching --> guardrails
Core 1: Dynamic Execution — compile / eval / exec Without Lying to Yourself¶
The only honest rule¶
Never run
eval/execon untrusted input in-process. If input is untrusted, you need process isolation. Anything else is self-deception.
Visual: What actually happens¶
graph TD
source["Source string or AST"]
compile["`compile(..., mode=\"eval\" | \"exec\")` -> code object"]
eval["`eval(code, globals, locals)`<br/>returns value for expressions"]
exec["`exec(code, globals, locals)`<br/>returns `None` and mutates mappings"]
source --> compile
compile --> eval
compile --> exec
Caption: You control where code executes only through the globals and locals mappings. Those mappings do not provide security.
Canonical facts (precise)¶
-
compile(source_or_ast, filename, mode)returns a code object. -
mode="eval": expression mode="exec": statementseval(...)evaluates an expression and returns its value.exec(...)executes statements and returnsNone.- If
globalslacks__builtins__, Python may inject it. If you care about restriction, set it explicitly.
Example: expression compilation + evaluation (explicit builtins)¶
co = compile("x * 2 + 1", "<expr>", "eval")
globals_ = {"__builtins__": {}, "x": 10}
print(eval(co, globals_, {})) # 21
Example: statements into isolated locals¶
co = compile("x = 42\ny = x * 2", "<stmt>", "exec")
globals_ = {"__builtins__": {}}
locals_ = {}
exec(co, globals_, locals_)
print(locals_["y"]) # 84
print("x" in globals_) # False
“Restricted namespace” (only for trusted internal config)¶
Whitelisting is useful to prevent accidental footguns in code you ship. It is not a security boundary.
src = "result = len('abc') + int('3')"
globals_ = {"__builtins__": {"len": len, "int": int}}
locals_ = {}
exec(compile(src, "<trusted-config>", "exec"), globals_, locals_)
print(locals_["result"]) # 6
Tiny AST whitelist DSL (still not for untrusted input)¶
import ast
_ALLOWED = (
ast.Expression,
ast.BinOp, ast.UnaryOp,
ast.Add, ast.Sub, ast.Mult, ast.Div, ast.Mod, ast.Pow,
ast.USub, ast.UAdd,
ast.Constant,
ast.Name, ast.Load,
)
def safe_eval_expr(expr: str, allowed_names: dict):
tree = ast.parse(expr, mode="eval")
for node in ast.walk(tree):
if not isinstance(node, _ALLOWED):
raise ValueError(f"Forbidden syntax: {type(node).__name__}")
co = compile(tree, "<safe-eval>", "eval")
globals_ = {"__builtins__": {}}
globals_.update(allowed_names)
return eval(co, globals_, {})
print(safe_eval_expr("x * 2 + 1", {"x": 10})) # 21
try:
safe_eval_expr("__import__('os').system('echo hi')", {"x": 1})
except ValueError as e:
print("Expected:", e)
Checklist: before adding eval/exec¶
Dynamic execution checklist
□ Can it be expressed as data (JSON/TOML/YAML) + normal code?
□ Is the input *guaranteed trusted* (no user path, no third-party injection)?
□ Are you compiling once (startup) not compiling in a hot loop?
□ Are builtins explicitly whitelisted (not implicitly injected)?
□ Do you have tests proving forbidden syntax is rejected and failures are explicit?
□ If any doubt about trust → isolate in a separate process.
Core 2: ABCs, Protocols, __subclasshook__ — Interfaces With Controlled Semantics¶
Visual: “Interface” options and what they guarantee¶
Interfaces: what you actually get
ABC (abc.ABC + @abstractmethod)
- Enforces "must implement" at instantiation time.
- Runtime guarantee: weak but real (cannot instantiate if abstract).
Protocol (typing.Protocol)
- Primary value: static typing (mypy/pyright).
- With @runtime_checkable: shallow isinstance/issubclass structural check.
- Does NOT validate signatures/invariants/behavior.
__subclasshook__
- Lets an ABC declare virtual subclasses based on structure.
- Must remain boring: trivial hasattr/callable checks + NotImplemented fallback.
ABC enforcement (nominal interface)¶
from abc import ABC, abstractmethod
class Drawable(ABC):
@abstractmethod
def draw(self) -> str: ...
class Circle(Drawable):
def draw(self) -> str:
return "circle"
print(Circle().draw()) # circle
try:
Drawable()
except TypeError as e:
print("Expected:", e)
Protocol (static-first); runtime check is shallow¶
from typing import Protocol, runtime_checkable
@runtime_checkable
class SupportsClose(Protocol):
def close(self) -> None: ...
def ensure_closed(obj):
if not isinstance(obj, SupportsClose):
raise TypeError(f"{obj!r} does not implement close()")
obj.close()
class FileLike:
def close(self) -> None:
print("closed")
ensure_closed(FileLike()) # closed
try:
ensure_closed(123)
except TypeError as e:
print("Expected:", e)
__subclasshook__ virtual subclassing (keep it boring)¶
from abc import ABC
class HasLen(ABC):
@classmethod
def __subclasshook__(cls, sub):
if hasattr(sub, "__len__") and callable(getattr(sub, "__len__", None)):
return True
return NotImplemented
class MyList:
def __len__(self): return 5
print(issubclass(MyList, HasLen)) # True
print(isinstance(MyList(), HasLen)) # True
Checklist: before adding an ABC / Protocol¶
Interface checklist
□ Is the interface stable and worth naming?
□ Do you have ≥2 independent implementations (or a real plan)?
□ Do you need runtime checks, or is static typing enough?
□ If you use __subclasshook__, is it trivial and defaulting to NotImplemented?
□ Do you have tests for one "good" and one "bad" implementation?
Core 3: Responsible Metaprogramming — Tracebacks, Performance, Globals, Monkey-Patching¶
Visual: Guardrails for “magic”¶
Magic Guardrails (minimum bar)
Observability:
- functools.wraps
- stable names/qualnames
- consistent exception chaining policy
- no traceback destruction
Reversibility:
- context-managed patches
- reset hooks for registries
- feature flags / kill switches
Determinism:
- no import-order dependence (or explicitly documented + tested)
- stable ordering in registries
48.1 Preserve tracebacks¶
import functools
def trace_safe(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception:
# Bare raise keeps the original traceback intact.
raise
return wrapper
@trace_safe
def risky():
raise ValueError("boom")
try:
risky()
except ValueError as e:
print("Expected:", e)
If you must add context, chain explicitly:
import functools
def wrap_with_context(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as exc:
raise RuntimeError(f"{func.__name__} failed") from exc
return wrapper
48.2 Measure overhead (don’t guess)¶
import timeit
def baseline(x): return x + 1
def wrapper(x):
return baseline(x)
print(timeit.timeit("baseline(1)", globals=globals(), number=200000))
print(timeit.timeit("wrapper(1)", globals=globals(), number=200000))
48.3 Global registries must be testable¶
from collections import defaultdict
_REGISTRY = defaultdict(list)
def registry_add(group, name, obj):
_REGISTRY[group].append((name, obj))
_REGISTRY[group].sort(key=lambda t: t[0])
def registry_clear(group=None):
if group is None:
_REGISTRY.clear()
else:
_REGISTRY.pop(group, None)
48.4 Monkey-patching: the real boundary¶
Demonstrate the failure (expected)¶
Many core types are immutable at the type level, so patching can fail:
Correct patterns¶
(A) Patch a user-defined type (safe target):
from contextlib import contextmanager
@contextmanager
def patch_attr(obj, name, new_value):
old = getattr(obj, name)
setattr(obj, name, new_value)
try:
yield old
finally:
setattr(obj, name, old)
class MyList(list):
pass
def noisy_append(self, x):
print(f"append({x})")
return super(MyList, self).append(x)
with patch_attr(MyList, "append", noisy_append):
m = MyList([1])
m.append(2) # append(2)
m = MyList([1])
m.append(2) # normal (reverted)
(B) Patch your module symbol, not the builtin (preferred in production):
# Imagine your module does: from time import time as now
# Patch YOUR module's `now`, not `time.time` globally.
Policy statement: patching builtins/stdlib belongs in tests only, and even there prefer unittest.mock.patch against your module symbol.
Core 4: Import Hooks & AST Transforms — Tooling-Grade, Not App-Grade¶
Visual: Why this is “outer darkness” for apps¶
Import hooks / AST transforms (blast radius)
- Touch every import (global semantics)
- Order-sensitive and hard to reason about
- Reload behavior is tricky
- Debuggers/profilers/IDEs need location preservation
Caption: Use for tooling (coverage, tracing, macro systems), not typical app features.
49.1 Minimal meta-path virtual module (reversible)¶
import sys
import types
from importlib.machinery import ModuleSpec
class VirtualFinder:
def find_spec(self, fullname, path=None, target=None):
if fullname == "virtual_mod":
return ModuleSpec(fullname, VirtualLoader())
return None
class VirtualLoader:
def create_module(self, spec):
return types.ModuleType(spec.name)
def exec_module(self, module):
module.answer = 42
def hello(): return "hi"
module.hello = hello
finder = VirtualFinder()
sys.meta_path.insert(0, finder)
try:
import virtual_mod
print(virtual_mod.answer) # 42
print(virtual_mod.hello()) # hi
finally:
sys.meta_path.remove(finder)
49.2 Minimal AST transform (mechanics only; preserve locations)¶
import ast
class SquareCall(ast.NodeTransformer):
def visit_Call(self, node):
self.generic_visit(node)
if isinstance(node.func, ast.Name) and node.func.id == "square" and len(node.args) == 1:
x = node.args[0]
new = ast.BinOp(left=x, op=ast.Mult(), right=x)
return ast.copy_location(new, node)
return node
source = "def f(x): return square(x) + 1"
tree = ast.parse(source)
tree = SquareCall().visit(tree)
ast.fix_missing_locations(tree)
ns = {}
exec(compile(tree, "<ast>", "exec"), ns, ns)
print(ns)
Checklist: before adding import hooks / AST transforms¶
Hook/transform checklist
□ Are you building tooling (coverage/tracing/macros), not app logic?
□ Is there a full disable switch?
□ Do you test cleanup (meta_path removal), reload, and import order?
□ Do you preserve locations (copy_location + fix_missing_locations)?
□ Is there a “no-hook” mode that still runs correctly?
Capstone: Plugin Architectures — Decorator vs Metaclass vs Import Hook¶
Visual: Selection guidance¶
Plugin Mechanism Choice (default rule)
Need explicit, testable, typing-friendly plugins?
→ Decorator registry (default)
Need hierarchy-wide invariants and “no opt-out” across subclasses?
→ Metaclass (accept conflicts + import-time side effects)
Need to change import semantics or instrument everything?
→ Import hooks (tooling-grade; avoid for apps)
Shared registry (deterministic + resettable)¶
from collections import defaultdict
_PLUGINS = defaultdict(list)
def plugins_clear():
_PLUGINS.clear()
def plugins_add(group: str, name: str, obj):
_PLUGINS[group].append((name, obj))
_PLUGINS[group].sort(key=lambda t: t[0])
def plugins_list(group: str):
return list(_PLUGINS.get(group, []))
1) Decorator-based (default)¶
def register(group: str):
def deco(cls):
plugins_add(group, cls.__name__, cls)
return cls
return deco
@register("ui")
class Button:
pass
print([n for n, _ in plugins_list("ui")]) # ['Button']
2) Metaclass-based (only when “no opt-out” is required)¶
class PluginMeta(type):
def __new__(mcs, name, bases, ns):
cls = super().__new__(mcs, name, bases, ns)
if ns.get("__abstract__", False):
return cls
group = ns.get("group")
if group is None:
for b in bases:
group = getattr(b, "group", None)
if group is not None:
break
if group is None:
group = "default"
plugins_add(group, name, cls)
cls.group = group
return cls
class Logger(metaclass=PluginMeta):
__abstract__ = True
group = "logging"
class FileLogger(Logger):
def log(self, msg): return f"[FILE] {msg}"
print([n for n, _ in plugins_list("logging")]) # ['FileLogger']
3) Import-hook-based discovery (intentionally omitted)¶
Policy: do not use import hooks for application plugin discovery. If you need discovery, use explicit imports or package entry points (outside the scope of this book).
Final code review checklist (drop-in)¶
Metaprogramming review checklist
□ Lowest-power tool chosen (ladder check)
□ Tracebacks preserved (wraps + clear chaining policy)
□ Deterministic behavior (ordering, import-time effects documented)
□ Reversible (context managers for patches, clear reset hooks)
□ Testable (feature on/off; registry reset; no hidden globals)
□ Measured (perf budget stated for hot paths)
□ Security honest (no in-process eval/exec for untrusted input)
Selection Guidance¶
- Default: Use decorators for plugin registration and extension points.
- Use a metaclass only when plugins are tightly coupled to an inheritance hierarchy and you explicitly want “no opt-out”.
- Reserve import hooks for coverage / tracing / analysis tools, not for core application or library plugins.
Code-Review Checklist for Plugin Architectures¶
- Have you justified why a simple decorator-based registry is insufficient?
- If using a metaclass, are import-time side effects and subclass behaviour clearly documented?
- If using import hooks, can the system run correctly with hooks disabled (feature flag / configuration)?
- Are plugin discovery and registration deterministic (no hidden import-order tricks)?
- Do tests cover plugin loading, failure modes, and disabling the plugin mechanism entirely?
Exercise¶
Extend the comparison by:
- Implementing a hybrid system where an import hook discovers modules, but registration is still done via decorators.
- Adding mypy stubs for plugin APIs and verifying that each architecture remains type-checkable.
You now understand metaclasses thoroughly — and exactly why you should almost never write one yourself.
Learning outcomes¶
By the end of this module, you should be able to:
- Apply a practical review policy to dynamic execution, monkey-patching, protocols, and import hooks instead of treating them as isolated tricks.
- Explain why restricted globals are not a real security boundary for
evalandexec. - Choose between decorators, metaclasses, entry points, and import hooks for plugin systems with an honest justification.
- Demand observability, reversibility, and measurement evidence before approving high-power runtime behavior.
- Separate tooling-grade mechanisms from application-grade defaults without overstating what the runtime can safely guarantee.
Common failure modes¶
- Presenting AST validation or restricted globals as a sandbox instead of acknowledging the need for process isolation with untrusted input.
- Approving monkey patches that have no context manager, reset hook, or blast-radius boundary.
- Using protocols or
__subclasshook__to imply semantic guarantees that only static analysis or real tests can provide. - Treating import hooks as a convenient plugin mechanism when explicit imports or packaging entry points would be clearer and safer.
- Skipping performance measurement for wrappers and dynamic machinery on hot paths because the mechanism "seems lightweight."
Exercises¶
- Take one dynamic execution example and rewrite it as a data-driven parser or explicit dispatch table; document what risk disappeared.
- Add timing evidence to one wrapper-heavy path and decide whether the abstraction still earns its keep.
- Build a context-managed monkey patch for a user-defined symbol, then write the revert path and failure test before using it.
- Compare a decorator registry, a metaclass registry, and an import-hook discovery sketch for the same plugin interface; justify the default choice in writing.
Self-test¶
- Can you explain why "trusted input only" is the real line for in-process
evalandexec? - Can you name the minimum evidence you would ask for before approving an import hook in production code?
- Can you point to one use of a protocol that improves type clarity and one misuse that pretends to prove runtime semantics?
- Can you explain how to turn off, reset, or bypass each high-power mechanism covered in this module?
Closing criteria¶
You have completed the module when you can review high-power runtime code with operational, not aesthetic, standards:
- The lowest-power mechanism has been considered first and documented honestly.
- Security claims stop at the real boundary the implementation can enforce.
- Reversibility, observability, and test isolation are part of the design, not cleanup work for later.
- Performance-sensitive behavior comes with measurement or an explicit statement that it is off the hot path.
Power Ladder Checkpoint¶
- Default to decorators for registration and callable policy when you need explicit, reversible behavior.
- Escalate to metaclasses only for hierarchy-wide class-creation rules that cannot be owned after class creation.
- Reserve import hooks and runtime-generated code for tooling-grade problems where explicit imports, entry points, or configuration are honestly insufficient.
- Reject any higher-power mechanism that does not come with reset hooks, observability, and a proof route.
- Use the Runtime Power Ladder as a review gate, not as optional background reading.
Glossary (Module 10)¶
| Term | Definition |
|---|---|
| Responsibility ladder | A policy hierarchy ranking metaprogramming tools by blast radius; use the lowest-power tool that solves the problem. |
| Red lines | Non-negotiable safety defaults (e.g., no in-process eval/exec for untrusted input, no builtin monkey-patching in production, no magic without guardrails). |
| Blast radius | The scope of collateral impact when “magic” fails (import hooks affect everything; descriptors affect one attribute). |
| Code object | Immutable compiled bytecode produced by compile(...), executed by eval or exec. |
| Dynamic execution | Running runtime-generated code via compile + eval/exec (strings or AST), with explicit globals/locals. |
| Trusted vs untrusted input | Security boundary: only trusted, internal inputs may be dynamically executed in-process; untrusted requires isolation. |
| Process isolation | Executing untrusted code in a separate process/container/VM to contain compromise; the only honest “sandbox.” |
| “Restricted globals” fallacy | The mistaken belief that limiting globals/__builtins__ makes eval/exec safe against a determined attacker. |
| AST whitelist DSL | A tiny expression language built by parsing/validating AST nodes; reduces accidental footguns but is not a security boundary. |
| Builtins injection | Python may insert __builtins__ into globals if missing; set it explicitly when controlling execution environment. |
Compile mode eval |
compile(..., mode="eval") produces a code object for a single expression; eval(...) returns its value. |
Compile mode exec |
compile(..., mode="exec") produces a code object for statements; exec(...) returns None and mutates mappings. |
| Observability | Ensuring magic remains debuggable: functools.wraps, stable names, preserved tracebacks, logging/metrics where needed. |
| Traceback preservation | Keeping original stack traces intact (e.g., raise vs creating new exceptions that erase context). |
| Exception chaining | Adding context without losing the original error using raise NewError(...) from exc. |
| Overhead budgeting | Measuring wrapper/metaprogramming cost (e.g., timeit) instead of guessing; critical for hot paths. |
| Determinism | Behavior must not depend on import order, hash randomization, or incidental iteration order; stable sorting and explicit flows. |
| Reversibility | Ability to disable/undo magic cleanly (context-managed patches, feature flags, reset hooks for registries). |
| Kill switch / feature flag | A configuration gate to turn off risky metaprogramming paths in production or during incidents. |
| Monkey patching | Runtime replacement of attributes/functions/methods; acceptable in tests with discipline, risky in production. |
| Patch target boundary | Patch your module’s symbol (preferred) or user-defined types; avoid patching builtins/stdlib types in production. |
| Context-managed patch | A reversible patch pattern that restores the original value on exit (prevents test bleed and production drift). |
| Global registry | Shared mutable state used for plugin registration; must expose reset/clear APIs and stable ordering to be testable. |
| Test isolation hook | A clear()/reset function that makes registries and global state deterministic across tests. |
| Interface (nominal) | Contract enforced by inheritance (e.g., ABC); failures typically occur at instantiation time for abstract classes. |
| Abstract Base Class (ABC) | abc.ABC + @abstractmethod defines required methods; abstract classes cannot be instantiated until implemented. |
| Protocol (structural typing) | typing.Protocol specifies required members for static checkers; runtime checks are shallow even with @runtime_checkable. |
@runtime_checkable |
Enables isinstance/issubclass structural checks for a Protocol, but does not validate semantics or signatures deeply. |
__subclasshook__ |
ABC hook to define “virtual subclassing” via simple structural checks; should stay trivial and return NotImplemented by default. |
| Import hook | Custom import machinery (via sys.meta_path) that can synthesize/transform modules; global semantics, high risk. |
| Finder / Loader | Import hook components: Finder locates a module spec; Loader creates/executes the module object. |
| Meta-path | sys.meta_path list of finders; inserting here changes how imports work process-wide. |
| Virtual module | A module provided by a custom loader without a physical file, often used for tooling or controlled demos. |
| AST transformation | Rewriting Python code at the AST level before compilation; must preserve source locations for debuggers/tracebacks. |
| Location preservation | Using ast.copy_location + ast.fix_missing_locations so transformed code reports correct line/column info. |
| Outer darkness | Colloquial label for import hooks/AST transforms: tooling-grade power that is usually inappropriate for application logic. |
| Plugin architecture selection | Policy choice: decorator registry (default) → metaclass (hierarchy-wide invariants) → import hooks (tooling-grade only). |
| Decorator-based registration | Explicit, testable plugin registration mechanism; generally the professional default. |
| Metaclass-based registration | Automatic registration at class-definition time across a hierarchy; accepts import-time side effects and conflicts. |
| Entry points | Packaging-level plugin discovery (e.g., installed distributions advertise plugins); preferred over import hooks for apps. |
You now understand metaclasses thoroughly — and exactly why you should almost never write one yourself.
Directory glossary¶
Use Glossary when you want the recurring language in this module kept stable while you move between lessons, exercises, and capstone checkpoints.