Chapter 15. Classes and object-oriented programming – The Quick Python Book

Chapter 15. Classes and object-oriented programming

This chapter covers

  • Defining classes
  • Using instance variables and @property
  • Defining methods
  • Defining class variables and methods
  • Inheriting from other classes
  • Making variables and methods private
  • Inheriting from multiple classes

In this chapter, we discuss Python classes, which can be used in a manner analogous to C structures but which can also be used in a full object-oriented manner. For the benefit of readers who aren’t object-oriented programmers, we’ll discuss the use of classes as structures in the first two subsections.

The remainder of the chapter discusses OOP in Python. This is only a description of the constructs available in Python; it’s not an exposition on object-oriented programming itself.

15.1. Defining classes

A class in Python is effectively a data type. All the data types built into Python are classes, and Python gives you powerful tools to manipulate every aspect of a class’s behavior. You define a class with the class statement:

class MyClass:
body

body is a list of Python statements, typically variable assignments and function definitions. No assignments or function definitions are required. The body can be just a single pass statement.

By convention, class identifiers are in CapCase—that is, the first letter of each component word is capitalized, to make them stand out. After you define the class, a new object of the class type (an instance of the class) can be created by calling the class name as a function:

instance = MyClass()

15.1.1. Using a class instance as a structure or record

Class instances can be used as structures or records. Unlike C structures, the fields of an instance don’t need to be declared ahead of time but can be created on the fly. The following short example defines a class called Circle, creates a Circle instance, assigns to the radius field of the circle, and then uses that field to calculate the circumference of the circle:

>>> class Circle:
... pass
...
>>> my_circle = Circle()
>>> my_circle.radius = 5
>>> print(2 * 3.14 * my_circle.radius)
31.4

Like Java and many other languages, the fields of an instance/structure are accessed and assigned to by using dot notation.

You can initialize fields of an instance automatically by including an __init__ initialization method in the class body. This function is run every time an instance of the class is created, with that new instance as its first argument. The __init__ method is similar to a constructor in Java, but it doesn’t really construct anything—it initializes fields of the class. This example creates circles with a radius of 1 by default:

By convention, self is always the name of the first argument of __init__. self is set to the newly created circle instance when __init__ is run . Next, the code uses the class definition. We first create a Circle instance object . The next line makes use of the fact that the radius field is already initialized . We can also overwrite the radius field ; as a result, the last line prints a different result than the previous print statement .

You can do a great deal more by using true object-oriented programming, and if you’re not familiar with OOP, I urge you to read up on it. Python’s object-oriented programming constructs are the subject of the remainder of this chapter.

15.2. Instance variables

Instance variables are the most basic feature of OOP. Take a look at the Circle class again:

class Circle:
def __init__(self):
self.radius = 1

radius is an instance variable of Circle instances. That is, each instance of the Circle class has its own copy of radius, and the value stored in that copy may be different from the values stored in the radius variable in other instances. In Python, you can create instance variables as necessary by assigning to a field of a class instance:

instance.variable = value

If the variable doesn’t already exist, it’s created automatically. This is how __init__ creates the radius variable.

All uses of instance variables—both assignment and access—require explicit mention of the containing instance—that is, instance.variable. A reference to variable by itself is a reference not to an instance variable but rather to a local variable in the executing method. This is different from C++ or Java, where instance variables are referred to in the same manner as local method function variables. I rather like Python’s requirement for explicit mention of the containing instance, because it clearly distinguishes instance variables from local function variables.

15.3. Methods

A method is a function associated with a particular class. You’ve already seen the special __init__ method, which is called on a new instance when that instance is first created. In the following example, we define another method, area, for the Circle class, which can be used to calculate and return the area for any Circle instance. Like most user-defined methods, area is called with a method invocation syntax that resembles instance variable access:

>>> class Circle:
... def __init__(self):
... self.radius = 1
... def area(self):
... return self.radius * self.radius * 3.14159
...
>>> c = Circle()
>>> c.radius = 3
>>> print(c.area())
28.27431

Method invocation syntax consists of an instance, followed by a period, followed by the method to be invoked on the instance. The previous syntax is sometimes called bound method invocation. area can also be invoked as an unbound method by accessing it through its containing class. This is less convenient and is almost never done. When a method is invoked in this manner, its first argument must be an instance of the class in which that method is defined:

>>> print(Circle.area(c))
28.27431

Like __init__, the area method is defined as a function within the body of the class definition. The first argument of any method is the instance it was invoked by or on, named self by convention.

Methods can be invoked with arguments, if the method definitions accept those arguments. This version of Circle adds an argument to the __init__ method, so that we can create circles of a given radius without needing to set the radius after a circle is created:

class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return self.radius * self.radius * 3.14159

Note the two uses of radius here. self.radius is the instance variable called radius. radius by itself is the local function variable called radius. The two aren’t the same! In practice, we’d probably call the local function variable something like r or rad, to avoid any possibility of confusion.

Using this definition of Circle, we can create circles of any radius with one call on the circle class. The following creates a Circle of radius 5:

c = Circle(5)

All the standard Python function features—default argument values, extra arguments, keyword arguments, and so forth—can be used with methods. For example, we could have defined the first line of __init__ to be

    def __init__(self, radius=1):

Then, calls to circle would work with or without an extra argument; Circle() would return a circle of radius 1, and Circle(3) would return a circle of radius 3.

There’s nothing magical about method invocation in Python. It can be considered shorthand for normal function invocation. Given a method invocation instance.method(arg1, arg2, . . .), Python transforms it into a normal function call by using the following rules:

1.  

Look for the method name in the instance namespace. If a method has been changed or added for this instance, it’s invoked in preference over methods in the class or superclass. This is the same sort of lookup discussed later in section 15.4.1.

2.  

If the method isn’t found in the instance namespace, look up the class type class of instance, and look for the method there. In the previous examples, class is Circle—the type of the instance c.

3.  

If the method still isn’t found, look for the method in the superclasses.

4.  

When the method has been found, make a direct call to it as a normal Python, using instance as the first argument of the function, and shifting all the other arguments in the method invocation one space over to the right. So, instance.method(arg1, arg2, . . .) becomes class.method(instance, arg1, arg2, . . .).

15.4. Class variables

A class variable is a variable associated with a class, not an instance of a class, and is accessed by all instances of the class, in order to keep track of some class-level information, such as how many instances of the class have been created at any point in time. Python provides class variables, although using them requires slightly more effort than in most other languages. Also, you need to watch out for an interaction between class and instance variables.

A class variable is created by an assignment in the class body, not in the __init__ function; after it has been created, it can be seen by all instances of the class. We can use a class variable to make a value for pi accessible to all instances of the Circle class:

class Circle:
pi = 3.14159
def __init__(self, radius):
self.radius = radius
def area(self):
return self.radius * self.radius * Circle.pi

With the above definition entered, we can type

>>> Circle.pi
3.1415899999999999
>>> Circle.pi = 4
>>> Circle.pi
4
>>> Circle.pi = 3.14159
>>> Circle.pi
3.1415899999999999

This is exactly how we would expect a class variable to act. It’s associated with and contained in the class that defines it. Notice in this example that we’re accessing Circle.pi before any circle instances have been created. Obviously, Circle.pi exists independently of any specific instances of the Circle class.

You can also access a class variable from a method of a class, through the class name. We do so in the definition of Circle.area, where the area function makes specific reference to Circle.pi. In operation, this has the desired effect; the correct value for pi is obtained from the class and used in the calculation:

>>> c = Circle(3)
>>> c.area()
28.27431

You may object to hardcoding the name of a class inside that class’s methods. You can avoid doing so through use of the special __class__ attribute, available to all Python class instances. This attribute returns the class of which the instance is a member, for example:

>>> Circle
<class '__main__.Circle'>
>>> c.__class__
<class '__main__.Circle'>

The class named Circle is represented internally by an abstract data structure, and that data structure is exactly what is obtained from the __class__ attribute of c, an instance of the Circle class. This lets us obtain the value of Circle.pi from c without ever explicitly referring to the Circle class name:

>>> c.__class__.pi
3.1415899999999999

Of course, we could use this internally in the area method to get rid of the explicit reference to the Circle class; replace Circle.pi with self.__class__.pi.

15.4.1. An oddity with class variables

There’s a bit of an oddity with class variables that can trip you up if you aren’t aware of it. When Python is looking up an instance variable, if it can’t find an instance variable of that name, it will then try to find and return the value in a class variable of the same name. Only if it can’t find an appropriate class variable will it signal an error. This does make it efficient to implement default values for instance variables; just create a class variable with the same name and appropriate default value, and avoid the time and memory overhead of initializing that instance variable every time a class instance is created. But this also makes it easy to inadvertently refer to an instance variable rather than a class variable, without signaling an error. Let’s look at how this operates in conjunction with the previous example.

First, we can refer to the variable c.pi, even though c doesn’t have an associated instance variable named pi. Python will first try to look for such an instance variable, but when it can’t find it, it will then look for a class variable pi in Circle and find it:

>>> c = Circle(3)
>>> c.pi
3.1415899999999999

This may or may not be what you want; it’s convenient but can be prone to error, so be careful.

Now, what happens if we attempt to use c.pi as a true class variable, by changing it from one instance with the intent that all instances should see the change? Again, we’ll use the earlier definition for Circle:

>>> c1 = Circle(1)
>>> c2 = Circle(2)
>>> c1.pi = 3.14
>>> c1.pi
3.140000000000001
>>> c2.pi
3.1415899999999999
>>> Circle.pi
3.1415899999999999

This doesn’t work as it would for a true class variable—c1 now has its own copy of pi, distinct from the Circle.pi accessed by c2. This is because the assignment to c1.pi creates an instance variable in c1; it doesn’t affect the class variable Circle.pi in any way. Subsequent lookups of c1.pi return the value in that instance variable, whereas subsequent lookups of c2.pi look for an instance variable pi in c2, fail to find it, and resort to returning the value of the class variable Circle.pi. If you want to change the value of a class variable, access it through the class name, not through the instance variable self.

15.5. Static methods and class methods

Python classes can also have methods that correspond explicitly to static methods in a language such as Java. In addition, Python has class methods, which are a bit more advanced.

15.5.1. Static methods

Just as in Java, you can invoke static methods even though no instance of that class has been created, although you can call them using a class instance. To create a static method, use the @staticmethod decorator, as shown in listing 15.1.

Listing 15.1. File circle.py

Now, interactively type the following:

>>> import circle
>>> c1 = circle.Circle(1)
>>> c2 = circle.Circle(2)
>>> circle.Circle.total_area()
15.70795
>>> c2.radius = 3
>>> circle.Circle.total_area()
31.415899999999997

Also notice that documentation strings are used. In a real module, you’d probably put in more informative strings, indicating in the class docstring what methods are available and including usage information in the method docstrings:

>>> circle.__doc__
'circle module: contains the Circle class.'
>>> circle.Circle.__doc__
'Circle class'
>>> circle.Circle.area.__doc__
'determine the area of the Circle'

15.5.2. Class methods

Class methods are similar to static methods in that they can be invoked before an object of the class has been instantiated or by using an instance of the class. But class methods are implicitly passed the class they belong to as their first parameter, so you can code them more simply, as in listing 15.2.

Listing 15.2. File circle_cm.py

The @classmethod decorator is used before the method def . The class parameter is traditionally cls . You can use cls instead of self.__class__ .

By using a class method instead of a static method, we don’t have to hardcode the class name into total_area. That means any subclasses of Circle can still call total_area and refer to their own members, not those in Circle.

15.6. Inheritance

Inheritance in Python is easier and more flexible than inheritance in compiled languages such as Java and C++ because the dynamic nature of Python doesn’t force as many restrictions on the language.

To see how inheritance is used in Python, we start with the Circle class given previously and generalize. We might want to define an additional class for squares:

Now, if we want to use these classes in a drawing program, they must define some sense of where on the drawing surface each instance is. We can do so by defining an x coordinate and a y coordinate in each instance:

class Square:
def __init__(self, side=1, x=0, y=0):
self.side = side
self.x = x
self.y = y
class Circle:
def __init__(self, radius=1, x=0, y=0):
self.radius = radius
self.x = x
self.y = y

This approach works but results in a good deal of repetitive code as we expand the number of shape classes, because each shape will presumably want to have this concept of position. No doubt you know where we’re going here. This is a standard situation for using inheritance in an object-oriented language. Instead of defining the x and y variables in each shape class, abstract them out into a general Shape class, and have each class defining an actual shape inherit from that general class. In Python, that looks like this:

There are (generally) two requirements in using an inherited class in Python, both of which you can see in the bolded code in the Circle and Square classes. The first requirement is defining the inheritance hierarchy, which you do by giving the classes inherited from, in parentheses, immediately after the name of the class being defined with the class keyword. In the previous code, Circle and Square both inherit from Shape. The second and more subtle element is the necessity to explicitly call the __init__ method of inherited classes. Python doesn’t automatically do this for you, but you can use the super function to have Python figure out which inherited class to use. This is accomplished in the example code by the super().__init__(x,y) lines. This calls the Shape initialization function with the instance being initialized and the appropriate arguments. If this weren’t done, then in the example, instances of Circle and Square wouldn’t have their x and y instance variables set.

Instead of using super, we could call Shape’s __init__ by explicitly naming the inherited class using Shape.__init__(self, x, y), which would also call the Shape initialization function with the instance being initialized. This wouldn’t be as flexible in the long run, because it hardcodes the inherited class’s name, which could be a problem later if the design and the inheritance hierarchy change. On the other hand, the use of super can be tricky in more complex cases. Because the two methods don’t exactly mix well, clearly document whichever approach you use in your code.

Inheritance comes into effect when you attempt to use a method that isn’t defined in the base classes but is defined in the superclass. To see this, let’s define another method in the Shape class called move, which will move a shape by a given displacement. It will modify the x and y coordinates of the shape by an amount determined by arguments to the method. The definition for Shape now becomes

class Shape:
def __init__(self, x, y):
self.x = x
self.y = y
def move(self, delta_x, delta_y):
self.x = self.x + delta_x
self.y = self.y + delta_y

If we enter this definition for Shape and the previous definitions for Circle and Square, we can then engage in the following interactive session:

>>> c = Circle(1)
>>> c.move(3, 4)
>>> c.x
3
>>> c.y
4

If you try this and are doing it in an interactive session, be sure to reenter the Circle class after the redefinition of the Shape class.

The Circle class didn’t define a move method immediately within itself, but because it inherits from a class that implements move, all instances of Circle can make use of move.

15.7. Inheritance with class and instance variables

Inheritance allows an instance to inherit attributes of the class. Instance variables are associated with object instances, and only one instance variable of a given name exists for a given instance.

To see this, consider the following example. Using these class definitions,

class P:
z = "Hello"
def set_p(self):
self.x = "Class P"
def print_p(self):
print(self.x)
class C(P):
def set_c(self):
self.x = "Class C"
def print_c(self):
print(self.x)

execute the following code:

>>> c = C()
>>> c.set_p()
>>> c.print_p()
Class P
>>> c.print_c()
Class P
>>> c.set_c()
>>> c.print_c()
Class C
>>> c.print_p()
Class C

The object c in this example is an instance of class C. C inherits from P, but c doesn’t inherit from some invisible instance of class P. It inherits methods and class variables directly from P. Because there is only one instance (c), any reference to the instance variable x in a method invocation on c must refer to c.x. This is true regardless of which class defines the method being invoked on c. As you can see, when they’re invoked on c, both set_p and print_p, defined in class P, refer to the same variable referred to by set_c and print_c when they’re invoked on c.

In general, this is what is desired for instance variables because it makes sense that references to instance variables of the same name should refer to the same variable. Occasionally, somewhat different behavior is desired, which you can achieve using private variables. These are explained in the next subsection.

Class variables are inherited, but you should take care to avoid name clashes and be aware of a generalization of the same behavior you saw in the earlier subsection on class variables. In our example, a class variable z is defined for the superclass P. It can be accessed in three different ways: through the instance c, through the derived class C, or directly through the superclass P:

>>> c.z; C.z; P.z
'Hello'
'Hello'
'Hello'

But if we try setting it through the class C, a new class variable will be created for the class C. This has no effect on P’s class variable itself (as accessed through P). But future accesses through the class C or its instance c will see this new variable rather than the original:

>>> C.z = "Bonjour"
>>> c.z; C.z; P.z
'Bonjour'
'Bonjour'
'Hello'

Similarly, if we try setting z through the instance c, a new instance variable will be created, and we’ll end up with three different variables:

>>> c.z = "Ciao"
>>> c.z; C.z; P.z
'Ciao'
'Bonjour'
'Hello'

15.8. Private variables and private methods

A private variable or private method is one that can’t be seen outside of the methods of the class in which it’s defined. Private variables and methods are useful for a number of reasons. They enhance security and reliability by selectively denying access to important or delicate parts of an object’s implementation. They avoid name clashes that can arise from the use of inheritance. A class may define a private variable and inherit from a class that defines a private variable of the same name, but this doesn’t cause a problem, because the fact that the variables are private ensures that separate copies of them are kept. Finally, private variables make it easier to read code, because they explicitly indicate what is used only internally in a class. Anything else is the class’s interface.

Most languages that define private variables do so through the use of a private or other similar keyword. The convention in Python is simpler, and it also makes it easier to immediately see what is private and what isn’t. Any method or instance variable whose name begins—but doesn’t end—with a double underscore (__) is private; anything else isn’t private.

As an example, consider the following class definition:

Using this definition, create an instance of the class:

>>> m = Mine()

x isn’t a private variable, so it’s directly accessible:

>>> print(m.x)
2

__y is a private variable. Trying to access it directly raises an error:

>>> print(m.__y)
Traceback (innermost last):
File "<stdin>", line 1, in ?
AttributeError: 'Mine' object has no attribute '__y'

The print_y method isn’t private, and because it’s in the Mine class, it can access __y and print it:

>>> m.print_y()
3

Finally, you should note that the mechanism used to provide privacy is to mangle the name of private variables and private methods when the code is compiled to bytecode. What specifically happens is that _classname is appended to the variable name:

>>> dir(m)
['_Mine__y', 'x', ...]

The purpose is to avoid any accidental accesses. If someone wanted to, they could access the value. But by performing the mangling in this easily readable form, debugging is made easy.

15.9. Using @property for more flexible instance variables

Python allows you as the programmer to access instance variables directly, without the extra machinery of getter and setter methods often used in Java and other OO languages. This lack of getters and setters makes writing Python classes cleaner and easier; but in some situations, using getter and setter methods can be handy. Suppose you want a value before you put it into an instance variable or where it would be handy to figure out an attribute’s value on the fly. In both cases, getter and setter methods would do the job but at the cost of losing Python’s easy instance variable access.

The answer is to use a property. A property combines the ability to pass access to an instance variable through methods like getters and setters and the straightforward access to instance variables through dot notation.

To create a property, you use the property decorator with a method having the property’s name:

class Temperature:
def __init__(self):
self._temp_fahr = 0
@property
def temp(self):
return (self._temp_fahr - 32) * 5 / 9

Without a setter, such a property is read-only. To change the property, you need to add a setter:

    @temp.setter
def temp(self, new_temp):
self._temp_fahr = new_temp * 9 / 5 + 32

Now, you can use standard dot notation to both get and set the property temp. Notice that the name of the method remains the same, but the decorator changes to the property name (temp in this case) plus .setter to indicate that a setter for the temp property is being defined:

The 0 in _temp_fahr is converted to centigrade before it’s returned . The 34 is converted back to Fahrenheit by the setter .

One big advantage of Python’s ability to add properties is that you can do initial development with plain-old instance variables and then seamlessly change to properties whenever and wherever you need to without changing any client code—the access is still the same, using dot notation.

15.10. Scoping rules and namespaces for class instances

Now you have all the pieces to put together a picture of the scoping rules and namespaces for a class instance.

When you’re in a method of a class, you have direct access to the local namespace (parameters and variables declared in the method), the global namespace (functions and variables declared at the module level), and the built-in namespace (built-in functions and built-in exceptions). These three namespaces are searched in the following order: local, global, and built in (see figure 15.1).

