Chapter 6. Object-Oriented Programming

CONTENTS
  •  What Is OOP?
  •  Objects and Classes
  •  Special Class Methods
  •  Inheritance
  •  Polymorphism
  •  Summary

Terms in This Chapter

  • Aggregation/containment

  • Attribute

  • Cardinality

  • Class

  • Class hierarchy

  • Code reuse

  • Cohesion

  • Coupling

  • Encapsulation

  • First class object

  • Garbage collection

  • Getter/Setter methods

  • Inheritance (implementation/ interface)

  • Instance

  • Instantiation

  • Late-bound polymorphism

  • Multiple inheritance

  • Object

  • Replaceability

  • Specialization

  • Typed polymorphism

This chapter explains the fundamentals of object-oriented programming, as well as why it's important and how to do it in Python.

What Is OOP?

Object-oriented programming (OOP) is a tool for organizing your programs into reusable objects. It won't necessarily make you program better or faster, yet it's one of the best ideas in programming to come along so far. A common myth about object-oriented programming is that it's hard to learn and that you should learn a non-OOP language first, but you don't have to do this. In fact, OOP is easy to learn because it models the real world. Bottom line? Don't fear object-oriented programming.

Here are the triple pillars of OOP:

  • Polymorphism

  • Encapsulation

  • Inheritance

Of the three, polymorphism is the most important because it supports the replaceable nature of objects.

Objects as Black Boxes (Encapsulation)

Say you're driving along and a young child racing toward his fleeing ball runs right in front of your car. Do you pause and think about what to do? No. Instinctively, your foot reaches for the break pedal.

Think of the break pedal as the interface to your car's breaking system. You know how to use it because it's well defined, whether this is your car or one you've rented or borrowed, whether the breaks are drum or antilock. The details of the system's implementation are encapsulated that is, hidden from you. All you need to know is how to use the interface to the break system the break pedal.

What does all this have to do with OOP? A great deal. With OOP you define classes that support interfaces, and you use them to instantiate objects. Two classes with the same interface can be used interchangeably. You can organize your program into many objects, each of which has a specific role (this is called cohesion). The objects perform their roles in ways that are encapsulated from the rest of the program, which means that you change the way an object works and keep the rest of your program intact.

Here are some important object-oriented ideas:

  • Coupling is when you change one module or class, and doing so adversely affects another module or class. (Some coupling may be necessary.)

  • Cohesion is defining modules and classes that have specific roles. (Cohesion helps to reduce coupling.)

  • Encapsulation is hiding the implementation details. In OOP, we hide them behind an interface.

  • Interface, in OOP speak, is a collection of methods associated with a class or module.

Objects and Classes

Objects in the real world are made up of many other objects (in OOP speak this is called aggregation or containment). For example, a car object contains tire objects, an engine object, a steering system object, a breaking system object, and so forth. Organizing a program into a hierarchy of objects helps us conceptualize it. Essentially, you break down the program into interrelated objects, which are in turn broken down into other objects. This is called inheritance.

A class is a template for creating objects by defining their behavior and attributes. Thus, a car class defines the behaviors of a car, such as

  • Moving forward or backward

  • Breaking

  • Turning

It also defines the car's attributes, such as

  • Make

  • Model

  • Color

  • Year

  • Two door or four door

The car class acts as a generic template for cars. Just as there are many cars in the world, so there can be many car objects in your program (this concept is known as object cardinality), but they share attributes and behavior. So, for example, you can stop and start both a Volvo and a Chevy. Think of the class as a cookie cutter and its objects as the cookies it stamps out. The cookies themselves are the instances of the class.

The great thing about programming in Python (and Java for that matter) is that you don't have to start from scratch. There are lots of general-purpose classes and modules for doing common things. For example, Python has a URL module that knows how to work with files from FTP and HTTP servers.

Let's review the key concepts so far:

  • A class (Car) is a template for defining objects (Volvo).

  • Attributes describe an object's appearance, state, or quality.

  • Behavior defines how an object acts.

  • Cardinality is the number of instances of an object in a system.

Define a Car class in Python as.

       jython  i car.py): class Car:        make = "?"        model = "?"        color = "?"        running = 0 #1 is true, 0 is false

Attributes

Here we see that the attributes of Car are defined by its variables, which are indented. We can use this class template to create car instances.

              # create an instance of my car        # and display its attributes myCar = Car() print "After Create:", myCar.color, myCar.make, myCar.model        # Set myCars attributes then display attributes myCar.color, myCar.make, myCar.model = "Red", "Ford", "Taurus" print "After set:", myCar.color, myCar.make, myCar.model

The output is

After Create: ? ? ? After set: Red Ford Taurus

Behavior

An object's behavior is determined by methods defined by its class. Let's return to the Car class example and add start and stop behavior.

class Car:        make = "?"        model = "?"        color = "?"        running = 0 #1 is true, 0 is false        def start(self):               if self.running == 1:                      print "The car is already running"               else:                      self.running = 1        def stop(self):               if self.running ==0:                      print "The car is already stopped"               else:                      self.running = 0

Here are some examples of the Car class methods followed by their output:

car = Car() car.color = "Red" car.make = "Ford" car.model ="Taurus"              # Start the car car.start() print "After Start:", car.color, car.make, car.model, car.running              #Stop the car car.stop() print "After Stop:", car.color, car.make, car.model, car.running        After Start: Red Ford Taurus 1        After Stop: Red Ford Taurus 0

The Python Objects Model

Unlike in Java, everything in Python is an object functions, modules, classes, packages, numeric types everything. Objects have an identity, a type, and a value. The type determines the methods and operations that an object supports. The value can be changeable (dictionaries, classes, lists) or immutable (strings, integers, tuples).

You know, for example, that you can create many instances of the same class. But did you know that a class is itself an object, a first class object, that can be dynamically modified? When you modify the template (class), you in effect modify the class instances not yet created.

To illustrate, we'll modify the attributes of the Car class for Fords.

>>> Car.make, Car.model, Car.color = "Ford", "Taurus", "Red"

Then we'll create a list of three Ford cars.

>>> ford_cars = [Car(), Car(), Car()]

Next we'll print out their make.

>>> ford_cars[0].make, ford_cars[1].make, ford_cars[2].make ('Ford', 'Ford', 'Ford')

Now we use the same technique to create three Hondas.

>>> Car.make, Car.model = "Honda", "Civic" >>> honda_cars = [Car(), Car(), Car()] >>> honda_cars[0].make, honda_cars[1].make, honda_cars[2].make ('Honda', 'Honda', 'Honda')

As you can see, when you can change the template, you essentially create objects with new attributes.

Special Class Methods

In Python you can define special methods for classes that have special meaning. You can also define methods to make your class look like other objects such as lists, dictionaries, and tuples.

Creating and Destroying Class Instances

Like other OOP languages, Python has a destructor and a constructor. A constructor is called when an instance is instantiated. A destructor is called when an instance is garbage-collected. The method __init__ denotes the constructor. The method __del__ denotes the destructor.

Here's the Car class (Car2.py) with its constructor and destructor:

class Car:        make = "?"        model = "?"        color = "?"        running = 0 #1 is true, 0 is false        def __init__(self, make, model, color):               self.make = make               self.model = model               self.color = color               print "constructor called"        def __del__(self):               print "destructor called"        ...

The __init__ method takes the form

__init__ (self[, args...]):

The __del__ method takes the form

__del__ (self):

First we create an instance of the Car class. Notice that constructor called is printed out, because the __init__ method is called when we instantiate Car.

>>> myCar = Car("Ford", "Taurus", "Red") constructor called

Next we print out the attributes that were set when the constructor was called.

>>> myCar.make, myCar.model, myCar.color ('Ford', 'Taurus', 'Red')

Then we try to destroy this instance to see if the destructor is called.

>>> myCar = None >>> myCar is None 1 >>> None >>> None destructor

The destructor isn't called when we assign myCar to None. That's because Car isn't destroyed until it's garbage-collected, which may never happen. For example, the garbage collector may be implemented to run only if there's no more memory left.

Representing the Class Instance as a String

There are two methods for creating string representations of an object: __repr__ and __string__.

The __repr__ method represents your object in such a way that it can be recreated with the eval statement. eval evaluates a string as a Python expression and works much like the interactive interpreter. __repr__ is called when you use back quotes or call the repr command. Here's what it looks like:

__repr__ (self):

