Exercise Answers¶
Page Maps¶
graph LR
family["Python Programming"]
program["Python Meta-Programming"]
section["Runtime Observation Inspection"]
page["Exercise Answers"]
capstone["Capstone evidence"]
family --> program --> section --> page
page -.applies in.-> capstone
flowchart LR
orient["Orient on the page map"] --> read["Read the main claim and examples"]
read --> inspect["Inspect the related code, proof, or capstone surface"]
inspect --> verify["Run or review the verification path"]
verify --> apply["Apply the idea back to the module and capstone"]
Use this page after attempting the exercises yourself. The goal is not to match every example exactly. The goal is to compare your reasoning against answers that ask a precise runtime question and then choose a tool that fits that question honestly.
Read each answer as a review model, not as a template to copy blindly. A strong answer should always make four things visible:
- the runtime question
- the tool choice
- the evidence produced by that choice
- the exact point where the workflow stays observational or crosses into behavior
The supplied course code is one defensible route:
labs/runtime_observation/evidence.pymakes side effects countablelabs/runtime_observation/debug_view.pyimplements the bounded attachment-first helpertests/test_runtime_observation_lab.pyproves the stated boundariesmake observation-lab-testruns the focused review route
Use it to review your reasoning, not as an answer to copy without explanation.
Answer 1: Separate visible names from stored state¶
Example answer:
class Explorer:
def __init__(self):
self.instance_only = "personal"
def method(self):
return "ok"
obj = Explorer()
Strong evidence:
vars(obj) == {"instance_only": "personal"}"method" in dir(obj)isTrue"method" in vars(obj)isFalse
Good conclusion:
dir(obj) discovers candidate names from instance, class, and MRO context. vars(obj)
shows only dictionary-backed stored state. Those are different questions and should not be
treated as interchangeable.
Reasoning checkpoint:
- the runtime question is "what names look reachable here?" when you choose
dir(obj) - the runtime question is "what state is stored directly on this instance?" when you choose
vars(obj) - a missing instance
__dict__still does not prove missing state because slot-backed storage can exist without a dictionary
Answer 2: Show that dynamic access is behavior¶
Example answer:
Strong evidence:
getattr(obj, "value")printsproperty executedhasattr(obj, "value")also printsproperty executed
Good conclusion:
Dynamic reads participate in the attribute protocol. They are not neutral inspection because descriptors and hooks can execute code while the caller thinks it is only "checking" an attribute.
Reasoning checkpoint:
- the narrower safer question is often "what object is attached under this name?" rather than "what value does runtime lookup produce?"
- if that narrower question is the real need, static lookup is the better first move
hasattris not a harmless pre-check because it walks the same dynamic path and can trigger the same behavior
Answer 3: Choose the right type check¶
Example answer:
from collections.abc import Iterable
def ensure_iterable(value):
if isinstance(value, str):
raise TypeError("strings are excluded")
if not isinstance(value, Iterable):
raise TypeError("expected an iterable")
return iter(value)
Strong evidence:
isinstance([1, 2], Iterable)isTrue- rejecting
stris a narrow rule layered on top of that broader capability check type(True) is boolis a good exact-type example when distinguishingboolfromint
Good conclusion:
isinstance is usually the right default when the question is role compatibility.
type(obj) is T is right when the requirement truly depends on exact identity and should
reject subclasses.
Reasoning checkpoint:
- the iterable helper asks a capability question, so
isinstancematches the job type(obj) is Tshould be paired with a concrete rejection reason such as "subclasses may overload arithmetic semantics" or "subclasses may carry policy we must not silently accept"- if you cannot name the rejected subclass behavior, your exact-type check is probably not justified yet
Answer 4: Prove what callable() does and does not promise¶
Example answer:
class CallableThing:
def __call__(self):
return 1
class Plain:
pass
callable_obj = CallableThing()
plain_obj = Plain()
plain_obj.__call__ = lambda: 1
Strong evidence:
callable(callable_obj)isTruecallable(plain_obj)isFalse
Good conclusion:
Callability depends on the object's type-level protocol, not on an instance attribute with the same name. A true result means only that the runtime permits a call attempt. It does not guarantee valid arguments, safe execution, or success.
Reasoning checkpoint:
CallableThing()is a callable instance because its type provides__call__plain_objremains non-callable because attaching__call__to the instance does not install the call protocol- your answer should still name the callable category: function, bound method, class, or callable instance
Answer 5: Compare static and dynamic lookup directly¶
Example answer:
import inspect
class Demo:
@property
def value(self):
print("property executed")
return 10
obj = Demo()
raw = inspect.getattr_static(obj, "value")
resolved = getattr(obj, "value")
Strong evidence:
rawis thepropertyobject itselfresolved == 10- the dynamic read prints
property executed
Good conclusion:
Static lookup better fits tooling when the goal is to inspect what is attached without triggering behavior. Dynamic lookup is the right choice only when the tool intentionally wants normal runtime semantics.
Reasoning checkpoint:
- attachment truth ends when you inspect the raw
propertyobject - execution truth begins the moment you ask
getattrto resolve the attribute normally - the important habit is not "always use static lookup," but "cross into resolution only when the runtime question truly needs it"
Answer 6: Review a debug or inspection helper¶
Example answer:
Suppose the helper currently does this:
- discover names with
dir(obj) - read each value with
getattr(obj, name) - recurse into everything it prints
Strong diagnosis:
dir(obj)is a discovery stepgetattr(obj, name)is dynamic resolution and may execute code- naive recursion can amplify accidental execution and create cycles
Good repair:
- keep
dir(obj)only for candidate names - move default reads to
inspect.getattr_static - make property evaluation and recursion explicit opt-ins
That repair matches the module's main discipline: the tool should stay observational by default and cross into execution only on purpose.
Reasoning checkpoint:
- after the repair, the helper can promise "I show attached structure by default and only resolve runtime values when you explicitly opt in"
- that promise is reviewable because the default path no longer depends on descriptor execution
- if recursion remains enabled, the helper should also state where recursion stops or why it cannot silently explode work
Answer 7: Compare inheritance-aware and exact inspection¶
Example answer:
Strong evidence:
isinstance(pet, Animal)isTruetype(pet) is AnimalisFalsetype(pet) is DogisTrue
Good conclusion:
Inheritance-aware checks answer capability or role questions. Exact-type checks answer identity questions. The right choice depends on what truth the review actually needs.
Reasoning checkpoint:
- a plugin-style extension system usually wants inheritance-aware acceptance because exact-type checks would reject legitimate extensions
- exact identity becomes appropriate only when the contract depends on one concrete implementation boundary
Answer 8: Inspect one fallback hook without tripping it first¶
Example answer:
import inspect
class LazyBag:
def __getattr__(self, name):
print(f"building {name}")
return name.upper()
obj = LazyBag()
Strong evidence:
inspect.getattr_static(obj, "__getattr__")exposes the fallback method without triggering itgetattr(obj, "token")printsbuilding token- the dynamic route produces a value only by executing fallback behavior
Good conclusion:
Tooling should inspect the attachment story first and cross into fallback execution only when the user explicitly wants runtime behavior rather than structure.
Reasoning checkpoint:
inspect.getattr_static(obj, "__getattr__")answers whether fallback machinery existsgetattr(obj, "token")answers what that fallback chooses to produce at runtime- "check first with
hasattr" is not a repair because it still executes the lookup path you were trying to inspect safely
Answer 9: Build a least-risk inspection workflow¶
A strong answer might use this debugging question:
- "Why does this object appear to have a
valueattribute?"
Strong least-risk order:
- inspect
type(obj)and the class MRO - inspect
vars(obj)orobj.__dict__if available - inspect
inspect.getattr_static(obj, "value") - only then use
getattr(obj, "value")if you actually want runtime semantics
Good conclusion:
The order matters because it keeps structure, storage, and behavior separate. Jumping
straight to getattr would answer a different question and might execute code too early.
Reviewable packet note:
- a strong submission names the exact step where the workflow crosses into behavior
- it also explains why each earlier step failed to answer the debugging question completely
- if the learner cannot defend the order, the workflow is still too hand-wavy
Answer 10: Produce an inspection decision packet¶
The packet is strong when another learner can answer:
- what runtime question is being asked
- which observation tool answers it with the least unnecessary behavior
- where the workflow intentionally crosses into dynamic execution
If readers still cannot tell whether a step was discovery, storage inspection, or behavioral resolution, the packet is not explicit enough yet.
Repair table for weak answer keys¶
Use this table when your answer explains the outcome but not the reasoning:
| Weak answer move | What it leaves unclear | Better revision move |
|---|---|---|
"It works because getattr returned the value." |
whether lookup executed behavior and whether that was acceptable | name the runtime question and whether dynamic resolution was intentionally chosen |
"I used type for precision." |
the subclass behavior being rejected | state what subclass-specific variation would make the broader check unsafe |
| "The helper is safer now." | the exact user promise that changed | write the new default contract in one sentence |
| "Static lookup is better." | better for which question | say whether you needed attached object truth or resolved runtime value truth |
Review what each answer can support¶
| Answer | Defensible reasoning | Common wrong turn | What it proves | What it does not prove |
|---|---|---|---|---|
| 1 names and storage | it compares dir and vars on the same object |
treating visible class names as instance fields | these discovered names and stored names differ | that dir is passive for every type |
| 2 dynamic access | it records a counter before and after each operation | calling hasattr a safe pre-check |
this property executed once per dynamic check | that every attribute read has the same cost |
| 3 type checks | it names the extension policy before selecting a check | calling exactness "more precise" without a rejection rule | the role check accepts this valid extension | that exact checks are never appropriate |
| 4 callability | it keeps the gate, valid call, and failing call separate | using callable as proof of argument fitness |
this type supports call syntax and owns a separate input policy | that invocation is cheap, reversible, or safe during inspection |
| 5 static lookup | it identifies attachment first and execution second | interpreting static absence as dynamic absence | fallback is attached and dynamic lookup generated the value | that static lookup reveals the eventual runtime value |
| 6 debug helper | it begins from a bounded non-evaluating contract | adding recursion and evaluation without new policy tests | the baseline reports selected attachments without triggering the target counters | that arbitrary repr, recursion, or concurrency is safe |
| 7 inheritance | it ties polymorphism to a plugin-style extension boundary | rejecting extensions merely because their concrete type differs | the chosen hierarchy supports role-compatible acceptance | that every subclass satisfies unrelated behavioral contracts |
| 8 fallback | it inspects fallback machinery before resolving a missing name | using hasattr and triggering fallback while "checking" |
the static route stays non-executing for this target | that fallback execution is always unsafe when explicitly requested |
| 9 least-risk order | it stops as soon as the current evidence answers the question | escalating through every tool regardless of need | the workflow minimizes unnecessary behavior for the stated question | that the same order answers every debugging question |
| 10 packet | it joins evidence, tests, reasoning, and capstone transfer | presenting green commands as the explanation | another learner can reproduce the Module 02 boundary claims | that all capstone metaprogramming mechanisms are justified |
Capstone transfer answer¶
A strong transfer compares public behavior rather than vaguely saying "inspect the capstone":
The manifest route returns field and action descriptions without invoking a delivery
action. The demo constructs a concrete plugin, resolves deliver, and calls it. The
important answer is not that one command is safe and the other is dangerous. It is that
they promise different product behavior:
- manifest: describe registered structure
- demo: execute an incident-delivery behavior with explicit inputs
The capstone proof test
test_manifest_does_not_construct_or_invoke_registered_plugin strengthens the first
claim: it uses an event-recording plugin and asserts that neither construction nor action
execution happened during manifest generation.
The capstone contains mechanisms that later modules will explain. Module 02 proves only that a reviewer can distinguish the observation route from the execution route and choose evidence accordingly.
What strong Module 02 answers have in common¶
Across the whole set, strong answers share the same habits:
- they state the observation question before choosing a builtin
- they separate discovery, storage inspection, classification, and value resolution
- they treat dynamic attribute access as execution-capable behavior
- they use static lookup when tooling needs attachment truth more than runtime behavior
- they separate inheritance-aware questions from exact-identity questions
- they leave behind a least-risk workflow another learner can reuse without guessing
If an answer still depends on "I just checked the attribute," revise it until you can say what kind of observation or execution actually happened.