Dynamic Members and Static Structure¶
Page Maps¶
graph LR
family["Python Programming"]
program["Python Meta-Programming"]
section["Signatures Provenance Runtime Evidence"]
page["Dynamic Members and Static Structure"]
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"]
By Module 03, the course has enough pieces to name one of the most important inspection choices in metaprogramming:
do you want the value normal lookup would produce, or do you want the raw structure attached to the object?
This page puts that choice into concrete tooling terms with:
inspect.getmembersinspect.getattr_static
This is a high-value lesson because many weak tools do not fail loudly here. They simply slide from "inspection" into "execution" without admitting it.
The sentence to keep¶
When enumerating members, ask:
am I trying to collect dynamic values, or am I trying to inspect attached structure without executing runtime behavior?
That question should drive the tool choice.
Keep one review question beside it:
if I use the dynamic path here, what behavior am I allowing to run while pretending to inspect?
inspect.getmembers is dynamic enumeration¶
inspect.getmembers(obj, predicate=None) works roughly like this:
- discover names with
dir(obj) - call
getattr(obj, name)for each name - optionally filter the resulting values with a predicate
- return sorted
(name, value)pairs
That is useful, but it is not passive.
Because it uses dynamic lookup, it can:
- execute properties
- trigger descriptors
- trigger
__getattr__ - activate proxy behavior
So getmembers is a value-oriented tool, not a structural-inspection tool.
That wording matters. It is not "bad." It is simply answering a different question:
- "what values does normal runtime lookup currently produce?"
That is a legitimate question, but it is not the same as:
- "what members are attached here before behavior runs?"
inspect.getattr_static is structural inspection¶
inspect.getattr_static(obj, name) tries to retrieve the raw attribute object without
running normal lookup behavior.
That means it can return:
- a property object
- a function stored on the class
- a descriptor object
- a plain attached value
without automatically executing it.
That is why it fits library and framework introspection much better when the goal is structure rather than behavior.
Use this table when the choice feels too abstract:
| Real question | Better surface | Why |
|---|---|---|
| Which descriptor object is attached under this name? | getattr_static |
attachment truth is the goal |
| What value would normal lookup yield right now? | getmembers or getattr |
resolved runtime value is the goal |
| Which members can I inspect without triggering application behavior? | static helper route | safety is part of the inspection contract |
One picture of the difference¶
graph TD
dynamic["Dynamic enumeration<br/>getmembers(obj)"]
dir["dir(obj)"]
getattr["getattr(obj, name)"]
effects["descriptor execution<br/>property evaluation<br/>fallback hooks"]
static["Static lookup<br/>getattr_static(obj, name)"]
raw["raw attached object"]
dynamic --> dir --> getattr --> effects
static --> raw
Caption: one path collects resolved values, the other inspects what is attached before runtime behavior runs.
A property shows the difference clearly¶
import inspect
class Example:
@property
def expensive(self):
print("SIDE EFFECT: property executed")
return 123
def method(self):
pass
obj = Example()
_ = inspect.getmembers(obj)
raw = inspect.getattr_static(Example, "expensive")
assert isinstance(raw, property)
The printed side effect is the important boundary:
getmembersresolved the value dynamicallygetattr_staticrevealed the attached structure without evaluation
The key self-study habit is to say which truth you got:
- attached-object truth
- resolved-value truth
If an answer just says "I inspected the member," it is still too vague.
Structural enumeration often needs a custom helper¶
Because getmembers is dynamic, framework or tooling code often wants its own structural
enumeration helper:
import inspect
def static_getmembers(obj, predicate=None):
for name in dir(obj):
value = inspect.getattr_static(obj, name)
if predicate is None or predicate(value):
yield name, value
That helper still inherits the lower-risk caveat around dir(obj), but it avoids the
larger mistake of resolving every name dynamically by default.
That is often good enough for framework discovery, manifests, or review tooling. You do not always need perfect structure recovery to make a much safer choice than broad dynamic enumeration.
Use the tool that matches the question¶
Good reasons to use getmembers:
- quick REPL exploration
- controlled debugging where executing descriptors is acceptable
- value-oriented inspection when you explicitly want runtime semantics
Good reasons to use getattr_static:
- framework discovery
- manifest or schema extraction
- class and descriptor inspection
- any tool that should not trigger business behavior while observing structure
The key discipline is not to use the dynamic tool by habit when the question is clearly structural.
Weak review move to reject:
- "I used
getmembersbecause it shows me everything"
It does not show "everything." It shows the results of dynamic lookup, which may be more than you wanted and less than you safely understand.
Static structure is often the stronger review surface¶
When reviewing framework code, the structural question is often the one that matters most:
- what descriptors are attached?
- what functions are defined on the class?
- what property objects exist?
- what raw members make up the framework contract?
Dynamic values may be interesting later, but they are not the first truth the reviewer needs.
That order matters for standalone learners:
- ask the structural question first
- collect attached-object evidence
- cross into value resolution only when the runtime question actually requires it
Evidence practicum: make hidden execution measurable¶
The course target makes the difference observable instead of asking you to trust a warning:
$ make evidence-lab |
python3 -c 'import json, sys; print(json.load(sys.stdin)["members"])'
{'dynamic_value': 7, 'evidence_strength': 'structural-before-dynamic', 'property_reads_after_getmembers': 1, 'property_reads_after_static_lookup': 0, 'static_value_is_property': True}
MemberEvidenceTarget.risk_score increments property_reads whenever the property body
runs. The two counters turn an otherwise invisible boundary into executable evidence:
inspect.getmembers(instance)returns the resolved value7and the counter becomes1inspect.getattr_static(instance, "risk_score")returns the property object and the counter stays0
Both results are truthful, but they answer different questions. The dynamic value is evidence about normal lookup behavior. The property object is evidence about attached structure. Neither should be relabeled as the other.
Transfer into the worked implementation¶
Run make evidence-repr. Its packet reports
"dynamic_member_lookup": false and "property_reads_after_repr": 0. Then run
make evidence-repr-test to prove that the representation works for dictionary-backed,
slotted, inherited-slot, private-slot, and custom-lookup cases.
This transfer matters: the safer member-inspection rule changes an actual utility design.
The representation does not enumerate names with dir() and then resolve them. It reads
owned instance storage and deliberately excludes the risk_score property.
Investigation¶
Temporarily replace the storage reader in safe_repr with
getattr(instance, "risk_score"). The test should expose the property read. Restore the
storage implementation before continuing. The failure is the lesson: a more informative-
looking string silently became application execution.
Review rules for member inspection¶
When reviewing member-inspection code, keep these questions close:
- is the code trying to inspect attached structure or resolved runtime values?
- is
getmembersbeing used wheregetattr_staticwould better match the tool's purpose? - does the enumeration path risk executing properties or proxy behavior unintentionally?
- is a custom structural helper justified instead of broad dynamic enumeration?
- does the code document when it chooses to cross from structure into value resolution?
Self-study lab¶
Before leaving this page, write one member-review note in this format:
- the member name under review
- the structural question
- the dynamic question
- the exact point where you chose one over the other
If you cannot name both questions separately, the tool choice is probably still accidental.
What to practice from this page¶
Try these before moving on:
- Compare
getmembers(obj)with astatic_getmembers(obj)helper on a class that has a property. - Use
getattr_staticto list properties on a class without evaluating them. - Explain one case where dynamic member values are the right goal and one where raw structure is the right goal.
If those feel ordinary, the final core can close the module with the highest-risk introspection surface here: frames and stack inspection.
Continue through Module 03¶
- Previous: Provenance Helpers and Best-Effort Recovery
- Next: Frames and Diagnostic-Only Runtime Evidence
- Practice: Exercises
- Terms: Glossary