The __str__ method is a way to do a nice looking string for printing. It's called by the str() built-in function and by the print statement. Here's what it looks like:

__str__ (self):

We can add __str__ to our Car class like this:

class Car:        ...        ...        def __str__(self):               strRun = ""               if self.running == 1:                      strRun="\nThe car is running"               else:                      strRun="\nThe car is stopped"               str = "make " + make + \                      "\nmodel " + model + \                      "\ncolor " + color + strRun               return str

Here's the output (from the Car2.py module) in interactive mode:

C:\py\book\chap9>jython -i Car2.py >>> car = Car("Ford", "Mustang", "Cherry Red") constructor called >>> print car make Ford model Mustang color Cherry Red The car is stopped

__repr__ can also be used for display. However, if at all possible, it should evaluate to an expression that returns the object. Thus, if you use the Python eval statement, which reads in text and parses it to an expression, you should be able to recreate the instance with the string that __repr__ returns.

Here's an example of __repr__:

def __repr__(self):        format = """Car("%s","%s","%s")"""        return format % (self.make, self.model, self.color)

Here's the output (from the Car2.py module) in interactive mode:

C:\py\book\chap9>jython -i Car2.py >>> car = Car ("Ford", "Mustang", "Midnight Blue") constructor >>> print `car` Car("Ford","Mustang","Midnight Blue") >>> print car make Ford model Mustang color Midnight Blue The car is stopped >>> car2 = eval(`car`) constructor >>> print car2 make Ford model Mustang color Midnight Blue The car is stopped

In the above example, we used the return from car.__repr__() in conjunction with the eval statement to create car2. The car2 instance has the same attributes as the original car instance.

Comparing, Hashing, and Truth Testing

Three other methods, shown in Table 6-1, have special meaning in Python. We'll use module Car3.py to demonstrate them.

max_Price = 7000 running = 0 def __nonzero__(self):        if (self.price > Car.max_Price):              return 0        else:              return 1 def __cmp__(self, other):        if (self.price > other.price): return 1        if (self.price < other.price): return -1        if (self.price == other.price): return 0 def __hash__(self):        v= self.make + self.model + self.color + `self.price`        return hash(v) def __init__(self, make, model, color, price=5000):        self.make = make        self.model = model        self.color = color        self.price = price        ...        ...
Table 6-1. Python Class Methods
Method Description
__cmp__ (self, other) Called by the comparison operators, ==,=>, >, <, =<
__hash__ (self) Used in conjunction with the hash function to return a 32-bit integer used for hash values in dictionaries
__nonzero__ (self) Used for Boolean truth testing; is the object in a true state or a false state

Here we've added a class variable called max_Price and an instance variable called price. The __nonzero__ method uses max_Price to see if the price of the instance is under the maximum price. The __cmp__ method uses the price to compare another Car instance to the current one. The hash is calculated by creating a large string representation of all the hash values of the individual attributes of the Car instance.

The following code is also part of Car3.py.

if __name__ == "__main__":        car1 = Car("Ford", "Mustang", "Midnight Blue", 5000)        car2 = Car("Chevrolet", "Corvette", "Champagne gold", 9000)               #To demonstrate compare __cmp__        if (car1 > car2):               print car1        else:               print car2               #to demonstrate Boolean __nonzero__        if(car1): print "Buy It"        if(not car2): print "I can't afford it"               #to demonstrate hash __hash__        print `hash(car1)`

The output looks like this:

C:\py\book\chap9>jython car3.py make Chevrolet model Corvette color Champagne gold The car is stopped Buy It I can't afford it -1273809553

Getting, Setting, and Deleting Attributes

We can override the default behavior for how objects are displayed, printed, constructed, compared, truth-tested, and so forth. We can also override how member variables in our instance are accessed. The methods shown in Table 6-2 customize attribute access for class instances.

Table 6-2. Instance Access Methods
Method Definition
__getattr__ (self, name) Gets the value of an attribute
__setattr__ (self, name, value) Sets the value of an attribute
__delattr__ (self, name) Deletes an attribute

Essentially, you provide getter/setter methods to control access to your member variables. Let's create a version of the Car class that controls access to the price attribute so that users can't set the price under $1,000.

