Hanno’s Coding Log: software development, geek stuff

Adding metadata to built-in types in Python

In this article I will show how you add metadata to any object and optionally change only part of its behavior (in this example the string representation __str__), while maintaining all other data and behavior of the object itself.
I will show how to write a class that dynamically decides which base class to use, based on the parameters that are passed to the constructor. I call that “dynamic inheritance”.

This way you can build universal decorators. (Decorators that work on more than one type.)

My motivation: For some coursework I had to write a quick and dirty implementation of a hash table and do some experiments with it. One of the tasks was printing the contents and mark the entries where a collision occurred.
But I also wanted the hash table to behave like a list of elements of the same type as my original data.

So if I wanted to print out the hash table it should be as easy as:

def printtable(tbl):
    for i in xrange(0,len(tbl)):
        print "%2i " % i, tbl[i]

So what I wanted was a container class that would behave exactly like the type you put in it, but prints additional metadata in its string representation.

In C++ this might be possible with Templates. (I have not tried whether you can put the
template type into the base classes list…)

That is, where the __new__ method from Python came in handy. I wrote something like this:

class htentry(object):
    def __new__(cls, obj, k):
        if obj != None: 
            #NOTE: this class exists only in the namespace of __new__!
            class dynentry(obj.__class__):
                k = False
                #now for overloading the __str__ function...
                def __str__(self):
                    if self.k == True:
                        #super(class, obj) casts obj to a base class of class
                        #(basically. It does something more complicated if class is already
                        #a base class of obj's type)
                        return super(dynentry, self).__str__() + 'k'
                        return super(dynentry, self).__str__()
            newval = dynentry(obj)
            newval.k = k
            return newval
            return None

Here k is the metadata (k for Kollision - which is german for collision).

__new__ is called, before the actual constructor is called and should return an instance of an object. (See -> Python documentation)

So what I do is dynamically creating a class that inherits the type of the object that I want to add metadata to, create an instance of it and set the metadata. This way, the returned object retains all data and behavior of the original object except for the things I have added.

Usage example:

    def __getitem__(self, idx):
        return htentry(self.ht[idx], self.collisionlist.get(idx, False))

This will return whatever data is stored on that position in the hash table with the additional effect that that data will have a ‘k’ appended to whatever print output it will generate if a collision took place during adding that data to the table.

  • Digg
  • del.icio.us
  • MisterWong
  • Technorati
  • StumbleUpon
  • Yigg

Related posts

You can follow any responses to this entry through the RSS 2.0 feed.

Stoppt die Vorratsdatenspeicherung! Jetzt klicken & handeln!Willst du auch bei der Aktion teilnehmen? Hier findest du alle relevanten Infos und Materialien: