预计阅读本页时间:-
Attribute Inheritance Search
The good news is that OOP is much simpler to understand and use in Python than in other languages, such as C++ or Java. As a dynamically typed scripting language, Python removes much of the syntactic clutter and complexity that clouds OOP in other tools. In fact, most of the OOP story in Python boils down to this expression:
object.attribute
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
We’ve been using this expression throughout the book to access module attributes, call methods of objects, and so on. When we say this to an object that is derived from a class statement, however, the expression kicks off a search in Python—it searches a tree of linked objects, looking for the first appearance of attribute that it can find. When classes are involved, the preceding Python expression effectively translates to the following in natural language:
Find the first occurrence of attribute by looking in object, then in all classes above it, from bottom to top and left to right.
In other words, attribute fetches are simply tree searches. The term inheritance is applied because objects lower in a tree inherit attributes attached to objects higher in that tree. As the search proceeds from the bottom up, in a sense, the objects linked into a tree are the union of all the attributes defined in all their tree parents, all the way up the tree.
In Python, this is all very literal: we really do build up trees of linked objects with code, and Python really does climb this tree at runtime searching for attributes every time we use the object.attribute expression. To make this more concrete, Figure 25-1 sketches an example of one of these trees.
Figure 25-1. A class tree, with two instances at the bottom (I1 and I2), a class above them (C1), and two superclasses at the top (C2 and C3). All of these objects are namespaces (packages of variables), and the inheritance search is simply a search of the tree from bottom to top looking for the lowest occurrence of an attribute name. Code implies the shape of such trees.
In this figure, there is a tree of five objects labeled with variables, all of which have attached attributes, ready to be searched. More specifically, this tree links together three class objects (the ovals C1, C2, and C3) and two instance objects (the rectangles I1 and I2) into an inheritance search tree. Notice that in the Python object model, classes and the instances you generate from them are two distinct object types:
Classes
Serve as instance factories. Their attributes provide behavior—data and functions—that is inherited by all the instances generated from them (e.g., a function to compute an employee’s salary from pay and hours).
Instances
Represent the concrete items in a program’s domain. Their attributes record data that varies per specific object (e.g., an employee’s Social Security number).
In terms of search trees, an instance inherits attributes from its class, and a class inherits attributes from all classes above it in the tree.
In Figure 25-1, we can further categorize the ovals by their relative positions in the tree. We usually call classes higher in the tree (like C2 and C3) superclasses; classes lower in the tree (like C1) are known as subclasses.[57] These terms refer to relative tree positions and roles. Superclasses provide behavior shared by all their subclasses, but because the search proceeds from the bottom up, subclasses may override behavior defined in their superclasses by redefining superclass names lower in the tree.
As these last few words are really the crux of the matter of software customization in OOP, let’s expand on this concept. Suppose we build up the tree in Figure 25-1, and then say this:
I2.w
Right away, this code invokes inheritance. Because this is an object.attribute expression, it triggers a search of the tree in Figure 25-1—Python will search for the attribute w by looking in I2 and above. Specifically, it will search the linked objects in this order:
I2, C1, C2, C3
and stop at the first attached w it finds (or raise an error if w isn’t found at all). In this case, w won’t be found until C3 is searched because it appears only in that object. In other words, I2.w resolves to C3.w by virtue of the automatic search. In OOP terminology, I2 “inherits” the attribute w from C3.
Ultimately, the two instances inherit four attributes from their classes: w, x, y, and z. Other attribute references will wind up following different paths in the tree. For example:
- I1.x and I2.x both find x in C1 and stop because C1 is lower than C2.
- I1.y and I2.y both find y in C1 because that’s the only place y appears.
- I1.z and I2.z both find z in C2 because C2 is further to the left than C3.
- I2.name finds name in I2 without climbing the tree at all.
Trace these searches through the tree in Figure 25-1 to get a feel for how inheritance searches work in Python.
The first item in the preceding list is perhaps the most important to notice—because C1 redefines the attribute x lower in the tree, it effectively replaces the version above it in C2. As you’ll see in a moment, such redefinitions are at the heart of software customization in OOP—by redefining and replacing the attribute, C1 effectively customizes what it inherits from its superclasses.