In Class Definition: The class and def Statements we show the syntax for creating class definitions; we cover the use of objects in Class Use: Making New Objects. We’ll discuss the notion of attribute or instance variable in The State of Being – Instance Variables. The initial state of an object is set by a special method that we’ll look at in At The Starting Line – Setting The Initial Values. We provide some exercises in Class Definition Exercises.
We define our own class of objects with class statement. Since the class encapsulates instance variables as well as method functions, a class definition can get lengthy.
Here’s the simplest form for a class definition.
class className :
suite of method defs
In Python 3, this will be the norm.
While we’re using Python 2, we’re going to use this slightly more complex version.
class className ( [ superclass ] , ... ) :
suite of method defs
The className is the name of the class. This name will be used to create new objects that are instances of the class. Traditionally, class names are capitalized and class elements (variables and methods) are not capitalized.
We’ll generally provide a superclass of object in Python 2. This provides some benefits that – while important – are also beyond the scope of the book.
The suite of defs is a series of definitions for the method functions of the class. This is indented within the class definition.
The suite of defs can contain any Python programming. Generally, we try to limit our class definition to the following things:
The heart of the class definition is the suite of method function definitions.
def methodName ( self, [ parameter [ = initializer ] ] , ... ):
suite of statements
This definition looks just like a function definition, with two exceptions.
First, it’s indented within the class statement suite.
Second, each of the method functions must have a first positional argument, self, which Python uses to manage the unique object instances. When referring to any method function or instance variable of the class, the instance qualifier self. must be used.
Example Definition. Here’s an example of a class with a single method definition. This class models a real-world object, a die. Note the indentation of the class definition suite. Each method function def begins at one level of indentation; each method function’s suite is at a second level of indentation.
die.py
1 2 3 4 5 6 7 8 9 | #!/usr/bin/env python
import random
class Die( object ):
"""Simulate a 6-sided die."""
def roll( self ):
"""Return a random roll of a die."""
u= random.randrange(6)
self.value= u+1
return self.value
|
The processing steps are silly, but they shows the difference between a local variable, u, that doesn’t live with the object, and an instance variable, self.value, that defines the state of the object.
Tip
Debugging a Class Definition
When we get syntax errors on a class definition, it can be in the class line or one of the internal method function definitions.
If we get a simple SyntaxError on the first line, we have misspelled class, left off a ( or ), or omitted the : that begins the suite of statements that defines the class.
If we get a syntax error further in the class definition, then our method functions aren’t defined correctly. Be sure to indent the def once (so it nests inside the class). Be sure to indent the suite of statements inside the def twice.
When we use the class name as if it were a function evaluation (for example, d= Die()), three things will happen.
Note the similarity between using our class definition as an object factory and the built-in factory functions like int(), float(), bool(), str(), list(), tuple(), set() and dict(). A class name is also the name of an object factory.
Let’s create and interact with two objects of our Die class. First, we’ll execute the class definition module using IDLEs F5 or Run menu, item Run Module.
If we are working from the command line, or using a different tool, we have to import our definitions, using from die import *.
1 2 3 4 5 6 7 8 9 10 | >>> from __future__ import print_function
>>> from die import *
>>> d1= Die()
>>> d2= Die()
>>> print(d1.roll(), d2.roll())
1 3
>>> print(d1.value, d2.value)
1 3
>>> print(d1, d2)
<die.Die object at 0x7fd30> <die.Die object at 0x7fd50>
|
We evaluate the roll() method of d1; we also evaluate the roll() method of d2. Each of these calls sets an object’s value variable to a unique, random number. There’s a pretty good chance (1 in 6) that both values might happen to be the same. If they are, simply evaluate d1.roll() and d2.roll() again to get new values.
We print the value variable of each object. The results aren’t too surprising, since the value attribute was set by the roll() method. This attribute will be changed the next time we evaluate the roll() method.
Note that we used the class definition to make two objects, d1, and d2. The objects are the focus of our program. We have manipulators (like the roll() method) and accessors (the value attribute) for these objects.
Tip
Debugging Object Construction
Assuming we’ve defined a class correctly, there are a three of things that can go wrong when attempting to construct an object of that class.
If we get a NameError: name 'Hack' is not defined, then the class (Hack, in this example) is not actually defined. This could mean one of three things: our class definition had errors in the first place, our definition class name isn’t spelled the same as our object creation (either we spelled it wrong when defining the class, or spelled it wrong when using the class to create an object.) The third possible error is that we have defined the class in a module, imported it, but forgot to quality the class name with the module name.
If our class wasn’t defined, it means we either forgot to define the class, or overlooked the SyntaxError when defining it. If our class has one name and our object constructor has another name, that’s just carelessness; pick a name and stick to it. If we are trying to import our definitions, we can either qualify the names properly, or use from module import * as the import statement.
Another common problem is using the class name without ()s. If we say d= Die, we’ve assigned the class object (Die) to the variable d. We have to say d= Die() to create an instance of a class.
If we’ve defined our class properly, we can get a message like TypeError: __init__() takes exactly 2 arguments (1 given) when we attempt to construct an object. This means that our __init__() method function doesn’t match the object construction call that we made.
The __init__() function must have a self parameter name, and it must be first. When we construct an object, we don’t provide an argument value for the self parameter, but we must provide values for all of the other parameters after self.
If your initialization function, __init__(), doesn’t seem to work, the most likely cause is that you have misspelled the name. There are two _ before and two _ after the init.
Each ordinary method function definition must have the instance qualifier – traditionally the variable self – as the first positional parameter. This name qualifies the instance variables and the method functions of this object.
Note
Yes, there are exceptions
The exceptions to using the self instance variable are beyond the scope of this book.
We’ll see two kinds of references to variables and functions in the suites of statements in a class.
Names qualified by self. When we say self.name, the variable name is bound to this object. These variables are part of the object, and have the same life as the object. The variable exists after the end of any method function evaluation.
Similarly, the name of a function that is qualified with self refers to a method function that is part of this class definition.
Unqualified names. Names not qualified by self are called free. These variables are ordinary local variables that has a scope that is tied to this execution of the method function. When the function finishes, the variable will be removed.
Similarly, the name of a function that is not qualified refers to a free function that is defined outside this class.
A free variable may also be a reference to a global variable or function, or a builtin function.
In the following example, the method function rollMany() evaluates self.roll(). The self qualifier shows that the roll() function is part of the Dice class.
The method function roll() evaluates random.randrange(). Since this does not use the self qualifier, it is defined outside this class definition.
class Die( object ):
"""Simulate a 6-sided die."""
def roll( self ):
"""Return a random roll of a die."""
u= random.randrange(6)
self.value= u+1
return self.value
def rollMany( self, n ):
all= [ self.roll() for i in range(n) ]
return tuple(all)
We’ve emphasized that the behavior of each object is declared through the method functions of the object’s class. The method functions are a formal contract between the object and its client objects, specifying what the object does.
The attributes, however, do not have formal definitions. Each object’s attributes are implemented through instance variables, which – like all Python variables – are created as needed by an assignment statement. In order to guarantee that all of the instance variables exist during the entire life of the object, it is best to initialize them by providing a method with the special name of __init__(). The __init__() method is always called automatically by Python when the object is created; we can exploit this to assure a correct initialization.
In this example, we updated our Die to add an __init__() function. This function will provide a default value for the self.value attribute.
die.py, version 2
1 2 3 4 5 6 7 8 9 10 | import random
class Die( object ):
"""Simulate a 6-sided die."""
def __init__( self ):
"""Initialize the die."""
self.value= None
def roll( self ):
"""Return a random roll of a die."""
self.value = random.randrange(6) + 1
return self.value
|
Bonus Questions. In the first version of Die, what would happen if we did the following?
dx = Die()
print(dx.value)
dx.roll()
print(dx.value)
Compare this with what happens when we do this with the new version of Die. Which class has better behavior?
Arguments to Control Initialization. Method functions can have parameters. All of the techniques we’ve seen for ordinary function definitions apply to method functions. We can have additional positional parameters after self, keyword parameters, default values, as well as the * and ** collections of additional parameters.
As with all method functions, the __init__() method function can accept parameters. This allows us to correctly initialize an object at the same time we are creating it. The object can begin its life in a specific state. Since we don’t call the __init__() function directly, this raises a question. How are argument values assigned to the parameter variables?
The class name becomes a factory function that makes new instances of the class. When we evaluate the class, using ()s, we can pass argument values to the class factory. The argument values we give to the class factory are given to the __init__() method function.
For any class, C, if we say a= C( some values ), Python acts as though we said
a= C() a.__init__( some values )
Example Class Definition. This next example is a class that defines a geometric point. The class provides some operations that manipulate that point. When we create a Point instance, we’ll provide an x and y coordinate. To define the point (x,y)=(3,2), we could say Point(3,2). This would, in effect, do the following for us p= Point(); p.__init__( 3, 2 ).
class Point( object ):
"""A 2-D geometric point."""
def __init__( self, x, y ):
"""Create a point at (x,y)."""
self.x, self.y = x, y
def offset( self, xo, yo ):
"""Offset the point by xo parallel to the x-axis
and yo parallel to the y-axis."""
self.x += xo
self.y += yo
def offset2( self, val ):
"""Offset the point by val parallel to both axes."""
self.offset( val, val )
Here’s an example of creating a Point at coordinates (2,3) via Point(2,3) and then manipulating that point. First we move it -1 unit on the x axis and 2 units on the y axis. Then we move it -2 on both axis.
>>> from point import Point
>>> p = Point(2,3)
>>> print(p)
<point.Point instance at 0x98d148>
>>> print(p.x, p.y)
2 3
>>> p.offset( -1, 2 )
>>> print(p.x, p.y)
1 5
>>> p.offset2( -2 )
>>> print(p.x, p.y)
-1 3
After using the offset() and offset2() manipulations, the point is now at (-1,3).
Other Special Names. In addition to the specially-named __init__() method, there are many other specially-named methods that are automatically used by Python; these special methods can simplify our programming. After __init__(), the next most important special method function name may be __str__().
The __str__() method is used to return the string representation of the object. For example, we can add this method to our Point class to return an easy-to-read string for a Point.
class Point( object ):
# ...other methods...
def __str__( self ):
return "({0:d},{1:d})".format(self.x, self.y)
Don’t Forget self. Within a class, we must be sure to use self. in front of the function names as well as attribute names. For example, our offset2() function accepts a single value and calls the object’s offset() function using the supplied value for both x and y offsets.
The method functions allow us to access and manipulate the instance variables of an object. The method functions create a formal interface for using the object. We can think of the method functions the way we think of the buttons on the front panel of a microwave oven. We don’t know what goes on inside the oven, but we do know that pushing certain buttons in a certain order will reheat our left-over General Tso’s Chicken.
Let’s look at an example of using our Die class.
>>> d1= Die()
>>> d1.roll()
1
>>> d1.roll()
2
In this case, we created an object, d1, which is defined by the Die class. When we say Die(), we are creating a new object, and implicitly evaluating Die.__init__() to initialize that object.
After creating an instance of Die, we then evaluated the roll() method of that instance. This method updates the instance variables, self.value, with a new random number. It also returns the value of the instance variable.
A method which returns information without changing any of the instance variables is sometimes called an accessor. A method which changes an instance variable is sometimes called a manipulator.
Tip
Debugging Class vs. Object Issues
Perhaps the biggest mistake newbies make is attempting to exercise the method functions of a class instead of a specific object. You can’t easily say Die.roll(), you’ll get the cryptic TypeError: unbound method error message. The phrase “unbound method” means that no instance was being used.
When you say d1= Die(), you are creating an instance. When you see d1.roll(), then you are asking that specific object to do its roll() operation.
The real work of a program occurs when objects collaborate. We define classes that depend on other classes; we create multiple instances of objects; objects evaluate method functions of other objects.
What we do to build up an application is to allocate specific areas of responsibility to different classes. We implement each class so that it handles just its narrow area of specialization. Each class has a formalized contract with other classes, allowing us to develop and debug each class as a separate, smaller and more manageable exercise.
We’ll start out by creating a tuple object that contains five instances of the Die class from the die.py module. We’ll use that tuple object to generate a dozen rolls of these five dice. This is the kind of thing that might form part of a simulation for multi-dice games like Yacht, Kismet, Yatzee, Zilch, Zork, Greed or Ten Thousand.
1 2 3 4 5 6 7 | from __future__ import print_function
import die
my_dice= ( die.Die(), die.Die(), die.Die(), die.Die(), die.Die() )
for i in range(12):
for d in my_dice:
d.roll()
print([d.value for d in my_dice])
|
We imported our die.py module to get the Die class definition.
We created a tuple, my_dice with five distinct objects, each an instance of die.Die.
Within the outer for loop, we used an explicit loop to iterate through the Die instances in the my_dice variable.
We evaluated the the roll() method of each individual die.Die instance.
Dice and Die Collaboration. Here’s a new class, Dice, which uses instances of our Die class. We’ll put this into the die.py file, right behind our Die class definition. This leads to a file that is
die.py, version 3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | import random
class Die( object ):
... already given ...
class Dice( object ):
"Simulate a pair of dice."
def __init__( self ):
"Create the two Die objects."
self.myDice = ( Die(), Die() )
def roll( self ):
"Return a random roll of the dice."
for d in self.myDice:
d.roll()
def getTotal( self ):
"Return the total of two dice."
t= 0
for d in self.myDice:
t += d.value
return t
def getTuple( self ):
"Return a tuple of the dice values."
return tuple( [d.value for d in self.myDice] )
def hardways( self ):
"Return True if this is a hardways roll."
return self.myDice[0].value == self.myDice[1].value
|
A Function Which Uses Die and Dice. The following function exercises an instance of this Dice class to roll two dice a dozen times and print the results.
import die
def test2():
x= die.Dice()
for i in range(12):
x.roll()
print(x.getTotal(), x.getTuple())
This function creates an instance of Dice, called x. It then enters a loop to perform a suite of statements 12 times. The suite of statements first manipulates the Dice object using its roll() method. Then it accesses the Dice object using getTotal() and getTuple() method.
Alternatives. The roll() method could also be written as
def roll( self ):
[ x.roll() for x in self.myDice ]
This will apply the roll() method to each Die in myDice. Interestingly it also creates a list object. Since the roll() function doesn’t return a value, this list object will actually be a sequence of None values. Since it isn’t assigned to a variable, it quietly blinks out of existence and is lost forever. So, each time Dice.roll() is called a little list of Nones is created and removed.
The getTotal() method could also be written as
def getTotal( self ):
"Return the total of two dice."
return sum( d.value for d in self.myDice )
Here are two additional topics: how we’ll organize our class definitions, and how to create an empty class definition.
Class Definitions. In the long run, we’ll put our class definitions in files and import them into our application programs. When doing this in IDLE, we should do the following.
We’ll expand on this technique in Modules : The unit of software packaging and assembly.
Empty Class Definitions, the pass Statement. Note that the suite of defs in a class definition is required. Sometimes, however, we don’t need any definitions; in this case we have to use the pass statement.
When we introduced creation of a specialized exception class in Raising The White Flag in Exceptional Situations, we showed how to use pass as a place-filler for the suite of defs. The pass statement is the “do nothing” place-filler; we use it when the syntax rules required a suite of defs, but we really don’t have anything to add.
Here’s an example exception definition that uses the pass statement. We want our own class of exceptions, but we don’t have any new or different processing, just a new name.
class MyError( Exception ):
pass
Dive Logging and Surface Air Consumption Rate.
The Surface Air Consumption Rate is used by SCUBA divers to predict air used at a particular depth. If we have a sequence of Dive objects with the details of each dive, we can do some simple calculations to get averages and ranges for our air consumption rate.
For each dive, we convert our air consumption at that dive’s depth to a normalized air consumption at the surface. Given depth (in feet), d, starting tank pressure (psi), s, final tank pressure (psi), f, and time (in minutes) of t, the SACR, c, is given by the following formula.