class Car:        ...        ...        def __setattr__(self, name, value):              if name == "price":                    if (value > 1000):                           self.__dict__["price"] = value                    else:                           self.__dict__["price"] = 1000              else:                    self.__dict__[name] = value        ...        ...

Notice that we use the __dict__ method that contains all of the member variables of the class instance. If we had tried to use self.attribute_name, we would have thrown our program into an endless loop of recursive calls to __setattr__ (I know this from experience). The method checks to see if the name of the attribute is price. If so, we provide some special handling for setting price. Otherwise, we just set the value of all other attributes: self.__dict__[name] = value, which, by the way, is the behavior of the default implementation. A similar technique could be used for __getattr__ and __delattr__. The sample code here uses the above added logic in the __setattr__ method.

C:\py\book\chap9>jython -i car4.py >>> car = Car("Ford", "Mustang", "Bright Orange", 5000) >>> car.price = 2000 >>> car.price 2000 >>> car.price = 1001 >>> car.price 1001 >>> car.price = 900 >>> car.price 1000

Inheritance

Inheritance allows you to reuse common code and so has a profound effect on the way you implement your Python classes. It also allows you to organize your classes into a hierarchical arrangement. Classes toward the bottom of the hierarchy inherit from classes higher up (like a family tree). Thus, if you create a class, classB, that inherits behavior and attributes from another class, ClassA, then classB becomes the subclass of classA, and classA becomes the superclass of classB.

A subclass can override or create new behavior and attributes specific to its role. This is called specialization.

The Limits of Inheritance

It may look like one, but inheritance is no silver bullet. In fact, there are reasons to limit its use. Like all good things, inheritance should be used in moderation, primarily to avoid coupling between subclass and superclass.

There are two forms of inheritance: implementation and interface.

Remember that the implementation of a class is one of the things we try to hide with encapsulation. If you're going to do implementation inheritance from a weakly written base class (superclass), watch out if a base class is buggy, so are all of its subclasses. And, if you introduce a bug into the base class, you introduce it into all of the subclasses.

I'm not saying organizing your classes into a hierarchy of classes is wrong; just proceed with caution because inheritance provides code reuse at the expense of encapsulation.

A good book on object-oriented design is Design Patterns (Gamma et al., Addison-Wesley, 1995). It describes some of the pitfalls of inheritance, including tight coupling of subclasses to superclasses, and provides blueprints for good designs. The book is indispensable to OOP programmers.

Code Reuse and the Class Hierarchy

One of the major advantages of object-oriented programming is code reuse. We'll illustrate this with a real-world example of inheritance.

Cars and trucks are similar in that they both have certain physical characteristics like doors and windows and they both have a make, model, and vehicle identification number (VIN). Of course, they're also very different. One is for carrying loads; the other is for carrying passengers. Still, we can put the things common to trucks and cars in a base class, which we can then subclass into separate car and truck classes

Based on these principles we can create a base class called Automobile that consists of make, model, VIN, price, mpg, and so forth. The Truck subclass may specialize Automobile with towing capacity, bed size, and the like; the Car subclass may do so with trunk capacity and number of passengers.

The class hierarchy for cars and trucks might look like Figure 6-1.

Figure 6-1. Automobile Class Hierarchy

graphics/06fig01.gif

Now we'll derive two classes from Automobile called Car and Truck, as in the figure. Here is the base class (from Automobile1.py):

class Automobile:        max_Price = 7000        running = 0        def __setattr__(self, name, value):               ...        def __nonzero__(self):               ...        def __cmp__(self, other):               ...               ...        def __hash__(self):               ...        def __init__(self, make, model, color, price=5000):               ...        def __del__(self):               ...        def start(self):               ...        def stop(self):               ...        def __str__(self):               ...        def __repr__(self):               ...

Here are the Car and Truck subclasses (from Automobile2.py):

class Car (Automobile):        def __init__(self, make, model, color, price, trunk_space= 5):               Automobile.__init__(self, make, model, color, price)               self.trunk_space = trunk_space class Truck (Automobile):        def __init__(self, make, model, color, price, tow_capacity = 2):               Automobile.__init__(self, make, model, color, price)               self.tow_capacity = tow_capacity

The statement class Car (Automobile) tells Python to subclass a Car class from Automobile. The statement Automobile.__init__(self, make, model, color, price) tells Python to call the base class constructor. The constructor isn't doing much right now, but when we build graphical user interfaces it will do a lot.

