Add default fallback for missing keys#107
Conversation
|
@drgrib take a look when you get a chance |
|
Thank you for your PR. I'll try to have a closer look this week. Unfortunately time is pretty limited in this crazy time. |
FelixSchwarz
left a comment
There was a problem hiding this comment.
Thank you very much - solid PR which needs just a few changes.
|
Oh, and if you rebase to/merge in the latest master branch, you should also get github actions running again. |
2967e03 to
8a412af
Compare
- Raise ValueError when _default is provided with _dynamic=False instead of silently overriding _dynamic - Nest the _default check inside the existing "k not in self._map" branch in __getitem__ - Preserve _default through copy()/deepcopy() - Add tests for the _dynamic=False error, copy, and deepcopy; reorder the missing-key assertions to check setup before behavior Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7b967f1 to
2c76767
Compare
|
@FelixSchwarz I think I got all your comments. |
There was a problem hiding this comment.
I'm sorry that it took so long but I thought about the PR a bit and something felt "off" after my first review. After pondering this a bit more I think I now have a better picture in my mind.
This is what annoyed me:
>>> from dotmap import DotMap
>>> m = DotMap({}, _default=[])
>>> m.x
[]
>>> m.y
[]
>>> m.x.append(42)
>>> m.y
[42]
That is clearly wrong.
After a lot of back and forth I came up with two options:
- return
deepcopy(self._default)on each miss - replace
_defaultwith_default_factory, called once per miss. Similar to defaultdict, e.g._default_factory=strinstead of_default=''
The latter version is a bit more explicit but on the other hand causes a bit longer lines for each user of that feature. But using a _default_factory just simplify the internal code of dotmap and it fits an established pattern in Python to avoid silently shared mutable values.
It prevents the problem by construction, and it actually simplifies the diff. _default_provided can go away since None simply means "not set". A factory must be callable, we can raise a TypeError for non-callables.
What do you think?
I and Claude both like the default factory approach :-) |
Per review feedback: a mutable _default value was shared across all missing keys, so mutating one leaked into every other miss. Following the collections.defaultdict pattern, _default_factory takes a callable invoked on each miss; the result is stored under the key and returned, giving every key its own independent value. Non-callables raise TypeError, and _default_provided is gone since None now simply means "not set". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixes #74.
This adds an opt-in
_defaultconstructor argument for callers who want missing attributes to return a fallback value instead of autovivifying an emptyDotMap. Existing behavior remains unchanged unless_defaultis provided.Example:
Tests: