Python
 8.1  Intro to Classes  

See Introductory classes, and Advanced classes.

See also Homework 11: Classes, which implements a fractions class.


Classes: The precursor of a class is the C struct and the Cobol record as a way to hold heterogeneous (not all the same type) data items. C can keep homogeneous data in an array. Actual classes include functions to create the class and to process the data. Here are stack classes in C, C++, and Java: stacks.

In Python, you can often use a tuple or a list for simple places where a class would be needed. A tuple where the structure is created but not changed, and a list otherwise. Early versions of the Python implementation were often written this way (in Python), and the implementers later added the feature named tuples to improve readability of these features.

If using tuples or lists doesn't suffice, here is the "poor man's" method and rules for creating Python classes:

  • Start with the __init__(self,...) constructor.
  • Initialize variables that are passed to the constructor as parameters.
  • Initialize other desired variables in the same constructor.
  • Put "self" into each function in the class as a new first parameter.
  • Add "self." to the start of each variable in the class.
  • Inside the class, call a member function (say, area()) with self.area()
  • From outside the class, call a member function (say, area() again) with instance.area()
  • The identifier self is only known inside functions that are inside a class, and nowhere else.

Um, there's a bit more to it than that.


Simple Python Examples: A very simple progression of examples is below. Example 5 illustrates a class variable and Example 6 a private variable.

Circle Class (various forms)
1
# circle1.py:
class Circle:
    def __init__(self):
        self.radius = 1

c = Circle()
print "radius:", c.radius
% python circle1.py
radius: 1
2
# circle2.py:
class Circle:
    def __init__(xkcd): # should use "self"
        xkcd.radius = 1

c = Circle()
print "radius:", c.radius
% python circle2.py
radius: 1
3
# circle3.py:
class Circle:
    def __init__(self):
        self.radius = 1
    def area(self):
        return self.radius * self.radius * 3.14159

c = Circle()
print "c.radius:", c.radius
print "c.area():", c.area()
c.radius = 3  # can change radius from outside
print "c.radius:", c.radius
print "c.area():", c.area()
% python circle3.py
c.radius: 1
c.area(): 3.14159
c.radius: 3
c.area(): 28.27431
4
# circle4.py:
class Circle:
    def __init__(self, radius):
        self.radius = radius
    def area(self):
        return self.radius * self.radius * 3.14159

c = Circle(1)
print "c.radius:", c.radius
print "c.area():", c.area()

c2 = Circle(3) # second circle object, diff radius
print "c2.radius:", c2.radius
print "c2.area():", c2.area()
% python circle4.py
c.radius: 1
c.area(): 3.14159

c2.radius: 3
c2.area(): 28.27431
Circle with a class variable: pi
5
# circle5.py:
class Circle:
    pi = 3.14159 # class variable
    def __init__(self, radius):
        self.radius = radius
    def area(self):
        return self.radius * self.radius * Circle.pi

print "Circle.pi", Circle.pi
c = Circle(1)
print "c.radius:", c.radius
print "c.area():", c.area()

c2 = Circle(3) # second circle object, diff radius
print "c2.radius:", c2.radius
print "c2.area():", c2.area()

Circle.pi = 4 # can change pi from outside
print "Circle.pi", Circle.pi
print "c.radius:", c.radius
print "c.area():", c.area()

print "c2.radius:", c2.radius
print "c2.area():", c2.area()
% python circle5.py
Circle.pi 3.14159
c.radius: 1
c.area(): 3.14159

c2.radius: 3
c2.area(): 28.27431

Circle.pi 4

c.radius: 1
c.area(): 4

c2.radius: 3
c2.area(): 36
Circle with two private instance variables: pi and radius
6
# circle6.py:
class Circle:
    def __init__(self, radius):
        self.__radius = radius # private variable
        self.__pi = 3.14159 # private variable
    def area(self):
        return self.__radius * self.__radius * self.__pi

c = Circle(1)
print "c.area():", c.area()

c2 = Circle(3)
print "c2.area():", c2.area()

print "c.__radius:", c.__radius # error
% python circle6.py
c.area(): 3.14159
c2.area(): 28.27431

c.__radius:
Traceback (most recent call last):
  File "circle.py", line 13, in 
    print "c.__radius", c.__radius
AttributeError: Circle instance has
    no attribute '__radius'


Types of variables and where they can be used: So far we've seen several types of variables associated with a class:

  • Instance variables: These are variables associated with a specific instance of a class. In the first 5 examples above, radius is an example of an instance variable. Inside an instance of Circle, it must be referred to by self.radius. Outside any reference must have the form instance.radius. For example, given c = Circle(3), we must write c.radius outside Circle.

    Inside a class like Circle, an instance variable like radius can only appear in the form self.radius and can only appear inside a function that has self as its first parameter.

  • Class variables: These belong to the class, not to an instance of a class. These must created by an assignment in the class body, and not in the __init__ function. (See pi in example 5 above.)

  • Private Instance variables: These must start with "__", but otherwise like instance variables. They cannot be accessed or changed from outside the class. (See __pi and __radius in example 6 above.)

