Visible Names and Stored State¶
Page Maps¶
graph LR
family["Python Programming"]
program["Python Meta-Programming"]
section["Runtime Observation Inspection"]
page["Visible Names and Stored State"]
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"]
The safest early observation habit in Python is learning not to ask one vague question about an object.
Instead, separate these questions:
- what names might be meaningful here?
- what state is physically stored on this object right now?
- what value would normal attribute lookup produce?
This page focuses on the first two. That separation prevents a large share of
introspection mistakes before getattr even enters the picture.
The sentence to keep¶
When you inspect an object, ask:
am I discovering candidate names, or am I reading stored state?
If you do not separate those questions, you will eventually mistake one for the other and reach for a riskier tool than you needed.
The four surfaces that matter first¶
Module 02 starts with four basic surfaces:
dir(obj)for best-effort name discoveryvars(obj)for attribute dictionaries when they existobj.__dict__for direct stored-state access when present__slots__for classes that replace dictionary-backed instance storage with a fixed layout
These are related, but they are not interchangeable.
dir(obj) discovers names, not truth¶
dir(obj) returns a best-effort list of names that may be meaningful on the object.
That list may draw from:
- the instance
- the class
- base classes in the MRO
- a custom
__dir__implementation
Two review points matter:
- treat the result like a discovery aid, not a contract
- do not rely on ordering as semantic meaning
CPython often sorts the result, but that is a debugging convenience rather than a promise the module should teach as truth.
vars(obj) and obj.__dict__ read stored state when it exists¶
vars(obj) returns the object's attribute dictionary when one exists.
Typical cases:
- instances usually expose a mutable dictionary
- modules expose a mutable dictionary
- classes expose a namespace view
If the object has no attribute dictionary, vars(obj) raises TypeError.
That matters because vars(obj) answers a much narrower and more honest question than
dir(obj):
what is physically stored on this object right now?
It does not tell you everything lookup could resolve. It tells you what storage is present.
__slots__ changes the storage model¶
When a class declares __slots__, instance storage may move out of a per-instance
dictionary into fixed slot storage.
That means:
- the instance may still expose discoverable names in
dir(obj) vars(obj)may fail because no__dict__exists- generic tools that assume dictionaries can break
This is one reason Module 02 keeps names and storage separate. Slots make the difference visible immediately.
One picture of discovery versus storage versus resolution¶
graph TD
visible["Visible names<br/>dir(obj)"]
stored["Stored state<br/>vars(obj) or obj.__dict__"]
resolved["Resolved value<br/>getattr(obj, name)"]
visible --> stored --> resolved
Caption: discovery is not storage, and storage is not normal attribute resolution.
Example: discoverable names can exceed stored state¶
class Explorer:
def __init__(self):
self.instance_only = "personal"
def method(self):
return "ok"
e = Explorer()
assert "instance_only" in vars(e)
assert "method" not in vars(e)
assert "method" in dir(e)
The method is discoverable because the class provides it, but it is not physically stored in the instance dictionary.
That difference is ordinary and important.
Example: slotted instances are visible without being dict-backed¶
class Slotted:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
s = Slotted(1, 2)
assert "x" in dir(s)
try:
vars(s)
except TypeError:
missing_dict = True
else:
missing_dict = False
assert missing_dict is True
This is a good reminder that visibility and storage are different questions.
dir() is not fully passive¶
Even the safer-looking discovery tool has a boundary:
That is why Module 02 treats dir() as lower risk than value resolution, not as
zero-risk. A custom __dir__ method can still execute code.
Prefer vars(obj) over probing for __dict__ with hasattr¶
A common anti-pattern is:
That is weaker than it looks because hasattr itself performs attribute access and can
run user code.
The safer pattern is:
This is a recurring Module 02 habit:
- ask the narrower question directly
- let the specific tool fail honestly
- avoid general probing that executes more protocol than needed
Classes expose namespace views, not plain instance-style state¶
For classes, vars(cls) and cls.__dict__ usually expose a namespace view rather than a
plain mutable instance dictionary.
That view is often a mappingproxy on CPython:
- it reflects the class namespace
- it is read-only as a mapping interface
- class assignment still mutates the underlying namespace
This is another reason to keep the storage question narrow. "Stored state" has slightly different shapes across instances, modules, and classes.
Review rules for this boundary¶
When reviewing runtime observation code, keep these questions close:
- is the tool trying to discover candidate names or read actual stored state?
- is
dir()being treated like a truth source instead of a discovery helper? - is
hasattrbeing used where a directvars()or explicit try/except would be safer? - does the code handle slotted objects honestly instead of assuming every instance has a dictionary?
- does the review distinguish instance storage from class-provided names?
What to practice from this page¶
Try these before moving on:
- Write
state_view(obj)that returnsset(dir(obj))plusvars(obj)when available. - Run it on a normal instance, a slotted instance, and a builtin such as
list(). - Explain one attribute that appears in
dir(obj)but not in stored state.
If those feel ordinary, you are ready for the next boundary: dynamic attribute access is powerful, but it is not passive inspection.
Continue through Module 02¶
- Previous: Overview
- Next: Dynamic Attribute Access Is Not Inspection
- Practice: Exercises
- Terms: Glossary