Figure 15.1. Direct namespaces

You also have access through the self variable to our instance’s namespace (instance variables, private instance variables, and superclass instance variables), its class’s namespace (methods, class variables, private methods, and private class variables), and its superclass’s namespace (superclass methods and superclass class variables). These three namespaces are searched in the order instance, class, and then superclass (see figure 15.2).

Figure 15.2. Self variable namespaces

Private superclass instance variables, private superclass methods, and private superclass class variables can’t be accessed using self. A class is able to hide these names from its children.

The module in listing 15.3 puts these two together in one place to concretely demonstrate what can be accessed from within a method.

Listing 15.3. File cs.py

This output is considerable, so we’ll look at it in pieces. In the first part, class C's method m’s local namespace contains the parameters self (which is our instance variable) and p along with the local variable lv (all of which can be accessed directly):

>>> import cs
>>> c = cs.C()
>>> c.m()
Access local, global and built-in namespaces directly
local namespace: ['lv', 'p', 'self']
parameter: p
local variable: lv

Next, method m’s global namespace contains the module variable mv and the module function mf, (which, as described in a previous section, we can use to provide a class method functionality). There are also the classes defined in the module (the class C and the superclass SC). These can all be directly accessed:

global namespace: ['C', 'mf', '__builtins__', '__file__', '__package__',
'mv', 'SC', '__name__', '__doc__']
module variable: mv
module function (can be used like a class method in other languages): mf()

Instance C’s namespace contains instance variable iv and our superclass’s instance variable siv (which, as described in a previous section, is no different from our regular instance variable). It also has the mangled name of private instance variable __piv (which we can access through self) and the mangled name of our superclass’s private instance variable __psiv (which we can’t access):

Access instance, class, and superclass namespaces through 'self'
Instance namespace: ['_C__pcv', '_C__piv', '_C__pm', '_SC__pscv',
'_SC__psiv', '_SC__spm', '__class__', '__delattr__', '__dict__',
'__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__sizeof__', '__str__', '__subclasshook__',
'__weakref__', 'cv', 'iv', 'm', 'm2', 'scv', 'siv', 'sm']
instance variable: self.xi
private instance variable: self.__piv
superclass instance variable: self.siv (but use SC.siv for assignment)

Class C’s namespace contains the class variable cv and the mangled name of the private class variable __pcv: both can be accessed through self, but to assign to them we need to use class C. It also has the class’s two methods M and M2, along with the mangled names of the private method __PM (which can be accessed through self):

Class namespace: ['_C__pcv', '_C__pm', '_SC__pscv', '_SC__spm', '__class__',
'__delattr__', '__dict__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__gt__', '__hash__', '__init__', '__le__',
'__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', 'cv', 'm', 'm2', 'scv', 'sm']
class variable: self.cv (but use C.cv for assignment)
method: self.m2()
class private variable: self.__pcv (but use C.__pcv for assignment)
private method: self.__pm()

Finally, superclass SC’s namespace contains superclass class variable scv (which can be accessed through self, but to assign to it we need to use the superclass SC) and superclass method SM. It also contains the mangled names of private superclass method __SPM and private superclass class variable __spcv, neither of which can be accessed through self:

Superclass namespace: ['_SC__pscv', '_SC__spm', '__class__', '__delattr__',
'__dict__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__gt__', '__hash__', '__init__', '__le__',
'__lt__', '__module__', '__ne__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', 'scv', 'sm']
superclass method: self.SM()
superclass class variable: self.scv

This is a rather full example to decipher at first. You can use it as a reference or a base for your own exploration. As with most other concepts in Python, you can build a solid understanding of what is going on by playing around with a few simplified examples.

15.11. Destructors and memory management

You’ve already seen class constructors (the __init__ methods). A destructor can be defined for a class as well. But unlike in C++, creating and calling a destructor isn’t necessary to ensure that the memory used by your instance is freed. Python provides automatic memory management through a reference-counting mechanism. That is, it keeps track of the number of references to your instance; when this reaches zero, the memory used by your instance is reclaimed, and any Python objects referenced by your instance have their reference counts decremented by one. For the vast majority of your classes, you won’t need to define a destructor.