Class Variable pi
# circle_test.py
class Circle:
    pi = 3.14159 # class variable
    def __init__(self, radius):
        # pi = 3.14159 # pi CANNOT be here
        self.radius = radius
    # pi = 3.14159 could be here instead
    def area(self):
        # pi = 3.14159 # pi CANNOT be here
        return self.radius * self.radius * Circle.pi
    # pi = 3.14159 could be here instead

print "Circle.pi:", Circle.pi
Circle.pi = 4
print "Circle.pi:", Circle.pi
c = Circle(3)
print "c.radius:", c.radius
print "c.area():", c.area()
Circle.pi = 3.14
print "Circle.pi:", Circle.pi
print "c.area():", c.area()

c2 = Circle(1)
print "Circle.pi:", Circle.pi
print "c2.area():", c2.area()
% python circle1.py
Circle.pi: 3.14159
Circle.pi: 4
c.radius: 3
c.area(): 36
Circle.pi: 3.14
c.area(): 28.26

Circle.pi: 3.14
c2.area(): 3.14

Instance Variable pi
# circle_test2.py
class Circle:
    def __init__(self, radius):
        self.radius = radius
    def area(self):
        self.pi = 3.14159
        return self.radius * self.radius * self.pi
    # self.pi = 3.14159 # name 'self' is not defined

c = Circle(4)
print "c.radius", c.radius
# print "c.pi:", c.pi # ERROR, c has no attribute 'pi'
print "c.area():", c.area()
c.radius = 5
print "c.radius", c.radius
print "c.area():", c.area()
% python circle2.py
c.radius 4
c.area(): 50.26544
c.radius 5
c.area(): 78.53975

Instance Variable pi
# circle_test2.py
class Circle:
    def __init__(self, radius):
        self.pi = 3.14159 # This works!
        self.radius = radius
    def area(self):
        return self.radius * self.radius * self.pi

c = Circle(4)
print "c.radius", c.radius
print "c.pi:", c.pi # This works!
print "c.area():", c.area()
c.radius = 5
print "c.radius", c.radius
print "c.area():", c.area()
% python circle2.py
c.radius 4
c.pi: 3.14159   <--This works!
c.area(): 50.26544
c.radius 5
c.area(): 78.53975


A Python Stack

This example came from Python Data Structures, a good online source of more advanced Python programs.

Stack Class Test the Stack Output
# stack.py:
class Stack:
    def __init__(self):
        self.items = []

    def isEmpty(self):
        return self.items == []

    def push(self, item):
        self.items.append(item)

    def pop(self): # pop and return entry
        return self.items.pop()

    def peek(self):
        return self.items[len(self.items)-1]

    def size(self):
        return len(self.items)
# stack_test.py:
import stack
import sys

s = stack.Stack() # holds 100 ints: 1 to 99
for i in range(0,100):
    s.push(i)
sys.stdout.write(str(s.size()) + "\n")
sys.stdout.write(str(s.peek()) + "\n")
# below, directly access items list
sys.stdout.write(str(s.items[50]) + "\n\n")
t = stack.Stack() # will hold 4 items
t.push('a')
t.push(7)
t.push([1, 2])
t.push(3.14)
for i in range(0, t.size()): # pop, write
    sys.stdout.write(str(t.pop()) + "\n")
sys.stdout.write("\n")

sys.stdout.write(str(t.size()) + "\n")
sys.stdout.write(str(t.isEmpty()) + "\n")
% python stack_test.py
100  # size of stack
99   # top of stack
50   # some stack item

3.14
[1, 2]
7
a

0    # empty, size=0
True # isEmpty()

Next, I'm going to make a few changes, shown in red below:

Stack Class Test the Stack Output
# stack.py:
class Stack:
    def __init__(self):
        self.__items = []

    def isEmpty(self):
        return self.__items == []

    def push(self, item):
        self.__items.append(item)

    def pop(self): # pop, return
        if self.isEmpty():
            return None
        return self.__items.pop()

    def peek(self, loc = -1):
        lim = self.size() # range
        if -lim <= loc < lim:
            return self.__items[loc]
        return None

    def size(self):
        return len(self.__items)
# stack_test.py:
import stack
import sys

s = stack.Stack() # holds 100 ints: 1 to 99
for i in range(0,100):
    s.push(i)
sys.stdout.write(str(s.size()) + "\n")
sys.stdout.write(str(s.peek()) + "\n")
sys.stdout.write(str(s.peek(50)) + "\n")
# next 2 barely in range
sys.stdout.write(str(s.peek(99)) + "\n")
sys.stdout.write(str(s.peek(-100)) + "\n")
# next 2 barely out of range
sys.stdout.write(str(s.peek(100)) + "\n")
sys.stdout.write(str(s.peek(-101)) + "\n")

t = stack.Stack() # will hold 4 items
t.push('a')
t.push(7)
t.push([1, 2])
t.push(3.14)
for i in range(0, t.size()): # pop, write
    sys.stdout.write(str(t.pop()) + "\n")
