Module 07: Descriptors, Lookup, and Attribute Control¶
Page Maps¶
graph LR
family["Python Programming"]
program["Python Meta-Programming"]
section["Descriptors Lookup Attribute Control"]
page["Module 07"]
capstone["Incident-plugin fields"]
family --> program --> section --> page
page -.applies in.-> capstone
flowchart LR
inspect["Inspect class state"] --> classify["Classify the descriptor"]
classify --> predict["Predict the winning lookup layer"]
predict --> execute["Compare with an actual read or write"]
execute --> transfer["Review capstone field ownership"]
Module 06 introduced a small TypedField to show that runtime validation needs an
explicit owner. This module opens that owner and studies the machinery it relies on.
The goal is not to collect descriptor tricks. The goal is to predict ordinary attribute
behavior, prove where values live, and decide whether a reusable descriptor has earned
its abstraction cost.
The learning problem¶
The statement “Python calls __get__” is not enough to review a descriptor-backed API.
A useful explanation must answer all six questions:
- When does the hook run?
- Which object owns the hook?
- Is the class attribute a data descriptor, a non-data descriptor, or neither?
- Which lookup layer wins for this exact public name?
- Where does per-instance state live?
- Why is a descriptor clearer than a property or a wider class mechanism?
The executable lab keeps those questions attached to observable evidence.
Prerequisites¶
Before starting, you should be able to:
- distinguish class state from instance state
- use
vars(obj)andvars(type(obj))without assuming they perform normal lookup - explain why a property is a data descriptor
- describe what Module 06's
TypedFieldenforces and what annotations alone do not
If the property-precedence example from Module 06 is still unclear, revisit it before continuing. Module 07 generalizes that exact lookup rule.
Run the module before reading deeply¶
From the Python Metaprogramming course directory:
The first command prints four stable evidence packets:
| Packet | Question it answers |
|---|---|
protocol |
does __set_name__ make an object a descriptor? |
precedence |
which layer wins for data, non-data, and plain class attributes? |
method_binding |
what identities prove that a function became a bound method? |
quantity_field |
where does a reusable data descriptor keep canonical instance values? |
The second command tests the claims rather than merely reproducing the output.
Source route:
labs/descriptor_lookup/protocol.pylabs/descriptor_lookup/precedence.pylabs/descriptor_lookup/binding.pylabs/descriptor_lookup/quantity.pylabs/descriptor_lookup/evidence.pytests/test_*descriptor*.py
The ordinary lookup model¶
For an instance read such as delivery.endpoint, keep this order available:
flowchart TD
start["delivery.endpoint"] --> class["Find endpoint through type(delivery).__mro__"]
class --> data{"Class value has __set__ or __delete__?"}
data -->|yes| datawin["Data descriptor wins"]
data -->|no| instance{"endpoint in delivery.__dict__?"}
instance -->|yes| instancewin["Instance dictionary wins"]
instance -->|no| getter{"Class value has __get__?"}
getter -->|yes| nondata["Non-data descriptor runs"]
getter -->|no| plain["Plain class value wins, or lookup fails"]
This is a review model for ordinary lookup driven by object.__getattribute__. A class
that overrides __getattribute__, a metaclass that changes class lookup, or a fallback
through __getattr__ adds more behavior. The lab states ordinary_lookup_only: true
instead of pretending to simulate every possible attribute engine.
Core sequence¶
Follow the cores in order:
- Descriptor Protocol and
__set_name__separates protocol status from class-creation naming. - Data and Non-Data Descriptor Precedence predicts the winning lookup layer before executing it.
- Functions, Binding, and Method Descriptors
proves method binding through
__func__and__self__. - Reusable Field Descriptors and Storage separates shared descriptor configuration from per-instance values.
- Descriptor Boundaries and Attribute Ownership decides when reuse pressure justifies the mechanism.
- Worked Example: Building a Unit-Aware Quantity Descriptor
traces the shipped
Quantityimplementation and its failures. - Exercises turns the lab and the incident-plugin fields into a design studio.
- Exercise Answers calibrates the evidence and the judgment.
- Module Glossary keeps lookup, ownership, and storage terms precise during review.
What changes in the learner's model¶
| Before this module | After this module |
|---|---|
| attribute access feels like dictionary lookup with exceptions | lookup is an ordered protocol with named winning layers |
__set_name__ feels like descriptor activation |
it is class-creation configuration and does not establish descriptor status |
methods “receive self automatically” |
a function's non-data __get__ returns a method whose identities can be inspected |
| the descriptor “stores the value” | descriptor configuration and instance value storage have different owners |
| reuse sounds sufficient to justify a descriptor | repeated attribute semantics and review cost are compared explicitly |
Capstone transfer¶
The incident-plugin capstone uses Field as a data descriptor:
PluginMetacollects the descriptor objects while creating plugin classesField.__set_name__records public and private storage names- generated initialization writes through
Field.__set__ - configuration reads through
Field.__get__ - values live in each plugin instance's
__dict__
Run the capstone field contract now:
After the capstone transfer is introduced later in this module, use
make capstone-field-ownership to inspect the descriptor without constructing or invoking
a plugin. The capstone is supporting evidence: the lab teaches the mechanism first, and
the application shows why a production field family accepts the complexity.
Evidence standard¶
For every descriptor claim, leave behind:
- the static class value, found without invoking it
- the protocol hooks found on its type
- the predicted winner
- the actual result, when execution is safe and useful
- the exact instance storage key, if a value is retained
- one limitation or rejected owner
“It works” is not sufficient. “The data descriptor wins” is also incomplete unless the
reviewer can see the __set__ or __delete__ hook that establishes that category.
Exit standard¶
Move to Module 08 only when you can:
- classify a class attribute from its actual hook set
- explain why
__set_name__alone is not descriptor status - predict data and non-data lookup with a same-named instance entry
- prove a bound method's function and receiver identities
- demonstrate independent instance storage for a reusable field
- defend or reject descriptor ownership against a property and a wider class hook
- connect those conclusions to the capstone
Fieldsurface
The next module can then focus on wider attribute hooks without treating the descriptor engine underneath them as magic.