C++ destructors are sometimes used to also perform cleanup tasks such as releasing or resetting system resources unrelated to memory management. To perform these functions in Python, using context managers using the with keyword or the definition of explicit cleanup or close methods for your classes is the best way to go.

If you need to, you can also define destructor methods for your classes. Python will implicitly call a class’s destructor method __del__ just before an instance is removed upon its reference count reaching zero. You can use this as a backup to ensure that your cleanup method is called. The following simple class illustrates this:

Notice that close is written so that it can be called more than once without complaint. This is what you’ll generally want to do. Also, the __del__ function has a print expression in it. But this is just for demonstration purposes. Take the following test function:

>>> def test():
... f = SpecialFile('testfile')
... f.write('111111\n')
... f.close()
...
>>> test()
entered __del__

When the function test exits, f’s reference count goes to zero and __del__ is called. Thus, in the normal case close is called twice, which is why we want close to be able to handle this. If we forgot the f.close() at the end of test, the file would still be closed properly because we’re backed up by the call to the destructor. This also happens if we reassign to the same variable without first closing the file:

>>> f = SpecialFile('testfile')
>>> f = SpecialFile('testfile2')
entered __del__

As with the __init__ constructor, the __del__ destructor of a class’s parent class needs to be called explicitly within a class’s own destructor. Be careful when writing a destructor. If it’s called when a program is shutting down, members of its global namespace may already have been deleted. Any exception that occurs during its execution will be ignored, other than a message being sent of the occurrence to sys.stderr. Also, there’s no guarantee that destructors will be called for all still-existing instances when the Python interpreter exits. Check the entries for destructors in the Python Language Manual and the Python FAQ for more details. They will also give you hints as to what may be happening in cases where you think all references to your object should be gone but its destructor hasn’t been called.

Partly because of these issues, some people avoid using Python’s destructors other than possibly to flag an error when they’ve missed putting in an explicit call. They prefer that cleanup always be done explicitly. Sometimes they’re worth using, but only when you know the issues.

If you’re familiar with Java, you’re aware that this is what you have to do in that language. Java uses garbage collection, and its finalize methods aren’t called if this mechanism isn’t invoked (which may be never in some programs). Python’s destructor invocation is more deterministic. When the references to an object go away, it’s individually removed. On the other hand, if you have structures with cyclical references that have a __del__ method defined, they aren’t removed automatically. You have to go in and do this yourself. This is the main reason why defining your own __del__ method destructors isn’t recommended.

The following example illustrates the effect of a cyclical reference in Python and how you might break it. The purpose of the __del__ method in this example is only to indicate when an object is removed:

Because they still refer to each other, a and b aren’t removed when test1 exits . This is a memory leak. That is, each time test1 is called, it leaks two more objects. The explicit call to the cleanup method is necessary to avoid this .

The cycle is broken in the cleanup method, not the destructor, and we only had to break it in one place. Python’s reference-counting mechanism took over from there. This approach is not only more reliable, but also more efficient, because it reduces the amount of work that the garbage collector has to do.

A more robust method of ensuring that our cleanup method is called is to use the try-finally compound statement. It takes the following form:

try:
body
finally:
cleanup_body

It ensures that cleanup_body is executed regardless of how or from where body is exited. We can easily see this by writing and executing another test function for the Circle class defined earlier:

>>> def test3(x):
... try:
... c = Circle("c", None)
... d = Circle("d", c)
... if x == 1:
... print("leaving test3 via a return")
... return
... if x == 2:
... print("leaving test3 via an exception")
... raise RuntimeError
... print("leaving test3 off the end")
... finally:
... d.cleanup()
...
>>> test3(0)
leaving test3 off the end
__del__ called on c
__del__ called on d
>>> test3(1)
leaving test3 via a return
__del__ called on c
__del__ called on d
>>> try:
... test3(2)
... except RuntimeError:
... pass
...
leaving test3 via an exception
__del__ called on c
__del__ called on d

