So far, we’ve concentrated on the OOP tool in Python—the class. But OOP is also about design issues—how to use classes to model useful objects. In this section, we’re going to touch on a few OOP core ideas and look at some examples that are more realistic than the ones we’ve seen so far. Most of the design terms we throw out here require more explanation than we can provide; if this section sparks your curiosity, we suggest exploring a text on OOP design or design patterns as a next step.
Python’s implementation of OOP can be summarized by three ideas:
By now, you should have a good feel for what inheritance is all about in Python. Python’s flavor of polymorphism flows from its lack of type declarations. Because attributes are always resolved at runtime, objects that implement the same interfaces are interchangeable; clients don’t need to know what sort of object is implementing a method they call.[47] Encapsulation means packaging in Python, not privacy; privacy is an option, as we’ll see later in this chapter.
We’ve talked about the mechanics of inheritance in depth already, but we’d like to show you an example of how it can be used to model real-world relationships. From a programmer’s point of view, inheritance is kicked off by attribute qualifications and searches for a name in an instance, its class, and then its superclasses. From a designer’s point of view, inheritance is a way to specify set membership. A class defines a set of properties that may be inherited by more specific sets (i.e., subclasses).
To illustrate, let’s put that pizza-making robot we talked about at the start of the chapter to work. Suppose we’ve decided to explore alternative career paths and open a pizza restaurant. One of the first things we’ll need to do is hire employees to service customers, make the pizza, and so on. Being engineers at heart, we’ve also decided to build a robot to make the pizzas; but being politically and cybernetically correct, we’ve also decided to make our robot a full-fledged employee, with a salary.
Our pizza shop team can be defined by the following classes in the
example file employees.py
. It defines four
classes and some self-test code. The most general class,
Employee
, provides common behavior such as bumping
up salaries (giveRaise
) and printing (
__ repr
__
). There
are two kinds of employees, and so two subclasses of
Employee--Chef
and Server
. Both
override the inherited work
method to print more
specific messages. Finally, our pizza robot is modeled by an even
more specific class: PizzaRobot
is a kind of
Chef
, which is a kind of
Employee
. In OOP terms, we call these
relationships “is-a” links: a robot is a chef, which is
a(n) employee.
class Employee: def __init__(self, name, salary=0): self.name = name self.salary = salary def giveRaise(self, percent): self.salary = self.salary + (self.salary * percent) def work(self): print self.name, "does stuff" def __repr__(self): return "<Employee: name=%s, salary=%s>" % (self.name, self.salary) class Chef(Employee): def __init__(self, name): Employee.__init__(self, name, 50000) def work(self): print self.name, "makes food" class Server(Employee): def __init__(self, name): Employee.__init__(self, name, 40000) def work(self): print self.name, "interfaces with customer" class PizzaRobot(Chef): def __init__(self, name): Chef.__init__(self, name) def work(self): print self.name, "makes pizza" if __name__ == "__main__": bob = PizzaRobot('bob') # make a robot named bob print bob # runs inherited __repr__ bob.giveRaise(0.20) # give bob a 20% raise print bob; print for klass in Employee, Chef, Server, PizzaRobot: obj = klass(klass.__name__) obj.work()
When we run this module’s self-test code, we create a
pizza-making robot named bob
, which inherits names
from three classes: PizzaRobot
,
Chef
, and Employee
. For
instance, printing bob
runs the Employee.
__ repr
__
method,
and giving bob
a raise invokes
Employee.giveRaise
, because that’s where
inheritance finds it.
C:pythonexamples> python employees.py
<Employee: name=bob, salary=50000>
<Employee: name=bob, salary=60000.0>
Employee does stuff
Chef makes food
Server interfaces with customer
PizzaRobot makes pizza
In a class hierarchy like this, you can usually make instances of any
of the classes, not just the ones at the bottom. For instance, the
for
loop in this module’s self-test code
creates instances of all four classes; each responds differently when
asked to work, because the work
method is
different in each. Really, these classes just simulate real world
objects; work
prints a message for the time being,
but could be expanded to really work later.
We introduced the notion of composition at the start of this chapter. From a programmer’s point of view, composition involves embedding other objects in a container object and activating them to implement container methods. To a designer, composition is another way to represent relationships in a problem domain. But rather than set membership, composition has to do with components—parts of a whole. Composition also reflects the relationships between parts; it’s usually called a “has-a” relationship, when OOP people speak of such things.
Now that we’ve implemented our employees, let’s throw them in the pizza shop and let them get busy. Our pizza shop is a composite object; it has an oven, and employees like servers and chefs. When a customer enters and places an order, the components of the shop spring into action—the server takes an order, the chef makes the pizza, and so on. The following example simulates all the objects and relationships in this scenario:
from employees import PizzaRobot, Server class Customer: def __init__(self, name): self.name = name def order(self, server): print self.name, "orders from", server def pay(self, server): print self.name, "pays for item to", server class Oven: def bake(self): print "oven bakes" class PizzaShop: def __init__(self): self.server = Server('Pat') # embed other objects self.chef = PizzaRobot('Bob') # a robot named bob self.oven = Oven() def order(self, name): customer = Customer(name) # activate other objects customer.order(self.server) # customer orders from server self.chef.work() self.oven.bake() customer.pay(self.server) if __name__ == "__main__": scene = PizzaShop() # make the composite scene.order('Homer') # simulate Homer's order print '...' scene.order('Shaggy') # simulate Shaggy's order
The PizzaShop
class is a container and controller;
its constructor makes and embeds instances of the employee classes we
wrote in the last section, as well as an Oven
class defined here. When this module’s self-test code calls the
PizzaShop
order
method, the
embedded objects are asked to carry out their actions in turn. Notice
that we make a new Customer
object for each order,
and pass on the embedded Server
object to
Customer
methods; customers come and go, but the
server is part of the pizza shop composite. Also notice that
employees are still involved in an inheritance relationship;
composition and inheritance are complementary tools:
C:pythonexamples> python pizzashop.py
Homer orders from <Employee: name=Pat, salary=40000>
Bob makes pizza
oven bakes
Homer pays for item to <Employee: name=Pat, salary=40000>
...
Shaggy orders from <Employee: name=Pat, salary=40000>
Bob makes pizza
oven bakes
Shaggy pays for item to <Employee: name=Pat, salary=40000>
When we run this module, our pizza shop handles two orders—one from Homer, and then one from Shaggy. Again, this is mostly just a toy simulation; a real pizza shop would have more parts, and there’s no real pizza to be had here. But the objects and interactions are representative of composites at work. As a rule of thumb, classes can represent just about any objects and relationships you can express in a sentence; just replace nouns with classes and verbs with methods, and you have a first cut at a design.
Object-oriented
programmers often talk about something called
delegation
too, which usually implies controller objects that embed other
objects, to which they pass off operation requests. The controllers
can take care of administrative activities such as keeping track of
accesses and so on. In Python, delegation is often implemented with
the
__ getattr
__
method hook; because it intercepts accesses to nonexistent
attributes, a wrapper class can use
__getattr
__
to route arbitrary accesses
to a wrapped object. For instance:
class wrapper: def __init__(self, object): self.wrapped = object # save object def __getattr__(self, attrname): print 'Trace:', attrname # trace fetch return getattr(self.wrapped, attrname) # delegate fetch
You can use this module’s wrapper
class to
control any object with attributes—lists, dictionaries, and
even classes and instances. Here, the class simply prints a trace
message on each attribute access:
>>>from trace import wrapper
>>>x = wrapper([1,2,3])
# wrap a list >>>x.append(4)
# delegate to list method Trace: append >>>x.wrapped
# print my member [1, 2, 3, 4] >>>x = wrapper({"a": 1, "b": 2})
# wrap a dictionary >>>x.keys()
# delegate to dictionary method Trace: keys ['a', 'b']
Classes are also commonly used to extend the functionality of Python’s built-in types, to support more exotic data structures. For instance, to add queue insert and delete methods to lists, you can code classes that wrap (embed) a list object, and export insert and delete methods that process the list.
Remember those set functions we wrote in Chapter 4? Here’s what they look like brought back to life as a Python class. The following example implements a new set object type, by moving some of the set functions we saw earlier in the book to methods, and adding some basic operator overloading. For the most part, this class just wraps a Python list with extra set operations, but because it’s a class, it also supports multiple instances and customization by inheritance in subclasses.
class Set: def __init__(self, value = []): # constructor self.data = [] # manages a list self.concat(value) def intersect(self, other): # other is any sequence res = [] # self is the subject for x in self.data: if x in other: # pick common items res.append(x) return Set(res) # return a new Set def union(self, other): # other is any sequence res = self.data[:] # copy of my list for x in other: # add items in other if not x in res: res.append(x) return Set(res) def concat(self, value): # value: list, Set... for x in value: # removes duplicates if not x in self.data: self.data.append(x) def __len__(self): return len(self.data) # on len(self) def __getitem__(self, key): return self.data[key] # on self[i] def __and__(self, other): return self.intersect(other) # on self & other def __or__(self, other): return self.union(other) # on self | other def __repr__(self): return 'Set:' + `self.data` # on print
By overloading indexing, our set class can often masquerade as a real list. Since we’re going to ask you to interact with and extend this class in an exercise at the end of this chapter, we won’t say much more about this code until Appendix C.
When we discussed details of the class
statement,
we mentioned that more than one superclass can be listed in
parentheses in the header line. When you do this, you use something
called
multiple inheritance; the class and its
instances inherit names from all listed superclasses. When searching
for an attribute, Python searches superclasses in the class header
from left to right until a match is found. Technically, the search
proceeds depth-first, and then left to right, since any of the
superclasses may have superclasses of its own.
In theory, multiple inheritance is good for modeling objects which belong to more than one set. For instance, a person may be an engineer, a writer, a musician, and so on, and inherit properties from all such sets. In practice, though, multiple inheritance is an advanced tool and can become complicated if used too much; we’ll revisit this as a gotcha at the end of the chapter. But like everything else in programming, it’s a useful tool when applied well.
One of the most common ways multiple inheritance is used is to “mix in” general-purpose methods from superclasses. Such superclasses are usually called mixin classes; they provide methods you add to application classes by inheritance. For instance, Python’s default way to print a class instance object isn’t incredibly useful:
>>>class Spam:
...def __init__(self):
# no __repr__ ...self.data1 = "food"
... >>>X = Spam()
>>>print X
# default format: class, address <Spam instance at 87f1b0>
As seen in the previous section on operator overloading, you can
provide a
__ repr
_
_
method to implement a custom string
representation of your own. But rather than code a
__ repr
__
in each
and every class you wish to print, why not code it once in a
general-purpose tool class, and inherit it in all classes?
That’s what
mixins are
for. The following code defines a mixin class called
Lister
that overloads the
_
_ repr
__
method for each
class that includes Lister
in its header line. It
simply scans the instance’s attribute dictionary (remember,
it’s exported in
__ dict
__
) to build up a string showing the
names and values of all instance attributes. Since classes are
objects, Lister
’s formatting logic can be
used for instances of any subclass; it’s a generic tool.
Lister
uses two special tricks to extract the
instance’s classname and address. Instances have a built-in
__ class
__
attribute that references the class the instance was
created from, and classes have a
__name
__
that is the name in the
header, so self.
__ class
_
_ .
__ name
__
fetches the name of an instance’s class. You get the
instance’s memory address by calling the built-in
id
function, which returns any object’s
address:
# Lister can be mixed-in to any class, to # provide a formatted print of instances # via inheritance of __repr__ coded here; # self is the instance of the lowest class; class Lister: def __repr__(self): return ("<Instance of %s, address %s: %s>" % (self.__class__.__name__, # my class's name id(self), # my address self.attrnames()) ) # name=value list def attrnames(self): result = '' for attr in self.__dict__.keys(): # scan instance namespace dict if attr[:2] == '__': result = result + " name %s=<built-in> " % attr else: result = result + " name %s=%s " % (attr, self.__dict__[attr]) return result
Now, the Lister
class is useful for any class you
write—even classes that already have a superclass. This is
where multiple inheritance comes in handy: by adding
Lister
to the list of superclasses in a class
header, you get its
__ repr
_
_
for free, while still inheriting from the
existing superclass:
from mytools import Lister # get tool class class Super: def __init__(self): # superclass __init__ self.data1 = "spam" class Sub(Super, Lister): # mix-in a __repr__ def __init__(self): # Lister has access to self Super.__init__(self) self.data2 = "eggs" # more instance attrs self.data3 = 42 if __name__ == "__main__": X = Sub() print X # mixed-in repr
Here, Sub
inherits names from both
Super
and Lister
; it’s a
composite of its own names and names in both its superclasses. When
you make a Sub
instance and print it, you get the
custom representation mixed in from Lister
:
C:pythonexamples> python testmixin.py
<Instance of Sub, address 7833392:
name data3=42
name data2=eggs
name data1=spam
>
Lister
works in any class it’s mixed into,
because self
refers to an instance of the subclass
that pulls Lister
in, whatever that may be. If you
later decide to extend Lister
’s
__ repr
__
to also
print class attributes an instance inherits, you’re safe;
because it’s an inherited method, changing
Lister
’s
__repr
__
updates each subclass that
mixes it in.[48] In some
sense, mixin classes are the class equivalent of modules. Here is
Lister
working in single-inheritance mode, on a
different class’s instances; like we said, OOP is about code
reuse:
>>>from mytools import Lister
>>>class x(Lister):
...pass
... >>>t = x()
>>>t.a = 1; t.b = 2; t.c = 3
>>>t
<Instance of x, address 7797696: name b=2 name a=1 name c=3 >
Because
classes
are objects, it’s easy to pass them around a program, store
them in data structures, and so on. You can also pass classes to
functions that generate arbitrary kinds of
objects;
such functions are sometimes called factories in
OOP design circles. They are a major undertaking in a strongly typed
language such as C++, but almost trivial in Python: the
apply
function we met in Chapter 4 can call any class with any argument in one
step, to generate any sort of instance:[49]
def factory(aClass, *args): # varargs tuple return apply(aClass, args) # call aClass class Spam: def doit(self, message): print message class Person: def __init__(self, name, job): self.name = name self.job = job object1 = factory(Spam) # make a Spam object2 = factory(Person, "Guido", "guru") # make a Person
In this code, we define an object generator function, called
factory
. It expects to be passed a class object
(any class will do), along with one or more arguments for the
class’s constructor. The function uses apply
to call the function and return an instance. The rest of the example
simply defines two classes and generates instances of both by passing
them to the factory
function. And that’s the
only factory
function you ever need write in
Python; it works for any class and any constructor arguments. The
only possible improvement worth noting: to support keyword arguments
in constructor calls, the factory can collect them with a
**args
argument and pass them as a third argument
to apply
:
def factory(aClass, *args, **kwargs): # +kwargs dict return apply(aClass, args, kwargs) # call aClass
By now, you should know that everything is an “object” in Python; even things like classes, which are just compiler input in languages like C++. However, only objects derived from classes are OOP objects in Python; you can’t do inheritance with nonclass-based objects such as lists and numbers, unless you wrap them in classes.
Speaking of objects, it turns out that methods are a kind of object too, much like functions. Because class methods can be accessed from either an instance or a class, they actually come in two flavors in Python:
Accessing a class’s function attribute by qualifying a class returns an unbound method object. To call it, you must provide an instance object explicitly as its first argument.
Accessing a class’s function attribute by qualifying an instance returns a bound method object. Python automatically packages the instance with the function in the bound method object, so we don’t need to pass an instance to call the method.
Both kinds of methods are full-fledged objects; they can be passed
around, stored in lists, and so on. Both also require an instance in
their first argument when run (i.e., a value for
self
), but Python provides one for you
automatically when calling a bound method through an instance. For
example, suppose we define the following class:
class Spam: def doit(self, message): print message
Now, we can make an instance, and fetch a bound method without
actually calling it. An object.name
qualification
is an object expression; here, it returns a bound method object that
packages the instance (object1
) with the method
function (Spam.doit
). We can assign the bound
method to another name and call it as though it were a simple
function:
object1 = Spam() x = object1.doit # bound method object x('hello world') # instance is implied
On the other hand, if we qualify the class to get to
doit
, we get back an unbound method object, which
is simply a reference to the function object. To call this type of
method, pass in an instance in the leftmost argument:
t = Spam.doit # unbound method object t(object1, 'howdy') # pass in instance
Most of the time, you call methods immediately after fetching them
with qualification (e.g., self.attr(args)
), so you
don’t always notice the method object along the way. But if you
start writing code that calls objects generically, you need to be
careful to treat unbound methods specially; they require an explicit
object.
[47] Some OOP
languages also define polymorphism to mean overloading functions
based on the type signatures of their arguments. Since there is no
type declaration in Python, the concept doesn’t really apply,
but type-base selections can be always be coded using
if
tests and type(X)
built-in
functions (e.g., if type(X) is type(0):doIntegerCase()
).
[48] For the curious reader, classes also
have a built-in attribute called __bases
__ ,
which is a tuple of the class’s superclass objects. A
general-purpose class hierarchy lister or browser can traverse from
an instance’s __class
__ to its class, and
then from the class’s __bases
__ to all
superclasses recursively. We’ll revisit this idea in an
exercise, but see other books or Python’s manuals for more
details on special object attributes.
[49] Actually,
apply
can call any callable
object; that includes functions, classes, and methods. The
factory
function here can run any callable, not
just a class (despite the argument name).