When we create a Friend object, all we need to do is store the name, phone number
and email address of a friend, so the __init__
method will be very simple - we
just need to create three attributes which we'll call name, phone and
email. This object will serve as a building block for our database, so we don't
need to define many other methods. As an example, we'll create a __str__
method,
and an identical __repr__
method so that we can display information about our
friends in a readable format.
>>> class Friend: ... def __init__(self,name,phone='',email=''): ... self.name = name ... self.phone = phone ... self.email = email ... def __str__(self): ... return 'Name: %s\nPhone: %s\nEmail: %s' % \ (self.name,self.phone,self.email) ... def __repr__(self): ... return self.__str__() ... >>> x = Friend('Joe Smith','555-1234','joe.smith@notmail.com') >>> y = Friend('Harry Jones','515-2995','harry@who.net') >>> print x Name: Joe Smith Phone: 555-1234 Email: joe.smith@notmail.com >>> y Name: Harry Jones Phone: 515-2995 Email: harry@who.netJust as with functions, we can provide default values for arguments passed to methods. In this case, name is the only required argument; the other two arguments default to empty strings. Notice that attributes are refered to as self.name; without the leading self, variables will be treated as local, and will not be stored with the object. Finally, notice that the print operator as well as the interactive session's default display have been overridden with the
__str__
and
__repr__
methods, respectively.
Now, we can create a database containing information about our friends, through the Frienddb class. To make it useful, we'll introduce persistence through the cPickle module introduced in Section 8.10.1. When we first create the database, we'll require the name of a file. If the file exists, we'll assume it's a database we've already created and work with that; if the file doesn't exist, we'll open it and prepare it to receive our pickled database. For this simple application, we can simply create a list of Friend objects, and store the list as an attribute in our Frienddb object.
When designing an object oriented application like this, we simply need to identify the
operations we'll want to do on the object we've created. In addition to the __init__
method,
some of the tasks we'd want to implement would be adding a new
friend to the database, searching the database for information about a particular person,
and storing the database when we're done with it. (In the following sections, it's assumed that the class definition for
Friend is either in the same file as the Frienddb definition or has been
properly imported along with the other appropriate modules (os,cPickle,
sys,re and string). In addition, the following class
statement will have appeared before the definition of these methods:
class Frienddb:
The __init__
method for our
database will take care of all the dealings with the file in which the database will be
stored.
import sys class Frienddb: def __init__(self,file=None): if file == None: print 'Must provide a filename' return self.file = file if os.path.isfile(file): try: f = open(file,'r') except IOError: sys.stderr.write('Problem opening file %s\n' % file) return try: self.db = cPickle.load(f) return except cPickle.UnpicklingError: print >>sys.stderr, '%s is not a pickled database.' % file return f.close() else: self.db = []Since the
__init__
statement acts as a constructor for class objects,
it should never return a value. Thus, when errors are encountered, a return
statement with no value must be used. This method puts two attributes into our
Frienddb object: file, the name of the file to store the database, and
db, the list containing Friend objects (if the database already exists)
or an empty list if there is not a file with the name passed to the method.
The method for adding an entry to the database is simple:
def add(self,name,phone='',email=''): self.db.append(Friend(name,phone,email))
Searching the database is a little more complex. We'll use the re module (Section 8.5) to allow regular expression searches, and return a list of Friend objects whose name attributes match the pattern which is provided. Since we'll be searching each name in the database, the regular expression is compiled before being used.
def search(self,pattern): srch = re.compile(pattern,re.I) found = [] for item in self.db: if srch.search(item.name): found.append(item) return foundFinally, we'll write methods to store the contents of the database into the file that was specified when the database was created. This is just a straightforward use of the cPickle module (Section 8.10.1). To insure that data is not lost, a
__del__
method which simply calls the store method (provided
that there were entries in the database) will be
added as well:
def store(self): try: f = open(self.file,'w') except IOError: print >>sys.stderr, \ 'Problem opening file %s for write\n' % self.file return p = cPickle.Pickler(f,1) p.dump(self.db) f.close() def __del__(self): if self.db: self.store()
Having created these two classes, we can now write a simple program to use them. You might want to include such a program in the file containing the class definitions; if so, see Section 10.3. We'll write a small interactive program that will recognize three commands: ``a'', which will cause the program to prompt for a name, phone number and email address and the add it to the database, ``? pattern'', which will print all the records whose name attribute matches pattern, and q, which will store the database and exit. In the program that follows, I'm assuming that the class definitions for Friend and Frienddb are stored in a file called frienddb.py which is in a directory on the python search path.
import frienddb file = raw_input('File? ') fdb = Frienddb(file) while 1: line = raw_input('> ') if line[0] == 'a': name = raw_input('Name? ') phone = raw_input('Phone number? ') email = raw_input('Email? ') fdb.add(name,phone,email) elif line[0] == '?': line = string.strip(line[1:]) fdb.search(line) elif line[0] == 'q': fdb.store() break
Other methods could be implemented as well. While not necessarily that useful, suppose
we wanted to access elements of our database either by a numeric index, indicating their
position in the database, or by the name attribute of the individual records.
Using the __getitem__
method, we could subscript the database just as we do
lists or dictionaries.
To properly process both string and numeric indices, we'd need to distinguish between
these two types of values.
One way to do this is by trying an numeric conversion of the index with the
int
function; if an exception is thrown, then we must have a string value.
def __getitem__(self,key): try: index = int(key) return self.db[index] except ValueError: found = 0 for item in self.db: if item.name == key: return item raise KeyError(key)If a string is given, and no record with that name is found, the method simply raises the builtin KeyError exception, allowing the caller of the method to decide how to handle this case. Now we can access the database through subscripts:
>>> import frienddb >>> fdb = frienddb.Frienddb('friend.db') >>> fdb[0] Name: Harry Smith Phone: 443-2199 Email: hsmith@notmail.com >>> fdb[1] Name: Sue Jones Phone: 332-1991 Email: sue@sue.net >>> fdb['Harry Smith'] Name: Harry Smith Phone: 443-2199 Email: hsmith@notmail.com