When you create a new instance of the Truck class, you get the attributes defined in it as well as all attributes defined in its base class, Automobile. Thus, the base classes and current classes mix to assemble a template for the class instance. Then each class instance is initialized with the attribute data that describes it.

Methods work similarly. An instance can access all of the methods of its class and its superclasses (base classes). If you call a method on an instance, Python checks its class for that method definition. If it finds it, it calls the method. If not, it checks the superclass (or superclasses). Python will look all the way up the class hierarchy for the method to call, so when you define a method with the same name as that of a base class method, you effectively override that method's functionality.

Python, unlike Java, provides multiple inheritance, which means that it can inherit functionality from more than one class. Java provides single inheritance, which means that a class can inherit only from one superclass.

There are problems with multiple inheritance. For example, two base classes can have a method with the same name. Python gets rid of these problems by having left-to-right multiple inheritance. That is, the base class to the left in the definition of the class overrides the base class to the right.

Let's say that our Car class inherits from two base classes, Automobile and Vehicle. Let's also say that both of these classes have a __start__ method. If we want Car to use Vehicle's __start__ method, we have to define it like this:

def Car (Vehicle, Automobile):

If we want Car to use Automobile's __start__ method, we have to define it like this:

def Car (Automobile, Vehicle):

Polymorphism

Of the three pillars of OOP, inheritance and encapsulation are nice to have but polymorphism is the true key to OOP's success. Polymorphism gives objects the most important feature of all: replaceability.

There are two forms of polymorphism: late-bound and typed. Visual Basic uses late-bound, whereas Java uses typed. Python polymorphism is a hybrid of the two.

Late-Bound Polymorphism

In Python, late-bound polymorphism means that class instances can appear to be other objects if their class implements special methods. Thus, because a function is an object, a class can act like a function just by implementing a special method. Numeric types are also objects, so with special methods a class can act like a numeric type.

