Attribute Semantics¶
Resolution order¶
Attribute access on AttributeDict prefers real type attributes first
, then falls back to the mapping key:
- Look up the name as a real type attribute (methods, descriptors, dunders, etc.) via the normal attribute machinery. If found, return it.
- Otherwise, if the name is a
strthat is a valid Python identifier and a key in the mapping, return the key's value. - If neither exists, raise
AttributeError.
Here's the practical upshot : a key named items, keys,
values, get, update, or copy does not shadow the method on the
attribute path:
d = AttributeDict(items=42)
d.items # <built-in method items...> — the real dict method
list(d.items) # [('items', 42)]
d["items"] # 42 — mapping access keeps the key's value
dict.items(d) # dict_items([('items', 42)]) — the mapping view
Mapping access (d[name], d[name] = v, del d[name]) is untouched and
always operates on keys.
Attribute set / delete¶
d.name = valuealways storesd["name"] = value.del d.namedeletes the key"name"; if absent it raisesAttributeError(note the deviation fromdel d["missing"], which raisesKeyError).
Edge-case keys¶
| Key | Mapping access | Attribute access |
|---|---|---|
"normal" |
yes | yes (key value) |
"with-space" |
yes | no (AttributeError) |
"123" |
yes | no |
"_private" |
yes | yes (key value, when not a type attribute) |
"__dunder__" |
yes | yes* (see note) |
"items"/"keys"/"get" |
yes | the dict method |
"foo-bar" / "" |
yes | no |
non-str (1, None, object, (1, 2)) |
yes | no |
* __dunder__ keys are addressable via attribute syntax only when they don't
collide with real type internals; the behavior is tested.
Non-string keys: mapping only¶
Keys don't have to be strings — AttributeDict accepts any hashable key,
just like dict. But attribute syntax only understands string identifiers,
so non-string keys are reachable only through mapping access:
class A: ...
attr_d = AttributeDict({A: None})
attr_d[A] # None — mapping access: the class object is a valid key
attr_d.A # AttributeError: object has no attribute 'A'
In other words, attr_d[A] reads the key's value, while attr_d.A looks
for a string attribute named "A" — which doesn't exist, so it raises
AttributeError. Non-string keys are documented and tested (see the table
above).
Why "type attributes win"?¶
See the decisions
and the FAQ. Short version: real dict attributes
(methods/descriptors) win on the attribute path, so d.items reads as a
method exactly like it does on a plain dict, while mapping access still
returns key values. The two paths are intentionally asymmetric — and
documented so you're never surprised.