I l@ve RuBoard |
5.1 IntroductionCredit: Alex Martelli, AB Strakt, author of forthcoming Python in a Nutshell Object-oriented programming (OOP) is among Python's greatest strengths. Python's OOP features keep improving steadily and gradually, just like Python in general. You could write object-oriented programs better in Python 1.5.2 (the ancient, long-stable version that was new when I first began to work with Python) than in any other popular language (excluding, of course, Lisp and its variants�I doubt there's anything you can't do well in Lisp-like languages, as long as you can stomach the parentheses-heavy concrete syntax). Now, with Python 2.2, OOP is substantially better than with 1.5.2. I am constantly amazed at the systematic progress Python achieves without sacrificing solidity, stability, and backward compatibility. To get the most out of Python's OOP features, you should use them "the Python way," rather than trying to mimic C++, Java, Smalltalk, or other languages you may be familiar with. You can do a lot of mimicry, but you'll get better mileage if you invest in understanding the Python way. Most of the investment is in increasing your understanding of OOP itself: what does OOP buy you, and which underlying mechanisms can your object-oriented programs use? The rest of the investment is in understanding the specific mechanisms that Python itself offers. One caveat is in order. For such a high-level language, Python is quite explicit about the OOP mechanisms it uses behind the curtains: they're exposed and available for your exploration and tinkering. Exploration and understanding are good, but beware the temptation to tinker. In other words, don't use unnecessary black magic just because you can. Specifically, don't use it in production code (code that you and others must maintain). If you can meet your goals with simplicity (and most often, in Python, you can), then keep your code simple. So what is OOP all about? First of all, it's about keeping some state (data) and some behavior (code) together in handy packets. "Handy packets" is the key here. Every program has state and behavior�programming paradigms differ only in how you view, organize, and package them. If the packaging is in terms of objects that typically comprise state and behavior, you're using OOP. Some object-oriented languages force you to use OOP for everything, so you end up with many objects that lack either state or behavior. Python, however, supports multiple paradigms. While everything in Python is an object, you package things up as OOP objects only when you want to. Other languages try to force your programming style into a predefined mold for your own good, while Python empowers you to make and express your own design choices. With OOP, once you have specified how an object is composed, you can instantiate as many objects of that kind as you need. When you don't want to create multiple objects, consider using other Python constructs, such as modules. In this chapter, you'll find recipes for Singleton, an object-oriented design pattern that takes away the multiplicity of instantiation. But if you want only one instance, in Python it's often best to use a module, not an OOP object. To describe how an object is made up, use the class statement: class SomeName: """ You usually define data and code here (in the class body). """ SomeName is a class object. It's a first-class object like every Python object, so you can reference it in lists and dictionaries, pass it as an argument to a function, and so on. When you want a new instance of a class, call the class object as if it was a function. Each call returns a new instance object: anInstance = SomeName( ) another = SomeName( ) anInstance and another are two distinct instance objects, both belonging to the SomeName class. (See Recipe 1.8 for a class that does little more than this but is quite useful.) You can bind and access attributes (state) of an instance object: anInstance.someNumber = 23 * 45 print anInstance.someNumber # 1035 Instances of an "empty" class like this have no behavior, but they may have state. Most often, however, you want instances to have behavior. Specify this behavior by defining methods in the class body: class Behave: def _ _init_ _(self, name): self.name = name def once(self): print "Hello, ", self.name def rename(self, newName) self.name = newName def repeat(self, N): for i in range(N): self.once( ) Define methods with the same def statement Python uses to define functions, since methods are basically functions. However, a method is an attribute of a class object, and its first formal argument is (by universal convention) named self. self always refers to the instance on which you call the method. The method with the special name _ _init_ _ is known as the constructor for the class. Python calls it to initialize each newly created instance, with the arguments that you passed when calling the class (except for self, which you do not pass explicitly, as Python supplies it automatically). The body of _ _init_ _ typically binds attributes on the newly created self instance to initialize the instance's state appropriately. Other methods implement the behavior of instances of the class. Typically, they do so by accessing instance attributes. Also, methods often rebind instance attributes, and they may call other methods. Within a class definition, these actions are always done with the self.something syntax. Once you instantiate the class, however, you call methods on the instance, access the instance's attributes, and even rebind them using the theobject.something syntax: beehive = Behave("Queen Bee") beehive.repeat(3) beehive.rename("Stinger") beehive.once( ) print beehive.name beehive.name = 'See, you can rebind it "from the outside" too, if you want' beehive.repeat(2) If you're new to OOP in Python, try implementing these things in an interactive Python environment, such as the GUI shell supplied by the free IDLE development environment that comes with Python. In addition to the constructor (_ _init_ _), your class may have other special methods, which are methods with names that start and end with two underscores. Python calls the special methods of a class when instances of the class are used in various operations and built-in functions. For example, len(x) returns x._ _len_ _( ), a+b returns a._ _add_ _(b), and a[b] returns a._ _getitem_ _(b). Therefore, by defining special methods in a class, you can make instances of that class interchangeable with objects of built-in types, such as numbers, lists, dictionaries, and so on. The ability to handle different objects in similar ways, called polymorphism, is a major advantage of OOP. With polymorphism, you can call the same method on each object and let each object implement the method appropriately. For example, in addition to the Behave class, you might have another class that implements a repeat method, with a rather different behavior: class Repeater: def repeat(self, N): print N*"*-*" You can mix instances of Behave and Repeater at will, as long as the only method you call on them is repeat: aMix = beehive, Behave('John'), Repeater( ), Behave('world') for whatever in aMix: whatever.repeat(3) Other languages require inheritance or the formal definition and implementation of interfaces for polymorphism to work. In Python, all you need is methods with the same signature (i.e., methods that are callable with the same arguments). Python also has inheritance, which is a handy way to reuse code. You can define a class by inheriting from another and then adding or redefining (known as overriding) some of its methods: class Subclass(Behave): def once(self): print '(%s)' % self.name subInstance = Subclass("Queen Bee") subInstance.repeat(3) The Subclass class overrides only the once method, but you can also call the repeat method on subInstance, as it inherits that method from the Behave superclass. The body of the repeat method calls once N times on the specific instance, using whatever version of the once method the instance has. In this case, it uses the method from the Subclass class, which prints the name in parentheses, not the version from the Behave class, which prints it after a greeting. The idea of a method calling other methods on the same instance and getting the appropriately overridden version of each is important in every object-oriented language, including Python. This is known as the Template-Method design pattern. Often, the method of a subclass overrides a method from the superclass, but needs to call the method of the superclass as a part of its own operation. You do this in Python by explicitly getting the method as a class attribute and passing the instance as the first argument: class OneMore(Behave): def repeat(self, N): Behave.repeat(self, N+1) zealant = OneMore("Worker Bee") zealant.repeat(3) The OneMore class implements its own repeat method in terms of the method with the same name in its superclass, Behave, with a slight change. This approach, known as delegation, is pervasive in all programming. Delegation involves implementing some functionality by letting another existing piece of code do most of the work, often with some slight variation. Often, an overriding method is best implemented by delegating some of the work to the same method in the superclass. In Python, the syntax Classname.method(self, ...) delegates to Classname's version of the method. Python actually supports multiple inheritance: one class can inherit from several others. In terms of coding, this is a minor issue that lets you use the mix-in class idiom, a convenient way to supply some functionality across a broad range of classes. (See Recipe 5.14 for an unusual variant of this.) However, multiple inheritance is important because of its implications for object-oriented analysis�how you conceptualize your problem and your solution in the first place. Single inheritance pushes you to frame your problem space via taxonomy (i.e., mutually exclusive classification). The real world doesn't work like that. Rather, it resembles Jorge Luis Borges's explanation in "The Analytical Language of John Wilkins", from a purported Chinese Encyclopedia, The Celestial Emporium of Benevolent Knowledge. Borges explains that all animals are divided into:
You get the point: taxonomy forces you to pigeonhole, fitting everything into categories that aren't truly mutually exclusive. Modeling aspects of the real world in your programs is hard enough without buying into artificial constraints such as taxonomy. Multiple inheritance frees you from these constraints. Python 2.2 has introduced an important innovation in Python's object model. Classic classes, such as those mentioned in this introduction, still work as they always did. In addition, you can use new-style classes, which are classes that subclass a built-in type, such as list, dict, or file. If you want a new-style class and do not need to inherit from any specific built-in type, you can subclass the new type object, which is the root of the whole inheritance hierarchy. New-style classes work like existing ones, with some specific changes and several additional options. The recipes in this book were written and collected before the release of Python 2.2, and therefore use mostly classic classes. However this chapter specifies if a recipe might be inapplicable to a new-style class (a rare issue) or if new-style classes might offer alternative (and often preferable) ways to accomplish the same tasks (which is most often the case). The information you find in this chapter is therefore just as useful whether you use Python 2.1, 2.2, or even the still-experimental 2.3 (being designed as we write), which won't change any of Python's OOP features. |
I l@ve RuBoard |