For example, to make a class act like a function we can use the function notation with the class to make a call and pass instances of the class where we expect a function object. All we need to do is add a __call__ method to a class in the form __call__(self[, args ]. The following interactive session shows how:

>>> class hello: ...          def __call__(self): ...                print "Hello World" ... >>> hi = hello()   # define an instance of hello >>> hi()           # use the hi instance as a function Hello World

Also, we can use a variable number of arguments with the __call__ method as follows:

>>> class Add: ...          def __call__(self, num1, num2): ...                return num1 + num2 ... >>> add = Add()    #define an instance of the Add class >>> add(1,2)       # use the add instance as a function 3

We can define classes that act like numeric types with the __add__, __radd__, __sub__, and __rsub__ methods. The method used depends on the class, as shown in Table 6-3. To demonstrate how we use them, let's define a simple Car class that implements __add__. Follow along in interactive mode.

>>> class Car: ...          def __init__(self, inventory): ...                self.inventory = inventory ...          def __add__(self, other):  #define an __add__ method ...                return self.inventory + other ... >>> #define a Car instance to hold the # of buicks >>> buicks_in_lot = Car(5) >>> #define a Car instance to hold the # of chevys >>> chevys_in_lot = Car(20)
Table 6-3. Methods That Define Classes to Act Like Numbers
Method Behavior
__add__ (self, other), __radd__ (self, other)
>> num1 + num2
__sub__ (self, other), __rsub__ (self, other)
>> num1 - num2
__mul__ (self, other), __rmul__ (self, other)
>> num1 * num2
__div__ (self, other), __rdiv__ (self, other)
>> num1 / num2
__mod__ (self, other), __rmod__ (self, other)
>> num1 % num2
__divmod__ (self, other), __rdivmod__ (self, other)
>> divmod(num1, num2)
__pow__ (self, other), __rpow__ (self, other)
>> num1 ** num2
__lshift__ (self, other), __rlshift__ (self, other)
>> num1 << num2
__rshift__ (self, other), __rrshift__ (self, other)
>> num1 >> num2
__and__ (self, other), __rand__ (self, other)
>> num1 & num2
__xor__ (self, other), __rxor__ (self, other)
>> num1 ^ num2
__or__ (self, other), __ror__ (self, other)
>> num1 | num2
__neg__ (self)
>> num1 = 1 >> -num1 -1
__pos__ (self) 
>> num1 = 1 >> +num1 1
__abs__ (self) 
>> num1 = -1 >> abs(num1) 1
>>> buicks_in_lot + 5            # if we add 5 more...how many buicks?       10 >>> chevys_in_lot + 5            # if we add 5 more; how many chevys?       25

Now we can add numeric types to our instances.

What about adding other instances? For example, what happens if we add chevys_in_lot + buicks_in_lot?

>>> chevys_in_lot + buicks_in_lot Traceback (innermost last):   File "<interactive input>", line 0, in ?   File "<interactive input>", line 5, in __add__ TypeError: __add__ nor __radd__ defined for these operands

Of course, this isn't what we want. To get the proper behavior we need to define a __radd__ method, which is necessary when the operand is on the right and the left operand doesn't support __add__. To add both instances together, we have to do this (follow along):

>>> class Car: ...          def __init__(self, inventory): ...                self.inventory = inventory ...          def __add__(self, other): ...                return self.inventory + other ...          def __radd__(self, other): ...                return self.inventory + other ... >>> buicks_in_lot, chevys_in_lot = Car(5), Car(20) >>> chevys_in_lot + buicks_in_lot 25

If you define the methods shown in Table 6-4, you can create a class whose instances act like a dictionary. Let's create such a class that implements all of the methods in the table and calls a real dictionary.

class MyDict:        def __init__(self):              self.dict = {}        def __len__(self):              return len(self.dict)        def __getitem__(self, key):              return self.dict[key]        def __setitem__(self, key, value):              self.dict[key]=value        def __delitem__(self, key):              del(self.dict[key])        def keys(self):              return self.dict.keys()        def values(self):              return self.dict.values()        def items(self):              return self.dict.items()        def has_key(self, key):              return self.dict.has_key(key)        def get(self, key):              return self.dict.get(key)        def clear(self):              self.dict.clear()        def copy(self):              return self.dict.copy()        def __str__(self):              return str(self.dict)        def __repr__(self):              return repr(dict)        def update(self, dict):              self.dict.update(dict) 
Table 6-4. Methods That Define Classes to Act like Dictionaries
Method Behavior
__len__(self)
>> len(dict) 3
__getitem__(self, key)
>> dict[key] 'Missy'
__setitem__(self, key, value)
>> dict[key] = value
__delitem__(self, key)
>> del dict[key]
keys(self)
>> dict.keys() ['Miguel', 'Adam', 'Rick']
values(self)
>> dict.value() ['Martha', 'Missy', 'Kiley']
items(self) 
>> dict.items() [('Rick', 'Kiley'), ('Adam', 'Missy'), ('Miguel', 'Martha')]
has_key(self, key)
>> dict.has_key(key) 1
get(self, key)
>>> dict.get('Rick') 'Kiley'
clear(self)
>>dict.clear()
copy(self)
>>cdict = dict.copy()
update(self, dict)
>>dict2.update(dict)

To test the dictionary, we write the following code:

if __name__ == "__main__":       main() def main():       dict = MyDict()       print "test __setitem__"       dict["Ham"] = "Eggs"       dict["Tom"] = "Jerry"       dict["Mike"] ="Ike"       print "setitem ", dict       print "test __getitem__ and get"       print "getiem ", dict["Ham"], dict["Tom"], dict["Mike"]       print "get ", dict.get("Ham"), dict.get("Tom"), dict.get("Mike")       print "test del"       del dict["Ham"]       print dict       print "test keys"       for key in dict.keys():              print " " + key       print "test values"       for value in dict.values():              print " " + `value`       print "test items"       for item in dict.items():              print " " + `item`       print "test has_keys"       print "true " + `dict.has_key("Tom")`       print "false " + `dict.has_key("Ham")`       print "test copy"       cdict = dict.copy()       print cdict       print "test clear"       dict.clear()       print dict       print "test update"       dict.update(cdict)       print cdict

We put the code we use to test the dictionary in the section of the module that runs only as a main module. That is,

>>> import dict >>> dict.main() test __setitem__ setitem {'Tom': 'Jerry', 'Mike': 'Ike', 'Ham': 'Eggs'} test __getitem__ and get getitem Eggs Jerry Ike get Eggs Jerry Ike test del {'Tom': 'Jerry', 'Mike': 'Ike'} test keys   Tom   Mike test values   'Jerry'   'Ike' test items   ('Tom', 'Jerry')   ('Mike', 'Ike') test has_keys true 1 false 0 test copy {'Tom': 'Jerry', 'Mike': 'Ike'} test clear {} test update {'Tom': 'Jerry', 'Mike': 'Ike'}

As promised, our dictionary class acts just like a regular dictionary.

To find out more about how to simulate built-in objects, refer to the documentation that ships with Python. It's a wealth of well-written information.

The Power of Polymorphism

The dictionary we created is simple; it only delegates operations to a real dictionary object. Imagine building a dictionary-like class that accesses a file, a database, or a directory server. To the application developer, this class looks just like another dictionary but does far more. You can even use it like a class instance transparently, with code written to work with built-in dictionaries. This is truly a powerful concept.

To write a class whose instances behave like a list, you implement the methods __getslice__(), __setslice__(), __delslice__(), append(), count(), index(), insert(), pop(), remove(), reverse(), and sort(), just like you do with Python standard list objects. For concatenation you use __add__() and __radd__().

Python's List and Dictionary Modules

Python ships with two class modules, userdict and userlist, that implement a dictionary and a list, respectively. Both are well documented.

We won't implement a list in this section. As an exercise at the end of the chapter, you'll create a list using a technique similar to the one we used for a dictionary.

Typed Polymorphism

With OOP you can build an entire framework of extensible reusable components. Python, unlike other languages, isn't strictly typed. In Java, Visual Basic, and C++, classes become types that the user can make into objects (i.e., instances), whereas in Python all instances of a class are of type Instance; also, classes aren't types unto themselves but are part of a type class. The following examples illustrate these ideas.

First we define a simple class, Myclass.

>>> class Myclass: ...    pass ...

Then we create an instance of Myclass and check its type.

>>> an_instance = Myclass() >>> type(an_instance) <jclass org.python.core.PyInstance at 5325988>

Next we check the type of Myclass.

>>> type(Myclass) <jclass org.python.core.PyClass at 7914640>

Programmers, if you're used to other languages that use typed classes, you may think that Python is a little weird. Actually, the typed system of other languages is orthogonal to Python's. The only real difference is a little bit of syntax and the fact that in Python classes don't define types.

For example, in Java you do this to get the type of a class:

an_instance.getClass().getName()

whereas in Python you do this:

an_instance.__class__.__name__

Again, in Java you do this to see if an object is an instance of a class:

if(an_instance instanceof MyClass)System.out.println("true");

In Python you do this:

if(isinstance(an_instance, MyClass)): print("true")

In Python, classes and instances are objects that can be manipulated at runtime, which is a good thing. For instance, you can add a method to a class at runtime so that every instance of that class has that method available in its interface. Also, you can add members to an instance at runtime. Whether those members are methods or other objects doesn't matter, because everything is an object that can be manipulated. This makes Python very flexible.

So, why have a section on typed polymorphism if Python doesn't really support it? Actually, Python has something very close. For example, you can check to see if an instance is a class type like this:

if isinstance(an_instance, MyClass)

Let's go back to our earlier example of cars and trucks with a function that takes a list of automobiles and starts each one.

def startAuto(list):        for auto in list:               if isinstance(auto, Automobile):                     auto.start()

The function invokes the __start__ method of each auto instance in the list. It doesn't care if the instance is a Car or a Truck because any instance of either is also an instance of Automobile and thus supports the __start__ method. This is the form of polymorphism most people with a Java background are used to.

The __start__ method works not only with the Car and Truck subclasses but also with any future subclass that inherits from Automobile, such as Motorcycle.

Summary

In this chapter we covered the fundamentals of object-oriented programming: polymorphism, inheritance, and encapsulation. We also looked at typed and late-bound polymorphism.

CONTENTS


Python Programming with the JavaT Class Libraries. A Tutorial for Building Web and Enterprise Applications with Jython
Python Programming with the Javaв„ў Class Libraries: A Tutorial for Building Web and Enterprise Applications with Jython
ISBN: 0201616165
EAN: 2147483647
Year: 2001
Pages: 25

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net