# pop empty stack
sys.stdout.write(str(t.pop()) + "\n\n")

sys.stdout.write(str(t.size()) + "\n")
sys.stdout.write(str(t.isEmpty()) + "\n\n")

# Error below: can't access '__items'
sys.stdout.write(str(s.__items[50]) + "\n")
% python stack_test.py
100  # size of stack
99   # top of stack
50   # some stack item
99   # index upper limit
0    # index lower limit
None # barely too large
None # barely too neg
3.14
[1, 2]
7
a
None # pop empty stack

0    # empty, size=0
True # isEmpty()

Traceback (most recent
    call last):
  File "stack_test.py",
    line 24, in 
    sys.stdout.write(...
AttributeError: Stack
  instance has no
  attribute '__items'

 

Inheritance: [Quote for Beazley] "Inheritance is a mechanism for creating a new class that specializes or modifies the behavior of an existing class. The original class is called a base class or a superclass. The new class is called a derived class or a subclass. When a class is created via inheritance. it 'inherits' the attributes defined by its base classes. However, a derived class may redefine any of these attributes and add new attributes of its own."

Shape base class, with subclasses Square and Circle
# shape.py:
import sys

class Shape(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __str__(self):
        return self.whoami() + \
          ': x = ' + str(self.x) + \
          ', y = ' + str(self.y)
    def limit(self, lim):
        if -lim <= self.x <= lim and \
           -lim <= self.y <= lim:
            return True
        return False
    def whoami(self): # get name of class
        return type(self).__name__

# 'Square' inherits from 'Shape'
class Square(Shape): 
    def __init__(self, side=1, x=0, y=0):
        # call __init__ method of 'Shape'
        super(Square, self).__init__(x, y)
        self.side = side
    def __str__(self):
        lim = self.limit(3)
        return super(Square, self).__str__() + \
          ", side = " + str(self.side) + \
          ", area = "   + str(self.area()) + \
          ", lim = "  + str(lim)
    def area(self):
        return self.side ** 2

# 'Circle' inherits from 'Shape'
class Circle(Shape):
    pi = 3.14159 # class variable
    def __init__(self, r=1, x=0, y=0):
        # call __init__ method of 'Shape'
        super(Circle, self).__init__(x, y)
        self.radius = r
    def __str__(self):
        lim = self.limit(4)
        return super(Circle, self).__str__() + \
          ", radius = " + str(self.radius) + \
          ", area = "   + str(self.area()) + \
          ", lim = "    + str(lim)
    def area(self):
        return self.radius ** 2 * self.pi
    # redefine limit method
    def limit(self, lim):
        if 0 <= self.x <= lim and \
           0 <= self.y <= lim:
            return True
        return False

sh1 = Shape(3,4)
sys.stdout.write("sh1: " + str(sh1) + '\n')
sq1 = Square(2,3,4)
sys.stdout.write("sq1: " + str(sq1) + '\n')
sq2 = Square(2,3,2)
sys.stdout.write("sq2: " + str(sq2) + '\n')
c1 = Circle(5,3,4)
sys.stdout.write("c1:  " + str(c1) + '\n')
c2 = Circle(5,-2,2)
sys.stdout.write("c2:  " + str(c2) + '\n')
Output (slightly edited)
% python shape.py
sh1: Shape:  x = 3, y = 4
sq1: Square: x = 3, y = 4, side = 2,   area = 4,        lim = False
sq2: Square: x = 3, y = 2, side = 2,   area = 4,        lim = True
c1:  Circle: x = 3, y = 4, radius = 5, area = 78.53975, lim = True
c2:  Circle: x =-2, y = 2, radius = 5, area = 78.53975, lim = False

Items of Interest or for study:

  • The base class is Shape.
    It inherits from object.
    It has instance variables x and y.
    It has instance methods __init__ , __str__ , limit, and whoami.
    (x,y) might be the location of the center of the shape.
    limit is present to illustrate use and redefinition of a method in derived classes.
    The method whoami uses a way to get the class name (not the class instance name).

  • The first derived class is Square.
    It has the additional instance variable side.
    It uses super(Square, self).__init__(x, y) to call Shape's initializer.
    Python 3 has a short-hand notation to use just super().__init__(x, y) instead, illegal in Python 2.
    Square uses super(Square, self).__str__() as part of its definition of its own __str__(self).
    Square has the additional instance method area.

  • The other derived class is Circle.
    It has the additional instance variable radius.
    It has the additional class variable pi.
    It has the additional instance method area.
    It redefines the instance method limit.

  • It's interesting that the whoami method of Shape gives the class name of the subclass in which it is called.
    (The self parameter of whoami refers to the class from which it is called.)

  • Notice that the method area is defined differently in the two derived classes.

  • A slight generalization of this example was given at the end of the Fall 2014 CS3723 final exam: Final Answers.

(Revision date: 2014-11-23. Please use ISO 8601, the International Standard.)