Python Forum
Thread Rating:
  • 2 Vote(s) - 3.5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
attrdict.py
#1
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
Tradition is peer pressure from dead people

What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
Reply
#2
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.
Reply
#3
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
Tradition is peer pressure from dead people

What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
Reply
#4
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
Reply
#5
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.
Tradition is peer pressure from dead people

What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
Reply
#6
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
Tradition is peer pressure from dead people

What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
Reply
#7
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?
Reply
#8
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?
Tradition is peer pressure from dead people

What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
Reply
#9
>>> 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}
Reply
#10
would .update() do that? (make a deep copy)
Tradition is peer pressure from dead people

What do you call someone who speaks three languages? Trilingual. Two languages? Bilingual. One language? American.
Reply


Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020