Introspection with classes

Kirk McDonald mooquack at suad.org
Tue Jan 31 08:00:59 EST 2006


Me again! Same project as before, if you're playing along at home.

The Everything Engine model (which I am blatantly copying in my project) 
is that everything in the engine is a 'node,' and that every node can be 
stored to and read from the database. Since this is the backend for a 
website, minimizing hits on the database is a worthy goal. Thus, it is 
paramount that reading in any given subclass of the base 'Node' class 
take (optimally) one query. (A simple, yet fairly simple-minded design 
-- and my first stab at the problem -- would be to have each subclass 
read in its own data after calling its parent's -- or parents'! -- read 
method(s). This has the disadvantage of cluttering a lot of code with 
database queries and making it quite difficult to create new subclasses.)

Here's a rough sketch of what I'm doing at the moment. I've invested 
enough time into it at this point that I've totally lost track of 
whether it's a good idea, and I would please like someone to tell me.

Every node has certain attributes. In this outline, let's say these are 
the node's unique id number, its title, and the id number of its owner.

class Node:
     dbInfo = { 'title'    : ['node', 'title',    ""]
                'owner_id' : ['node', 'owner_id', None] }

     def __init__(self):
         self.default()

     def default(self):
         self.node_id = None
         for k, v in self.dbInfo.items():
             setattr(self, k, v[2])

     def read(self, db, node_id):
         # construct a database query from the table/column info in
         # dbInfo

     def commit(self, db):
         # write the current values to the db

     def insert(self, db):
         # insert a new node using the current values

     def nuke(self, db):
         # remove the node from the db, then:
         self.default()

So, simple enough. (The real version has signifigantly more stuff going 
on, as well as a permission system, but I have elided these for the sake 
of clarity.) By doing that dance with dbInfo, I can easily subclass Node.

Say we want a new node that holds some text. We just need to add a new 
attribute like so:

class Document(Node):
     dbInfo = {}
     dbInfo.update(Node.dbInfo)
     dbInfo['content'] = ['docs', 'text', ""]

... and that's it. That's the whole Document class. (Well, the real 
version has a render method that takes the content, parses it in 
interesting ways, and writes it to the client, but that's simple, too.) 
The shortness of this class is why this strikes me as a good idea.

The other advantage is I can make use of multiple inheritance. First, 
say we have a class that just holds a dictionary:

class DictNode(Node):
     dbInfo = {}
     dbInfo.update(Node.dbInfo)
     dbInfo['stuff'] = ['dictnode', 'dict', {}]

(Assume a mechanism is provided to automatically pickle the dictionary, 
which there is.)

Now say I want a nodetype that's just a document with a dictionary 
attached. This is almost exactly how the User class is implemented: the 
document holds the user's "homenode," which is just a brief bio or 
whatever the user wants to put in there, and the dictionary holds the 
user's settings. They snap together like Legos. It looks just like this:

class User(Document, DictNode):
     dbInfo = {}
     dbInfo.update(Document.dbInfo)
     dbInfo.update(DictNode.dbInfo)

If the User class wanted its own attributes (say, a password), they 
would just get added to the end.

This is my third stab at the problem (the first was mentioned above, the 
second was basically the same as this but dbInfo was an instance 
variable instead of a class variable which, uh, was sort of dumb).

Coming from C++, I'm still wrapping my brain around Python's 
introspection abilties. Can I generalize this more? Am I missing some 
idiom or language feature that would benefit me? Where's my time machine?

-Kirk McDonald



More information about the Python-list mailing list