Dashboard > HOW-TOS > Everything Is a Dictionary > Information > Page Comparison
HOW-TOS Log In   View a printable version of the current page.
Everything Is a Dictionary
Version 1 by K Lars Lohn
on Mar 02, 2005 11:46.


 
compared with
Current by K Lars Lohn
on Mar 18, 2005 14:48.

(show comment)
 
Key
These lines were removed. This word was removed.
These lines were added. This word was added.

View page history


There are 0 changes. View first change.

  
  
 In Python, it is said that everything is a dictionary. From namespaces to classes to instances of classes, the components are implemented as collections of associations between keys and values. You can look at these dictionaries by viewing the __dict__ component. You can also add new key value pairs at will.
  
 To a programmer versed in languages like C++, this can lead to some startling situations. For example, two instances of a class may or may not have the same list of attributes. Python lets programmers define class attributes on the fly.
  
 Let's say we have a class called MyObject and a couple instances of it:
 {code}
 myobj1 = MyObject()
 myobj2 = MyObject()
 {code}
 I can add attributes to these objects:
 {code}
 myobj1.fred = 1
 myobj2.sally = 14
 {code}
 myobj1 doesn't have a 'sally' component like myobj2 does. Vice versa for the 'fred' component.
  
 Why would you want to do this? As a C++ programmer specializing in databases, one of the most common problems that I saw in production systems was the C++ code being too fragile. For many reasons, database schema evolve. Attributes are added or removed as time passes. While it is possible to write robust C++ that can deal with these changes, it takes significant planning and foresight.
  
 Consider designing an employee object in C++. For a first cut, one generally thinks of using a class called Employee as perhaps a subclass of some Person class:
 {code}
 class Employee : public Person {
  public:
  Employee (String name, PostalAddress address, String department, Decimal salary);
  ~Employee();
  String department_;
  Decimal salary_;
 };
 {code}
 This is a very logical way to begin, but already we're in trouble. What happens if a couple months down the road, the database guys add a new column to the Employee table, for example, IdNumber? The Employee class is rather brittle and cannot express this new attribute without recoding. How do we solve this?
  
 Mixins? While this will work, we make a coding mess out of the Employee class to do it. Some of your programmers will understand it, but many of your coders will see black magic. You'd better have a retention plan for your best coders. A couple more years down the road, your new programming team is going to be chomping at the bit to dump the code and rewrite it.
  
 Another solution would be to drop the Employee class entirely and replace it with a collection. Rogue Wave's database library had a class called RWDBRow that was a collection of named value objects. It was robust in that it could grow to accommodate new values coming in from the database, but it wasn't a very programmer friendly class. To use it, you'd end up throwing inheritance out the window: no more clear hierarchical relationship between Person and Employee. You could roll your own class that would be better, but you'd still be hard pressed to reproduce the simplicity of the original Employee class.
  
 Python, because of its "everything is a dictionary" design, can offer a best of both worlds compromise. Oh, it's not perfect, but it can lead to some clearer and flexible code . Imagine taking a database query and creating an object where the attributes have the names and values from the query results.
  
 Consider this simple little do-nothing class.
 {code}
 class RowObject (object):
  def __init__(self):
  pass
 {code}
  
 Along with this generator function:
 {code}
 def objectsFromQuery (aDBAPI2Cursor, anSQLStatement, aRowObjectClass):
  aDBAPI2Cursor.execute(anSQLStatement)
  while True:
  aRow = aDBAPI2Cursor.fetchone()
  if aRow is None: break
  targetRowObject = aRowObjectClass()
  for aColumnDescription, columnValue in zip(aDBAPI2Cursor.description, aRow):
  targetRowObject.__dict__[aColumnDescription[0]] = columnValue
  yield targetRowObject
 {code}
  
 The generator function takes a cursor, an SQL string and a reference to a class that will represent row objects. It returns a sequence of instances of the row class with attributes and values set by the SQL return results. Any class can be passed to this generator. A good candidate would be an Employee class complete with methods appropriate for employees:
 {code}
 class Employee (Person):
  def __init__ (self, args):
  # initialization code
  
  def giveRaise (self, amount):
  self.salary += amount
  
  def fire (self):
  # add code...
 {code}
  
  
 This means I can write code like this:
 {code}
 for anEmployee in objectsFromQuery(aCursor, "select * from Employee ", Employee):
  if anEmployee.salary > 50000:
  anEmployee.fire()
  else:
  anEmployee.giveRaise(1)
 {code}
  
 Now let's say that we have a production system that runs twenty-four hours a day for several years. The database schema evolves. Some of the more recently instantiated objects will have attributes that older objects do not have. Yet these object are instances of the same class. With judicious use of exception handling, these objects can co-exist without too much trauma.
  
 Perhaps in the future, I'll talk about changing method definitions at run time - or even, heaven forbid, changing inheritance hierarchies at run time...
Site powered by a free Open Source Project / Non-profit License (more) of Confluence - the Enterprise wiki.
Learn more or evaluate Confluence for your organisation.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.2.7 Build:#524 Jul 28, 2006) - Bug/feature request - Contact Administrators