From 2599adbfecbbe7254104fa05b1b13aed80930f0c Mon Sep 17 00:00:00 2001 From: Loris Ercole Date: Tue, 10 Nov 2020 18:36:06 +0100 Subject: [PATCH] Add AttributeDict class A class that stores values in a dictionary, but whose keys are also exposed as attributes. --- thermocepstrum/utils/attributedict.py | 79 +++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 thermocepstrum/utils/attributedict.py diff --git a/thermocepstrum/utils/attributedict.py b/thermocepstrum/utils/attributedict.py new file mode 100644 index 0000000..932520a --- /dev/null +++ b/thermocepstrum/utils/attributedict.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + + +class AttributeDict(dict): # pylint: disable=too-many-instance-attributes + """ + This class internally stores values in a dictionary, but exposes + the keys also as attributes, i.e. asking for attrdict.key + will return the value of attrdict['key'] and so on. + + Raises an AttributeError if the key does not exist, when called as an attribute, + while the usual KeyError if the key does not exist and the dictionary syntax is + used. + """ + + def __init__(self, dictionary=None): + """Recursively turn the `dict` and all its nested dictionaries into `AttributeDict` instance.""" + super().__init__() + if dictionary is None: + dictionary = {} + + for key, value in dictionary.items(): + if isinstance(value, Mapping): + self[key] = AttributeDict(value) + else: + self[key] = value + + def __repr__(self): + """Representation of the object.""" + return f'{self.__class__.__name__}({dict.__repr__(self)})' + + def __getattr__(self, attr): + """Read a key as an attribute. + + :raises AttributeError: if the attribute does not correspond to an existing key. + """ + try: + return self[attr] + except KeyError: + errmsg = f"'{self.__class__.__name__}' object has no attribute '{attr}'" + raise AttributeError(errmsg) + + def __setattr__(self, attr, value): + """Set a key as an attribute.""" + try: + self[attr] = value + except KeyError: + raise AttributeError( + f"AttributeError: '{attr}' is not a valid attribute of the object '{self.__class__.__name__}'") + + def __delattr__(self, attr): + """Delete a key as an attribute. + + :raises AttributeError: if the attribute does not correspond to an existing key. + """ + try: + del self[attr] + except KeyError: + errmsg = f"'{self.__class__.__name__}' object has no attribute '{attr}'" + raise AttributeError(errmsg) + + def __deepcopy__(self, memo=None): + """Deep copy.""" + from copy import deepcopy + + if memo is None: + memo = {} + retval = deepcopy(dict(self)) + return self.__class__(retval) + + def __getstate__(self): + """Needed for pickling this class.""" + return self.__dict__.copy() + + def __setstate__(self, dictionary): + """Needed for pickling this class.""" + self.__dict__.update(dictionary) + + def __dir__(self): + return self.keys()