Typically, you will average the SACR over a number of similar dives. You will want to create a Dive class with start pressure, finish pressure, time and depth. Typical values are a starting pressure of 3000, ending pressure of 700 to 1500, depth of 30 to 80 feet and times of 30 minutes (at 80 feet) to 60 minutes (at 30 feet). SACR’s are typically between 10 and 20. Your Dive class should have a function named getSACR() which returns the SACR for that dive.
To make it a little simpler to put the data in, we’ll treat time as string of HH:MM, and use string functions to pick this apart into hours and minutes. We can save this as tuple of two integers, hours and minutes. To compute the duration of a dive, we need to normalize our times to minutes past midnight, by doing hh*60+mm. Once we have our times in minutes past midnight, the difference is number of minutes of duration for the dive. You’ll want to create a function getDuration() to do just this computation for each dive.
Initialize a Dive with the start and finish pressure in PSI, the in and out time as a string, and the depth as an integer. This method should parse both the in string and out string into time tuples of hours and minutes. The parseTime() can be used to do this for both the in time and the out time.
Note that a practical dive log would have additional information like the date, the location, the air and water temperature, sea state, equipment used and other comments on the dive.
Return a nice string representation of the dive information.
Compute the SACR value from the starting pressure, final pressure, time and depth information. The duration can be computed using the getDuration() function.
Pick apart a HH:MM time and convert the strings to integers to produce a 2-tuple of hours and minutes after midnight.
Accepts two 2-tuples of hours and minutes, normalizes these to minutes past midnight, and returns the difference. This is the dive’s duration in minutes.
We’ll want to initialize our dive log as follows:
log = [
Dive( start=3100, finish=1300, in="11:52", out="12:45", depth=35 ),
Dive( start=2700, finish=1000, in="11:16", out="12:06", depth=40 ),
Dive( start=2800, finish=1200, in="11:26", out="12:06", depth=60 ),
Dive( start=2800, finish=1150, in="11:54", out="12:16", depth=95 ),
]
Your application can then process a sequence of Dives, get the SACR for each dive, and compute the average SACR over all the dives in the dive log. Here’s a start on the final program.
total= 0
for d in log:
print(d, d.getSACR())
total += d.getSACR()
print(total, len(log))
Stock Valuation.
A block of shares in a stock has a number of attributes, including a purchase price, purchase date, and number of shares in the block. Commonly, methods are needed to compute the total spent to buy the stock, and the current value of the stock. An investor may have multiple blocks of stock in a company; this collection is called a Position.
Beyond a simple collection of shares are larger groupings. A Portfolio, for example, is a collection of Positions; it has methods to compute the total value of all positions of stock. We’ll look at Position and Portfolio in a subsequent exercise. For now, we’ll just lock at a block of shares.
When we purchase stocks a little at a time, each block of shares has a different price. We want the total value of the entire set of shares, plus the average purchase price for the set of shares as a whole.
First, define a ShareBlock class which has the purchase date, price per share and number of shares.
Populate the individual instance variables with date, price and shares. We’ll define another class with the ticker symbol that can act as a container for the several of these blocks for a particular company.
Return a nice string that shows the date, price and shares.
Computer the purchase value as
the
.
Given a
salePrice, compute the sale value using the sale price
in
.
Given a
salePrice, compute the return on investment
as
.
We can load our database with a piece of code the looks like the following. The first statement will create a sequence with four blocks of stock. We chose variable name that would remind us that the ticker symbols for all four is ‘GM’. The second statement will create another sequence with four blocks.
blockGM = [
ShareBlock( purchDate='25-Jan-2001', purchPrice=44.89, shares=17 ),
ShareBlock( purchDate='25-Apr-2001', purchPrice=46.12, shares=17 ),
ShareBlock( purchDate='25-Jul-2001', purchPrice=52.79, shares=15 ),
ShareBlock( purchDate='25-Oct-2001', purchPrice=37.73, shares=21 ),
]
blockEK = [
ShareBlock( purchDate='25-Jan-2001', purchPrice=35.86, shares=22 ),
ShareBlock( purchDate='25-Apr-2001', purchPrice=37.66, shares=21 ),
ShareBlock( purchDate='25-Jul-2001', purchPrice=38.57, shares=20 ),
ShareBlock( purchDate='25-Oct-2001', purchPrice=27.61, shares=28 ),
]
We can tally the purchase price of a block, for example, as follows:
totalGM= 0
for s in blockGM:
totalGM += s.getPurchValue()
print(totalGM)
Once we have the ShareBlock class working, we can move on to processing the entire position.
Stock Position.
In Stock Valuation, we looked at a block of stock shares. A collection of these blocks represents a position on that stock. We can define an additional class, Position, which will have an the name, symbol and a sequence of ShareBlocks for a given company.
Accept the company name, ticker symbol and a collection of ShareBlock instances.
Return a string that contains the symbol, the total number of shares in all blocks and the total purchase price for all blocks.
Sum the purchase value for each block.
Given a salePrice, sum the sale value for each block.
Given a
salePrice, compute the return on investment
as
.
This is an ROI
based on an overall yield.
We can create our Position objects with the following kind of initializer. This creates a sequence of three individual Position objects; one has a sequence of GM blocks, one has a sequence of EK blocks and the third has a single CAT block.
portfolio= [
Position( "General Motors", "GM", blocksGM ),
Position( "Eastman Kodak", "EK", blocksEK )
Position( "Caterpillar", "CAT",
[ ShareBlock( purchDate='25-Oct-2001',
purchPrice=42.84, shares=18 ) ] )
]
You can now write a main program that writes some simple reports on each Position object in the portfolio, and the overall portfolio. This report should display the individual blocks purchased. This should be followed with a total price paid, and then the overall average price paid (the total paid divided by the total number of shares).
Statistics Library.
We can create a class which holds a sequence of samples. This class can have functions for common statistics on the object’s sequence of samples.
For additional details on these algorithms, see the exercises in Doubles, Triples, Quadruples : The tuple and the exercises in Common List Design Patterns.
Save a sequence of samples in an instance variable. It could, at this time, also precompute a number of useful values, like the sum, count, min and max of this set of data.
Return a summary of the data. An example is a string like "%d values, min %g, max %g, mean %g" with the number of data elements, the minimum, the maximum and the mean.
Return the sum divided by the count.
Return the smallest value in the sequence of data values.
Return largest value in the sequence of data values.
The variance() is a more complex calculation. For each sample, compute the difference between the sample and the mean, square this value, and sum these squares. The number of samples minus 1 is the degrees of freedom. The sum, divided by the degrees of freedom is the variance.
Return the square root of the variance.
The mode() returns the most popular of the sample values. The following algorithm can be used to locate the mode of a set of samples.
Computing the Mode
Initialize. Create an empty dictionary, freq, for frequency distribution.
For Each Value. For each value, v, in the sequence of sample values.
Unknown? If v is not a key in freq, then add v to the dictionary with a value of 0.
Increment Frequency. Increment the frequency count associated with v in the dictionary.
Extract. Extract the dictionary items as a sequence of tuples (value, frequency).
Sort. Sort the sequence of tuples in ascending order by frequency.
Result. The last value in the sorted sequence is a tuple with the most common sample value and the frequency count.