Reputation: 143
I have a behaviour that I cannot explain in my code: I want to implement a dictionary class that adds the following two behaviours to standard dictionaries:
I have somewhat achieved this behaviour by subclassing from collections.OrderedDict (to have a notion of oldest (key,item) pair) and overwriting the __setitem__
(to enforce maximum len) and __missing__
(to generate missing keys on the fly) methods:
from collections import OrderedDict
from weakref import proxy
class AttachedAutoLimitedDict(OrderedDict):
"""A special dictionary implementation that is meant to be used together with an associated object.
When a not-existing key is requested from the dictionary it attempts to generate a value to that key by calling
the generation method of the associated object. Furthermore the dictionary can be limited in size
by setting the max_len property during construction."""
def __init__(self, associated_object, max_len: int | None = None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.associated_object = proxy(associated_object)
if max_len is None:
self.__setitem__ = super().__setitem__
else:
self.__setitem__ = self.__setitemmaxlen__
self.max_len = max_len
def __missing__(self, key):
"""Overwrites the __missing__ implementation. Attempts to generate a value for a missing key
by using the key as argument to the generation method of the associated object."""
try:
self.__setitem__(key, self.associated_object.generation_method(key))
return self[key]
except AttributeError:
super().__missing__(key)
def __setitemmaxlen__(self, key, value):
"""This ensures that the dictionary does not grow beyond max_len"""
# have to do it with the +1 since len(self) is incremented before the __setitem__ call
if len(self) >= self.max_len + 1:
# this is inherited from OrderedDict, last=False specifies FIFO popping
self.popitem(last=False)
super().__setitem__(key, value)
Now the implementation seems to be working like this (haven't fully tested it yet). But I had a problemt when in the implementation of __missing__
I used self[key]=self.associated_object.generation_method(key)
instead of self.__setitem__(key, self.associated_object.generation_method(key))
, because the custom implementation of __setitem__
was never called (and hence the restriction on len was not enforced).
Does somebody know why that happens?
Upvotes: 0
Views: 39
Reputation: 143
As per @ken's suggestion in the comment, python looks up dunder methods on the class, not on the instance. Therefore dynamically assigning to __setitem__
in the instance initialization has no effect. The below works:
class AttachedAutoLimitedDict(OrderedDict):
"""A special dictionary implementation that is meant to be used together with an associated object.
When a not-existing key is requested from the dictionary it attempts to generate a value to that key by calling
the generation method of the associated object. Furthermore the dictionary can be limited in size
by setting the max_len property during construction."""
def __init__(self, associated_object, max_len: int | None = None, *args, **kwargs):
super().__init__(*args, **kwargs)
self.associated_object = proxy(associated_object)
self.max_len = max_len
def __missing__(self, key):
"""Overwrites the __missing__ implementation. Attempts to generate a value for a missing key
by using the key as argument to the generation method of the associated object."""
try:
self[key]=self.associated_icon_object.get_run_data(key)
return self[key]
except AttributeError:
super().__missing__(key)
def __setitem__(self, key, value):
"""This ensures that the dictionary does not grow beyond max_len"""
if self.max_len is not None and len(self) >= self.max_len + 1:
self.popitem(last=False)
super().__setitem__(key, value)
Upvotes: 0