I am attempting to set attributes on a dict key and have a feeling my approach is wrong. How would I get the current key inside alfa? Alfa in production will be used to create and update a dataclass.
class Dictn(dict):
def __init__(self, *args, **kwds):
super(Dictn, self).__init__(*args, **kwds)
self.__dict__ = self
def __getitem__(self, key):
if key not in self.keys():
self.__setitem__(key, None)
dict.__getitem__(self, key)
return self
def alfa(self, **kwds):
''' Need current key to set attributes on it '''
self.__setattr__(**kwds)
def beta(self, **kwds):
''' Need current key to set attributes on it '''
self.__setattr__(**kwds)
dictn = Dictn()
dictn['ABC'].alfa(es=1, te=2)
dictn['ABC'].beta(es=3, te=4)
A dictionary stores objects that can be recovered as key-value pairs. (Remember, everything in python is an object.) If you want those objects to have additional attributes, you need to edit them directly, not the dict class which only acts as a container. The dict doesn't really care what it stores, and it certainly isn't (and shouldn't be) responsible for handling any properties on its contents.
(By the way, dictn["ABC"] represents a value in the dictionary, not a key. They key here is "ABC". I'm not sure if you're just confusing the definitions here or if you mean something else entirely.)
You could set an instance variable to the key used in __getitem__().
class Dictn(dict):
def __init__(self, *args, **kwds):
super(Dictn, self).__init__(*args, **kwds)
self.__dict__ = self
def __getitem__(self, key):
if key not in self.keys():
self.__setitem__(key, None)
self.current_key = key
dict.__getitem__(self, key)
return self
def alfa(self, **kwds):
// use self.current_key to get current_key to set attributes on it
self.__setattr__(**kwds)
def beta(self, **kwds):
// use self.current_key to get current_key to set attributes on it
self.__setattr__(**kwds)
Solution is to use a class as an attribute setter:
#dataclass()
class Position:
es: int = None
te: int = None
class ValueObj():
def alfa(self, **kwds):
setattr(self, 'alfa', Position(**kwds))
def beta(self, **kwds):
setattr(self, 'beta', Position(**kwds))
class Dictn(dict):
def __init__(self, *args, **kwds):
super(Dictn, self).__init__(*args, **kwds)
self.__dict__ = self
def __getitem__(self, key):
if key not in self.keys():
self.__setitem__( key,
ValueObj(),
)
return dict.__getitem__(self, key)
dictn = Dictn()
dictn['ABC'].alfa(es=1, te=2)
dictn['ABC'].alfa
>> Position(es=1, te=2)
This answer was posted as an edit to the question Attributes as dictionary values by the OP misantroop under CC BY-SA 4.0.
Related
Here is a simple code of attribute dict:
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
More context:
https://stackoverflow.com/a/14620633/1179925
I can use it like:
d = AttrDict({'a':1, 'b':2})
print(d)
I want this to be possible:
d.b = 10
print(d)
But I want this to be impossible:
d.c = 4
print(d)
Is it possible to throw an error on new key creation?
You could check if they are already in there
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__dict__ = self
def __setattr__(self, key, value):
if key not in [*self.keys(), '__dict__']:
raise KeyError('No new keys allowed')
else:
super().__setattr__(key, value)
def __setitem__(self, key, value):
if key not in self:
raise KeyError('No new keys allowed')
else:
super().__setitem__(key, value)
First I thought this would be a bad idea since no initial values could be added but from the builtins it states the following:
dict(iterable) -> new dictionary initialized as if via:
d = {}
for k, v in iterable:
d[k] = v
So this does allow you to change the methods without them having effect on the initialization of the Class as it creates a new one from {} instead of from its own instance.
They will be able to change __ dict __ always though..
You can override the __setattr__ special method.
class AttrDict(dict):
def __init__(self, *args, **kwargs):
super(AttrDict, self).__setattr__('_initializing', True)
super(AttrDict, self).__init__(*args, **kwargs)
self.__dict__ = self
super(AttrDict, self).__setattr__('_initializing', False)
def __setattr__(self, x, value):
if x == '_initializing':
raise KeyError("You should not edit _initalizing")
if self._initializing or x in self.__dict__.keys():
super(AttrDict, self).__setattr__(x, value)
else:
raise KeyError("No new keys allowed!")
Note that I needed to add an attribute _initializing to let __setattr__ distinguish between attributes created by __init__ and attributes created by users.
Since python does not have private attributes, users might still set _initializing to True and then add their attributes to the AttrDict instance, so I added a further check to be sure that they are not trying to edit _initializing.
It is still not 100% safe, since an user could still use super() to set _initializing to True.
I am trying to expose the classes dictionary making it both and subscriptable and be able to iterate through the dict values. Here is the class :
class ExampleClass():
def __init__(self, *args, **kwargs):
for key, value in self.kwargs.items():
setattr(self, key, value)
for arg in args:
setattr(self, arg, arg) if isinstance(arg, str) else setattr(self, str(arg), arg)
def __str__(self):
return 'This is the example class'
def __getitem__(self, obj):
return self.__dict__[obj]
def __len__(self):
return len(self.__dict__.items())
If we create an instance and pass in these values :
cls = ExampleClass(123456,'cash', name='newexample', id=1)
This will store all of the args and kwargs as instance attributes, and using the syntax cls['id'] will return 1 as expected. But when I use the syntax for i in cls: print(i) I get a KeyError : KeyError : 0
How can I make this object's dict both subscriptable and iterable ?
You need to implement the __iter__ method.
class ExampleClass():
def __init__(self, *args, **kwargs):
for key, value in kwargs.items():
setattr(self, key, value)
for arg in args:
setattr(self, arg, arg) if isinstance(arg, str) else setattr(self, str(arg), arg)
def __str__(self):
return 'This is the example class'
def __getitem__(self, obj):
return self.__dict__[obj]
def __len__(self):
return len(self.__dict__.items())
def __iter__(self):
return iter(self.__dict__)
cls = ExampleClass(123456,'cash', name='newexample', id=1)
print(cls['cash'])
print(cls['name'])
for i in cls: print(i)
This is the method which is called to create an iterator for your type so that it can be iterated. Your underlying dict already implements it, so you're sort of just proxying it here.
To make a class subscriptable, it must contain dunder getitem(), it may or may not contain dunder iter().
At the same time, an iterable must contain iter().
To check if your class has the required method, perform print(dir(your_class)), and look for the respective dunder function.
If you don't have one, create it.
I have a custom dictionary class NewDict with its own set of methods and what not. I would like to make another custom dictionary class CustomDictionary, such that the value of every key in this custom dictionary is of the class NewDict.
Right now I have:
from collections import OrderedDict
from NewDict import NewDict
class CustomDictionary(OrderedDict):
def __init__(self, *args, **kwargs):
super(CustomDictionary, self).__init__(*args, **kwargs)
so what do I have to add so that this class knows that every value in it is of NewDict?
class NewDict(dict):
_keys = ['my_key_1', 'my_key_2', ...]
def __init__(self, **kwargs):
for key in self._keys:
if key in kwargs:
self[key] = kwargs[key]
else:
self[key] = None
<various methods>
As I suggested in my comment, you can implements collections.MutableMapping.
Here is an exemple using an internal dictionary. You can use a classic dict or any other dictionary-like classes.
class NewDict(dict):
pass
import collections
class CustomDictionary(collections.MutableMapping):
""" A dictionary which contains only ``NewDict`` values. """
def __init__(self, *args, **kwargs):
self._data = dict() # or collections.OrderedDict, etc.
self.update(*args, **kwargs)
def __iter__(self):
return self._data.__iter__()
def __setitem__(self, key, value):
if not isinstance(value, NewDict):
raise TypeError(repr(type(value)))
self._data.__setitem__(key, value)
def __delitem__(self, key):
self._data.__delitem__(key)
def __getitem__(self, key):
return self._data.__getitem__(key)
def __len__(self):
return self._data.__len__()
Like presented by Or Duan, you can make type checking. I prefer using isinstance for that, to allow instances of a subclasses of NewDict.
Pros: collections.MutableMapping implements all classic dict methods, like get, update, setdefault and so on.
Cons: this implementation use Abstract Base Classes which insert some magic methods in your class. But they are documented.
Then, you can use this dictionary like any other dictionary:
custom_dict = CustomDictionary()
custom_dict["key1"] = NewDict()
custom_dict["key1"]["my_key_1"] = 3.14
inner_dict = custom_dict["key2"] = NewDict()
inner_dict["my_key_1"] = 2
inner_dict["my_key_2"] = 4
If you add the following methods, you can print the values:
def __str__(self):
return self._data.__str__()
__repr__ = __str__
With the Dictionary above:
import pprint
pprint.pprint(custom_dict)
You get:
{'key2': {'my_key_2': 4, 'my_key_1': 2}, 'key1': {'my_key_1': 3.14}}
You can achieve that by overriding the __setitem__ method. This is python3 example:
class CustomDictionary(dict):
def __setitem__(self, key, value):
if type(value) != NewDict:
raise TypeError("Can't do that :(")
Please note that this has some overhead for each insert.
Taking this question as a pointer, let's say there exists a class like the following:
class Container(object):
def __init__(self, **kwargs):
self._meta = defaultdict(lambda: None)
for attr, value in kwargs.iteritems():
self._meta[attr] = value
def __getattr__(self, key):
try:
return self._meta[key]
except KeyError:
raise AttributeError(key)
def __setattr__(self, key, value):
if key in ('_meta', '_hasattr'):
super(Container, self).__setattr__(key, value)
else:
self._meta[key] = value
This allows the following behavior:
c = Container()
c.a = 1
print(c.a) # 1
print(c.b) # None
Question: What is the best way to implement an operator such that the following works:
# Should delete the value of a from Container._meta
del c.a
Of course, one could obviously implement a method like,
def _delete(self, key):
...
But is there way to re-use a python operator to do this?
Just define the __delattr__ method:
def __delattr__(self, key):
del self._meta[key]
I would like to create a data structure that behaves like a dictionary with one added functionality which is to keep track of which keys have been "consumed". Please note that I can't just pop the values as they are being reused.
The structure should support these three cases, i.e. mark the key as consumed when accessed as:
if key in d:
...
d[key]
d.get(key)
This is what I have written:
class DictWithMemory(dict):
def __init__(self, *args, **kwargs):
self.memory = set()
return super(DictWithMemory, self).__init__(*args, **kwargs)
def __getitem__(self, key):
self.memory.add(key)
return super(DictWithMemory, self).__getitem__(key)
def __contains__(self, key):
self.memory.add(key)
return super(DictWithMemory, self).__contains__(key)
def get(self, key, d=None):
self.memory.add(key)
return super(DictWithMemory, self).get(key, d)
def unused_keys(self):
"""
Returns the list of unused keys.
"""
return set(self.keys()).difference(self.memory)
As I am not very familiar with the internals of dict, is there a better way to achieve this result?
Here's a solution that abstracts everything away inside a metaclass. I'm not sure if this is really any more elegant, but it does provide some amount of encapsulation should you change your mind about how to store the used keys:
class KeyRememberer(type):
def __new__(meta, classname, bases, classDict):
cls = type.__new__(meta, classname, bases, classDict)
# Define init that creates the set of remembered keys
def __init__(self, *args, **kwargs):
self.memory = set()
return super(cls, self).__init__(*args, **kwargs)
cls.__init__ = __init__
# Decorator that stores a requested key in the cache
def remember(f):
def _(self, key, *args, **kwargs):
self.memory.add(key)
return f(self, key, *args, **kwargs)
return _
# Apply the decorator to each of the default implementations
for method_name in [ '__getitem__', '__contains__', 'get' ]:
m = getattr(cls, method_name)
setattr(cls, method_name, remember(m))
return cls
class DictWithMemory(dict):
# A metaclass that ensures the object
# has a set called 'memory' as an attribute,
# which is updated on each call to __getitem__,
# __contains__, or get.
__metaclass__ = KeyRememberer
def unused_keys(self):
"""
Returns the list of unused keys.
"""
print "Used", self.memory
return list(set(super(DictWithMemory,
self).keys()).difference(self.memory))