Here, with the addition of three lines of code, we’re able to ensure that our cleanup method is called when our function is left, which in this case can be via an exception, a return statement, or returning after its last statement.

15.12. Multiple inheritance

Compiled languages place severe restrictions on the use of multiple inheritance, the ability of objects to inherit data and behavior from more than one parent class. For example, the rules for using multiple inheritance in C++ are so complex that many people avoid using it. In Java, multiple inheritance is completely disallowed, although Java does have the interface mechanism.

Python places no such restrictions on multiple inheritance. A class can inherit from any number of parent classes, in the same way it can inherit from a single parent class. In the simplest case, none of the involved classes, including those inherited indirectly through a parent class, contains instance variables or methods of the same name. In such a case, the inheriting class behaves like a synthesis of its own definitions and all of its ancestor’s definitions. For example, if class A inherits from classes B, C, and D, class B inherits from classes E and F, and class D inherits from class G (see figure 15.3), and none of these classes share method names, then an instance of class A can be used as if it were an instance of any of the classes BG, as well as A; an instance of class B can be used as if it were an instance of class E or F, as well as class B; and an instance of class D can be used as if it were an instance of class G, as well as class D. In terms of code, the class definitions y look like this:

class E:
. . .
class F:
. . .
class G:
. . .
class D(G):
. . .
class C:
. . .
class B(E, F):
. . .
class A(B, C, D):
. . .
Figure 15.3. Inheritance hierarchy

The situation is more complex when some of the classes share method names, because Python must then decide which of the identical names is the correct one. For example, assume we wish to resolve a method invocation a.f(), on an instance a of class A, where f isn’t defined in A but is defined in all of F, C, and G. Which of the various methods will be invoked?

The answer lies in the order in which Python searches base classes when looking for a method not defined in the original class on which the method was invoked. In the simplest cases, Python looks through the base classes of the original class in left-to-right order but always looks through all of the ancestor classes of a base class before looking in the next base class. In attempting to execute a.f(), the search goes something like this:

1.  

Python first looks in the class of the invoking object, class A.

2.  

Because A doesn’t define a method f, Python starts looking in the base classes of A. The first base class of A is B, so Python starts looking in B.

3.  

Because B doesn’t define a method f, Python continues its search of B by looking in the base classes of B. It starts by looking in the first base class of B, class E.

4.  

E doesn’t define a method f and also has no base classes, so there is no more searching to be done in E. Python goes back to class B and looks in the next base class of B, class F.

Class F does contain a method f, and because it was the first method found with the given name, it’s the method used. The methods called f in classes C and G are ignored.

Of course, using internal logic like this isn’t likely to lead to the most readable or maintainable of programs. And with more complex hierarchies, other factors come into play to make sure that no class is searched twice and to support cooperative calls to super.

But this is probably a more complex hierarchy than you’d expect to see in practice. If you stick to the more standard uses of multiple inheritance, as in the creation of mixin or addin classes, you can easily keep things readable and avoid name clashes.

Some people have a strong conviction that multiple inheritance is a bad thing. It can certainly be misused, and nothing in Python forces you to use it. After being involved with a number of object-oriented project developments in industry since starting with one of the first versions of C++ in 1987, I’ve concluded that one of the biggest dangers seems to be creating inheritance hierarchies that are too deep. Multiple inheritance can at times be used to help keep this from happening. That issue is beyond the scope of this book. The example we use here only illustrates how multiple inheritance works in Python and doesn’t attempt to explain the use cases—for example, as in mixin or addin classes—for it.

15.13. Summary

This chapter has briefly presented the basics of Python object-oriented programming in a way that will make it easy for any reader familiar with OO to instantly use these features of Python. We made no attempt to make this an introduction to OOP. If you need to learn the basic concepts, refer to OOP books on the market.

As you become more experienced with Python, you’ll find you can also do deeper things than have been described here. In addition to the features described in this chapter, an aspiring Python OO programmer may also wish to use the operator-overloading features provided by special method attributes, which are described in chapter 20.

Classes and objects can be particularly useful in dealing with GUI interfaces, as you’ll see in the next chapter, which explores creating cross-platform GUI applications with Python.