self.job = job
self.pay = pay # real instance data
def getattr(self, attr): # on person.attr
if attr == 'tax':
return self.pay * 0.30 # computed on access
else:
raise AttributeError() # other unknown names
def info(self):
return self.name, self.job, self.pay, self.tax
This revision has a new tax rate (30 percent), introduces a getattr qualification
overload method, and deletes the original tax method. Because this new version of the
class is re-imported when its existing instances are loaded from the shelve file, they
acquire the new behavior automatically—their tax attribute references are now inter-
cepted and computed when accessed:
C:\...\PP4E\Dbase> python
>>> import shelve
>>> dbase = shelve.open('cast') # reopen shelve
>>>
>>> print(list(dbase.keys())) # both objects are here
['bob', 'emily']
>>> print(dbase['emily'])
<person.Person object at 0x019AEE90>
>>>
>>> print(dbase['bob'].tax) # no need to call tax()
21000.0
Because the class has changed, tax is now simply qualified, not called. In addition,
because the tax rate was changed in the class, Bob pays more this time around. Of
course, this example is artificial, but when used well, this separation of classes and
persistent instances can eliminate many traditional database update programs. In most
cases, you can simply change the class, not each stored instance, for new behavior.
Shelve Constraints
Although shelves are generally straightforward to use, there are a few rough edges worth
knowing about.
Keys must be strings (and str)
First, although they can store arbitrary objects, keys must still be strings. The following
fails, unless you convert the integer 42 to the string 42 manually first:
dbase[42] = value # fails, but str(42) will work
This is different from in-memory dictionaries, which allow any immutable object to be
used as a key, and derives from the shelve’s use of DBM files internally. As we’ve seen,
Shelve Files | 1321