Python Forum
attrdict.py - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: General (https://python-forum.io/forum-1.html)
+--- Forum: Code sharing (https://python-forum.io/forum-5.html)
+--- Thread: attrdict.py (/thread-15082.html)

Pages: 1 2


attrdict.py - Skaperen - Jan-02-2019

a while back, i got help here on PFio regarding accessing a dictionary like attributes. i coded up a full class for it. i haven't tested it thoroughly but it basically seems to be working.

attrdict.py
class attrdict:
    '''Class that is like a dictionary with items usable like attributes.

#---------------------------------------------------------------
# purpose       class that is like a dictionary with items
#               usable like attributes
#
# init usage    object = attrdict(dictionary_ref)
#               object = attrdict(dictionary_ref,key=value)
#               object = attrdict(key=value)
#
# attr usage    object.name
#
# dict usage    object[key] = value
#               value = object[key]
#
# note          attribute usage is like string keys that are
#               limited to what can be a valid identifier.
#
# note          the referenced dictionary is shared with this
#               object so changes to either are in effect for
#               the other
#---------------------------------------------------------------
'''
    def __init__(self,*args,**opts):
        if args:
            if not isinstance(args[0],(dict,attrdict)):
                raise TypeError('attrdict: passed a non-dictionary inital reference')
            self.__dict__ = args[0]
        else:
            self.__dict__ = {}
        if isinstance(opts,dict):
            if opts:
                self.__dict__.update(opts)
        if isinstance(opts,attrdict):
            if opts.__dict__:
                self.__dict__.update(opts.__dict__)

    def __eq__ (self,other):
        if isinstance(other,attrdict):
            return self.__dict__ == other
        return NotImplemented

    def __ne__ (self,other):
        if isinstance(other,attrdict):
            return self.__dict__ != other
        return NotImplemented

    def __setitem__(self,key,value):
        self.__dict__[key] = value
        return

    def __delitem__(self,key):
        del self.__dict__[key]
        return

    def __getitem__(self,key):
        return self.__dict__[key]

    def items(self):
        return self.__dict__.items()

    def keys(self):
        return self.__dict__.keys()

    def values(self):
        return self.__dict__.values()

    def __hash__(self):
        return hash(self.__dict__)

    def __contains__(self,value):
        return value in self.__dict__

    def __repr__(self):
        return repr(self.__dict__)

    def __str__(self):
        return str(self.__dict__)

    def __len__(self):
        return len(self.__dict__)

    def __bool__(self):
        return bool(self.__dict__)

    def __le__(self,other):
        return NotImplemented

    def __lt__(self,other):
        return NotImplemented

    def __ge__(self,other):
        return NotImplemented

    def __gt__(self,other):
        return NotImplemented

    def __bytes__(self,*args):
        return NotImplemented

    def __format__(self,*args):
        return NotImplemented



RE: attrdict.py - scidam - Jan-05-2019

At least one method will rise an exception (unhashable type):

def __hash__(self):
    return hash(self.__dict__)
Another issue is original dict instances are iterable, but attrdict ones aren't.
So, you probably need to accomplish the class with __iter__ and __next__ methods.


RE: attrdict.py - Skaperen - Jan-09-2019

i just removed __hash__() (i can't reason why that was in there) and made __iter__() and __next__() do their thing on the dictionary itself.

class attrdict:
    '''Class that is like a dictionary with items usable like attributes.

#---------------------------------------------------------------
# purpose       class that is like a dictionary with items
#               usable like attributes
#
# init usage    object = attrdict(dictionary_ref)
#               object = attrdict(dictionary_ref,key=value)
#               object = attrdict(key=value)
#
# attr usage    object.name
#
# dict usage    object[key] = value
#               value = object[key]
#
# note          attribute usage is like string keys that are
#               limited to what can be a valid identifier.
#
# note          the referenced dictionary is shared with this
#               object so changes to either are in effect for
#               the other
#---------------------------------------------------------------
'''
    def __init__(self,*args,**opts):
        if args:
            if not isinstance(args[0],(dict,attrdict)):
                raise TypeError('attrdict: passed a non-dictionary inital reference')
            self.__dict__ = args[0]
        else:
            self.__dict__ = {}
        if isinstance(opts,dict):
            if opts:
                self.__dict__.update(opts)
        if isinstance(opts,attrdict):
            if opts.__dict__:
                self.__dict__.update(opts.__dict__)

    def __eq__ (self,other):
        if isinstance(other,attrdict):
            return self.__dict__ == other
        return NotImplemented

    def __ne__ (self,other):
        if isinstance(other,attrdict):
    def __ne__ (self,other):
        if isinstance(other,attrdict):
            return self.__dict__ != other
        return NotImplemented

    def __setitem__(self,key,value):
        self.__dict__[key] = value
        return

    def __delitem__(self,key):
        del self.__dict__[key]
        return

    def __getitem__(self,key):
        return self.__dict__[key]

    def items(self):
        return self.__dict__.items()

    def keys(self):
        return self.__dict__.keys()

    def values(self):
        return self.__dict__.values()

    def __iter__(self):
        return self.__dict__.__iter__()

    def __next__(self):
        return self.__dict__.__next__()

    def __contains__(self,value):
        return value in self.__dict__

    def __repr__(self):
        return repr(self.__dict__)

    def __str__(self):
        return str(self.__dict__)

    def __len__(self):
        return len(self.__dict__)

    def __bool__(self):
        return bool(self.__dict__)
    def __le__(self,other):
        return NotImplemented

    def __lt__(self,other):
        return NotImplemented

    def __ge__(self,other):
        return NotImplemented

    def __gt__(self,other):
        return NotImplemented

    def __bytes__(self,*args):
        return NotImplemented

    def __format__(self,*args):
        return NotImplemented
i'm wondering if it will be just as easy to read with those empty lines removed.

class attrdict:
    '''Class that is like a dictionary with items usable like attributes.
#---------------------------------------------------------------
# purpose       class that is like a dictionary with items
#               usable like attributes
#
# init usage    object = attrdict(dictionary_ref)
#               object = attrdict(dictionary_ref,key=value)
#               object = attrdict(key=value)
#
# attr usage    object.name
#
# dict usage    object[key] = value
#               value = object[key]
#
# note          attribute usage is like string keys that are
#               limited to what can be a valid identifier.
#
# note          the referenced dictionary is shared with this
#               object so changes to either are in effect for
#               the other
#---------------------------------------------------------------
'''
    def __init__(self,*args,**opts):
        if args:
            if not isinstance(args[0],(dict,attrdict)):
                raise TypeError('attrdict: passed a non-dictionary inital reference')
            self.__dict__ = args[0]
        else:
            self.__dict__ = {}
        if isinstance(opts,dict):
            if opts:
                self.__dict__.update(opts)
        if isinstance(opts,attrdict):
            if opts.__dict__:
                self.__dict__.update(opts.__dict__)
    def __eq__ (self,other):
        if isinstance(other,attrdict):
            return self.__dict__ == other
        return NotImplemented
    def __ne__ (self,other):
        if isinstance(other,attrdict):
            return self.__dict__ != other
        return NotImplemented
    def __setitem__(self,key,value):
        self.__dict__[key] = value
        return
    def __delitem__(self,key):
        del self.__dict__[key]
        return
    def __getitem__(self,key):
        return self.__dict__[key]
    def items(self):
        return self.__dict__.items()
    def keys(self):
        return self.__dict__.keys()
    def values(self):
        return self.__dict__.values()
    def __iter__(self):
        return self.__dict__.__iter__()
    def __next__(self):
        return self.__dict__.__next__()
    def __contains__(self,value):
        return value in self.__dict__
    def __repr__(self):
        return repr(self.__dict__)
    def __str__(self):
        return str(self.__dict__)
    def __len__(self):
        return len(self.__dict__)
    def __bool__(self):
        return bool(self.__dict__)
    def __le__(self,other):
        return NotImplemented
    def __lt__(self,other):
        return NotImplemented
    def __ge__(self,other):
        return NotImplemented
    def __gt__(self,other):
        return NotImplemented
    def __bytes__(self,*args):
        return NotImplemented
    def __format__(self,*args):
        return NotImplemented



RE: attrdict.py - nilamo - Jan-09-2019

That seems like a lot of work. Why not just use dict as a the parent class, and add in __getattr__ and __setattr__ methods?

>>> class attrdict(dict):
...   def __getattr__(self, key):
...     return self[key]
...   def __setattr__(self, key, value):
...     self[key] = value
...
>>> spam = attrdict({"foo": "bar", 42: "carrots"})
>>> spam
{'foo': 'bar', 42: 'carrots'}
>>> spam.foo
'bar'
>>> spam[42]
'carrots'
>>> print(spam)
{'foo': 'bar', 42: 'carrots'}
>>> spam.cat = "dog"
>>> spam
{'foo': 'bar', 42: 'carrots', 'cat': 'dog'}
>>> spam.items()
dict_items([('foo', 'bar'), (42, 'carrots'), ('cat', 'dog')])
>>> for key in spam:
...   print(key)
...
foo
42
cat



RE: attrdict.py - Skaperen - Jan-10-2019

because i didn't know about that? now that i know what, i can go read more about how. but what you are suggesting makes sense as the simpler way to do it.


RE: attrdict.py - Skaperen - Jan-10-2019

i added an __init__() method which handles zero or more arguments of dict/attrdict or list/tuple of zero or more key:value 2-sequence pairs.

class attrdict(dict):
    '''Class that is like a dictionary with items usable like attributes.

#---------------------------------------------------------------
# purpose       class that is a dictionary with items usable
#               like attributes
#
# init usage    object = attrdict(dictionary)
#               object = attrdict(dictionary,key=value...)
#               object = attrdict(key=value...)
#
# attr usage    object.name
#
# dict usage    object[key]
#
# note          attribute usage is like string keys that are
#               limited to what can be a valid identifier.
#
# thanks        [email protected]
#---------------------------------------------------------------
'''
    def __init__(self,*args,**opts):
        arn = 0
        for arg in args:
            arn += 1
            if isinstance(arg,(attrdict,dict)):
                self.update(arg)
            elif arg and isinstance(arg,(list,tuple)):
                an = -1
                for ar in arg:
                    an += 1
                    if isinstance(ar,(list,tuple)) and len(ar)==2:
                        self[ar[0]] = ar[1]
                    else:
                        raise TypeError('not a 2-sequence at ['+str(an)+'] of argument '+str(arn))
            else:
                raise TypeError('argument '+str(arn)+' is not a sequence')
        if opts:
            if isinstance(opts,(attrdict,dict)):
                self.update(opts)
            else:
                raise TypeError('options ('+repr(opts)+') is not a dictionary')
    def __getattr__(self, key):
        return self[key]
    def __setattr__(self, key, value):
        self[key] = value



RE: attrdict.py - Gribouillis - Jan-10-2019

The rule of least surprise suggests that the __init__ method has the same behaviour as dict's __init__ method, why not simply inherit this method?


RE: attrdict.py - Skaperen - Jan-11-2019

i need to create attrdict objects from existing dictionaries because most of my existing code that uses attrdict does it that way. in some cases i don't know that i can easily avoid that. does dict.__init__() do that?


RE: attrdict.py - nilamo - Jan-11-2019

>>> x = {"a": 1}
>>> b = dict(x)
>>> b
{'a': 1}
>>> #or using keyword expansion:

>>> c = dict(**x)
>>> c
{'a': 1}
>>> #make sure they aren't just shallow copies

>>> x["b"] = 2
>>> x
{'a': 1, 'b': 2}
>>> b
{'a': 1}
>>> c
{'a': 1}



RE: attrdict.py - Skaperen - Jan-12-2019

would .update() do that? (make a deep copy)