If you are thinking "What's this?", see Whose Sounds in the Cellar.
If you are wondering "How did I get here?", use the back button of your browser.
If you are asking "What Sounds?", read on.

Python: constrained containers

As it happens, I was playing with the del.icio.us API today because of reasons which I shall hopefully elaborate upon in the coming few days. While cooking up some code to interact with the API, I saw the need to model a basic del.icio.us post entity. For my needs, this entity wouldn't have to be much more than a dumb dictionary with keys like url, description, etc. So I started out that way but pretty soon I thought, wouldn't it be nice to be able to write post.url instead of post['url']? Further, wouldn't it be nice to constrain the keys in the dictionary to those required and prevent typo errors such as post['descrption'] instead of post['description']?

This seemed like the perfect opportunity to put into practice some of the stuff I'd learned about "new" style classes not too long ago. So I wrote the following class which implements the requirements laid out in the preceding paragraph. Since I'm just getting the hang of this stuff, please critique the code!

class Post(object):
    """Class to model a del.icio.us Post. It works like a struct/dict 
    like object which limits keys to a retricted subset, i.e. those
    used in a del.icio.us post."""

    __slots__ = ['description', 'url', 'extended', 'tags']

    def __getitem__(self, key):
        if key in self.__slots__:
            return self.__getattribute__(key)
        else:
            raise KeyError

    def __setitem__(self, key, value):
        if key in self.__slots__:
            self.__setattr__(key, value)
        else:
            raise KeyError('Given key is not allowed in class %s'\
                                % self.__class__.__name__)

    def __contains__(self, key):
        try:
            self.__getitem__(key)
        except:
            return False
        return True
    
    # urllib.urlencode() just needs this beyond the basic stuff above
    def items(self):
        return [(k, self[k]) for k in self.__slots__ if k in self]

And here's the usage of the class.

>>> p = Post()
>>> p.url
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: url
>>> p.url = 'http://google.com'
>>> p.url
'http://google.com'
>>> p['url']
'http://google.com'
>>> p['desc']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "yummy.py", line 17, in __getitem__
    raise KeyError
KeyError
>>> p['description'] = 'Google homepage'
>>> p['description']
'Google homepage'
>>> p.items()
[('description', 'Google homepage'), ('url', 'http://google.com')]
>>> p.tags = 'search'
>>> p.items()
[('description', 'Google homepage'), ('url', 'http://google.com'), ('tags', 'search')]
>>> 'extended' in p
False
>>> p.extended = 'homepage of the world'
>>> p.items()
[('description', 'Google homepage'), ('url', 'http://google.com'), ('extended', 'homepage of the world'), ('tags', 'search')]
>>>

So, what am I doing wrong? :-)

techtalk | 6 comments | permalink | 04.07.2008 18:04 SGT


Re: Python: constrained containers
HS wrote on Fri, 04 Jul 2008 18:47

No critiques. Just a couple of questions:

Why do you want to restrict the possible key values? It seems somewhat 'unpythonic' to do so.

Also, how about deriving your class from Dictionary? Wouldn't that also work?


Reply to this comment
    Re: Python: constrained containers
    deepak wrote on Fri, 04 Jul 2008 19:14

    Even after deriving from `dict`, I'd still have to do some attr magic to get attribute style access on keys (x.key vs x[key]). So I don't see how much I'll gain from inheriting from dict.

    As for restricting, don't think of it as restricting keys but restricting attributes. And I already mentioned preventing typos being a reason to want to do that.


    Reply to this comment
Re: Python: constrained containers
Harish Mallipeddi wrote on Sat, 05 Jul 2008 01:35

Basically what you're doing is to implement the dictionary protocol. Alternatively if you want to disallow x[key], you can just use a dictionary internally to store the attributes and only implement the __getattr__() method on Post. This method will look for the desired attribute name in the internal dict and raise an AttributeError if the dictionary doesn't have it. I do something similar in my django-pipes module.

http://github.com/mallipeddi/django-pipes/tree/master/main.py

Although I agree with you on the dotted syntax being nicer than the dictionary-style access, I don't see how this is going to prevent typos. Python is a dynamic language. If you typed p['descrption'] or p.descrption, you won't catch the error until runtime. In the first case, you would get a KeyError, and in the second case you would get an AttributeError. How did it help you to prevent the typo?


Reply to this comment
    Re: Re: Python: constrained containers
    deepak wrote on Sat, 05 Jul 2008 01:49

    Actually, for this particular use-case, I think I don't even need to implement the full dictionary protocol since I don't really need to access stuff as x[key]; x.key would be enough.

    As for key restriction, let's say I typed p['descrption'] and then shunted off p to urllib.urlencode & merrily POSTed the params to delicious. The error wouldn't be apparent till after that network call. Why get to that stage?

    Of course, I am stretching this a bit since even a minimum amount of test code/runs would detect such errors. On the other hand, the fact that the set of acceptable keys is defined kind of becomes a self-documenting thing no?

    But chiefly, I just wanted to see how to implement this :-)


    Reply to this comment
Re: Python: constrained containers
Harish Mallipeddi wrote on Sat, 05 Jul 2008 01:39

Also you probably don't want to use __getattribute__() and __setattr__() in your code. You can instead use getattr(self, x) and setattr(self, x, val). The former are typically meant for you to implement in your classes. The latter provide API calls for generic attribute access on an object.


Reply to this comment

 
Name:
URL:
Title:
Comment:
Please type spam captcha image in this box