DekGenius.com
[ Team LiB ] Previous Section Next Section

20.2 Classes Are Customized by Inheritance

Besides serving as object generators, classes also allow us to make changes by introducing new components (called subclasses), instead of changing existing components in place. Instance objects generated from a class inherit the class's attributes. Python also allows classes to inherit from other classes, and this opens the door to coding hierarchies of classes, that specialize behavior by overriding attributes lower in the hierarchy. Here, too, there is no parallel in modules: their attributes live in a single, flat namespace.

In Python, instances inherit from classes, and classes inherit from superclasses. Here are the key ideas behind the machinery of attribute inheritance:

  • S uperclasses are listed in parentheses in a class header. To inherit attributes from another class, just list the class in parentheses in a class statement's header. The class that inherits is called a subclass, and the class that is inherited from is its superclass.

  • Classes inherit attributes from their superclasses. Just like instances, a class gets all the attribute names defined in its superclasses; they're found by Python automatically when accessed, if they don't exist in the subclass.

  • Instances inherit attributes from all accessible classes. Instances get names from the class they are generated from, as well as all of that class's superclasses. When looking for a name, Python checks the instance, then its class, then all superclasses above.

  • Each object.attribute reference invokes a new, independent search. Python performs an independent search of the class tree, for each attribute fetch expression. This includes both references to instances and classes made outside class statements (e.g., X.attr), as well as references to attributes of the self instance argument in class method functions. Each self.attr in a method invokes a new search for attr in self and above.

  • Logic changes are made by subclassing, not by changing superclasses. By redefining superclass names in subclasses lower in a hierarchy (tree), subclasses replace, and thus, customize inherited behavior.

20.2.1 A Second Example

The next example builds on the one before. Let's define a new class, SecondClass, which inherits all of FirstClass's names and provides one of its own:

>>> class SecondClass(FirstClass):                    # Inherits setdata
...     def display(self):                            # Changes display 
...         print 'Current value = "%s"' % self.data

SecondClass defines the display method to print with a different format. But because SecondClass defines an attribute of the same name, it effectively overrides and replaces the display attribute in FirstClass.

Recall that inheritance works by searching up from instances, to subclasses, to superclasses, and stops at the first appearance of an attribute name it finds. Since it finds the display name in SecondClass before the one in FirstClass, we say that SecondClass overrides FirstClass's display. Sometimes we call this act of replacing attributes by redefining them lower in the tree overloading.

The net effect here is that SecondClass specializes FirstClass, by changing the behavior of the display method. On the other hand, SecondClass (and instances created from it) still inherits the setdata method in FirstClass verbatim. Figure 20-2 sketches the namespaces involved; let's make an instance to demonstrate:

>>> z = SecondClass(  )
>>> z.setdata(42)             # setdata found in FirstClass
>>> z.display(  )             # finds overridden method in SecondClass.
Current value = "42"
Figure 20-2. Specialization by overriding inherited names
figs/lpy2_2002.gif

As before, we make a SecondClass instance object by calling it. The setdata call still runs the version in FirstClass, but this time the display attribute comes from SecondClass and prints a custom message.

Here's a very important thing to notice about OOP: the specialization introduced in SecondClass is completely external to FirstClass; it doesn't effect existing or future FirstClass objects—like x from the prior example:

>>> x.display(  )       # x is still a FirstClass instance (old message).
New value

Rather than changing FirstClass, we customized it. Naturally, this is an artificial example; but as a rule, because inheritance allows us to make changes like this in external components (i.e., in subclasses), classes often support extension and reuse better than functions or modules can.

20.2.2 Classes and Modules

Before we move on, remember that there's nothing magic about a class name. It's just a variable assigned to an object when the class statement runs, and the object can be referenced with any normal expression. For instance, if our FirstClass was coded in a module file instead of being typed interactively, we could import and use its name normally in a class header line:

from modulename import FirstClass           # Copy name into my scope.
class SecondClass(FirstClass):              # Use class name directly.
    def display(self): ...

Or, equivalently:

import modulename                           # Access the whole module.
class SecondClass(modulename.FirstClass):   # Qualify to reference
    def display(self): ...

Like everything else, class names always live within a module, and so follow all the rules we studied in Part V. For example, more than one class can be coded in a single module file—like other names in a module, they are run and defined during imports, and become distinct module attributes. More generally, each module may arbitrarily mix any number of variables, functions, and classes, and all names in a module behave the same way. File food.py demonstrates:

var = 1           # food.var
def func(  ):     # food.func
    ...
class spam:     # food.spam
    ...
class ham:      # food.ham
    ...
class eggs:     # food.eggs
    ...

This holds true even if the module and class happen to have the same name. For example, given the following file, person.py:

class person:
    ...

We need to go through the module to fetch the class as usual:

import person                  # Import module
x = person.person(  )          # class within module.

Although this path may look redundant, it's required: person.person refers to the person class inside the person module. Saying just person gets the module, not the class, unless the from statement is used:

from person import person      # Get class from module.
x = person(  )                 # Use class name.

Like other variables, we can never see a class in a file without first importing and somehow fetching from its enclosing file. If this seems confusing, don't use the same name for a module and a class within it.

Also keep in mind that although classes and modules are both namespaces for attaching attributes, they correspond to very different source code structures: a module reflects an entire file, but a class is a statement within a file. We'll say more about such distinctions later in this part of the book.

    [ Team LiB ] Previous Section Next Section