Descriptor Composition and Wrapper Fields¶
Page Maps¶
graph LR
family["Python Programming"]
program["Python Meta-Programming"]
section["Descriptor Systems Validation Framework Design"]
page["Descriptor Composition and Wrapper Fields"]
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"]
Once descriptor systems start carrying several concerns at once, teams hit a design fork:
- create more subclasses for every combination
- or layer behavior around an inner field
This core focuses on the second option.
The sentence to keep¶
Descriptor composition wraps one field descriptor in another so new behavior can be added through delegation instead of by multiplying subclasses for every field variant.
That is useful, but only if the layers stay inspectable.
Why composition shows up here¶
By Module 08, a field might want more than one concern:
- storage
- validation
- coercion
- logging
- caching
If every combination becomes its own subclass, the design quickly turns into a class tree that is harder to review than the field logic itself.
Composition is the usual escape hatch.
A delegation base¶
class FieldWrapper:
def __init__(self, inner):
self.inner = inner
def __set_name__(self, owner, name):
self.inner.__set_name__(owner, name)
def __get__(self, obj, owner=None):
if obj is None:
return self
return self.inner.__get__(obj, owner)
def __set__(self, obj, value):
self.inner.__set__(obj, value)
This is the useful center of the pattern:
- the wrapper owns one extra concern
- the inner descriptor still owns the underlying field contract
That keeps layering explicit.
A validated wrapper¶
def validated(validator, inner):
class ValidatedField(FieldWrapper):
def __init__(self):
super().__init__(inner)
def __set__(self, obj, value):
self.inner.__set__(obj, validator(value))
return ValidatedField()
This shape works because the wrapper does one narrow thing:
- it transforms or rejects the incoming value
- then it delegates ordinary field storage to the inner descriptor
That is much easier to reason about than a whole new subclass hierarchy for every rule.
What good composition looks like¶
Good wrapper-field composition usually has these traits:
- one obvious inner owner
- one obvious added concern per layer
- consistent forwarding of
__set_name__,__get__, and__set__ - shallow enough depth that a reviewer can still follow it mentally
That last point matters more than people think.
What bad composition looks like¶
Composition becomes harder to trust when:
- wrappers hide the inner descriptor completely
- different layers each mutate values in surprising ways
__set_name__is forwarded inconsistently- the stack grows deep enough that field behavior becomes an archaeological dig
At that point, “composable” is no longer the same as “clear.”
Why __set_name__ deserves special care¶
One easy bug in wrapped descriptors is forwarding __set_name__ incorrectly.
Problems include:
- never forwarding it to the inner field
- forwarding it more than once
- partially shadowing the name state in both wrapper and inner descriptor
Because __set_name__ often initializes storage keys and public names, this is not a
small detail. It is part of whether the wrapped field works at all.
Composition is not a license for endless stacks¶
There is a real limit to how many field layers remain readable.
As a practical rule, very shallow composition is much easier to justify than deep stacks.
If a field design now wants:
- logging
- validation
- coercion
- caching
- encryption
- remote persistence
all as separate wrapper layers, the design may already be asking for a stronger framework abstraction than “one wrapped descriptor.”
Why this is better than subclass explosion¶
Used well, composition lets a field system say:
- here is the base storage or coercion field
- here is one wrapper adding validation
- here is another wrapper adding one more cross-cutting concern
That makes behavior additive and local instead of forcing a huge family of subclasses such as:
CachedStringFieldValidatedCachedStringFieldLoggedValidatedCachedStringField
and so on.
The goal is not sophistication. The goal is keeping ownership legible.
Review rules for wrapper fields¶
When reviewing descriptor composition, keep these questions close:
- what does the inner descriptor own?
- what one extra concern does this wrapper add?
- is
__set_name__delegated correctly and only once? - how many layers do you need to traverse to understand one attribute write?
- has the stack grown large enough that a different framework abstraction would be clearer?
What to practice from this page¶
Try these before moving on:
- Wrap one validating field around one storage field and explain which concern each layer owns.
- Show one bug caused by forgetting to forward
__set_name__. - Write one short review note rejecting a four-layer field stack as too opaque.
If those feel ordinary, the next step is hint-driven validation and coercion, where the descriptor starts reading type metadata as runtime evidence.
Continue through Module 08¶
- Previous: External Storage Descriptors
- Next: Hint-Driven Validation and Coercion
- Return: Overview
- Terms: Glossary