Search
Python Classes and Object-Oriented Programming

A Python class defines a way of organizing code into containers of data, and functions that transform the data in some way. Once a class is instantiated in code, it becomes an object (and is usually assigned to a variable).

Below is a class definition named Shape. It stores the data attribute shape which is just a string. The only member function implemented in this class is the special __str__ function which returns the string I am a shape when called on an object instantiated from the class Shape.

The functions __init__ and __str__ both have special meanings in Python. __init__ is automatically called when an object is instantiated. Any code placed in this function will be automatically run. __str__ is a special function that is called when the print function is called on an object.

class Shape():
    
    def __init__(self):
        self.shape = 'shape'
        
    def __str__(self):
        return "I am a {}.".format(self.shape)

Now we will instantiate the class and store it in an object with the variable name s. Then we call the print statement giving s as and argument and it automatically runs the __str__ function and returns its result.

s = Shape()
print(s)
I am a shape.

Class inheritance

Now we will derive a class from Shape called Polygon. A Polygon is a Shape and therefore all of the functions defined in Shape will work on an instantiated object of Polygon (unless they are explicitly overridden).

We will also implement some new functions, not defined in Shape that can compute the perimeter of a polygon and return the number of edges.

There is also a new data attribute called side_lengths which is initialized to None. We can't set its exact value yet because we don't have enough information about the polygon, e.g. How many sides it has, but we need to define it in order to define compute_perimeter because it requires side_lengths as an argument. Same goes for get_number_of_sides.

Notice that Shape is placed as an argument in the class definition of Polygon, this indicates that Polygon is derived from or inherited from Shape. Also notice that class data attributes always start with self.

class Polygon(Shape):
    
    def __init__(self):
        self.shape = 'polygon'
        self.side_lengths = None
        
    def compute_perimeter(self):
        return sum(self.side_lengths)
    
    def get_number_of_edges(self):
        return len(self.side_lengths)

Now we can instantiate a Polygon object called p and call print on it. It returns the function call from __str__ that is only defined in Shape because a Polygon is a Shape.

p = Polygon()
print(p)
I am a polygon.

We can now derive another class from Polygon, this time we are specialized enough that we know a Rectangle has 4 sides, so we can give a default value for the data attribute side_length.

class Rectangle(Polygon):
    
    def __init__(self):
        self.shape = 'rectangle'
        self.side_lengths = [1, 1, 1, 1]

And now we can call compute_perimeter on the object rect that was instantiated from Rectangle even though we didn't define the function in the Rectangle class. We can do this because a Rectangle is a Polygon.

rect = Rectangle()
rect.compute_perimeter()
4

Likewise, we can derive a Triangle class from Polygon and it inherits the member functions from both Polygon and Shape. Again, a Triangle is a Polygon which is a Shape.

class Triangle(Polygon):
    
    def __init__(self):
        self.shape = 'triangle'
        self.side_lengths = [2, 2, 2] 

Examples of calling functions on the object t instantiated from Triangle.

t = Triangle()
t.compute_perimeter()
6
t.get_number_of_edges()
3
print(t)
I am a triangle.

Everything in Python is an object. Including the lists we've already been using. This might help you understand the syntax of the sort function a little better now.

x = [20, 4, 100]
x.sort()
x
[4, 20, 100]

You can also define classes that take arguments similar to the way regular functions do. Both classes and functions that are members of a class must use self as their first argument. After that you can use arguments as you normally would, including keyword arguments and variable arguments.

class Car():
    
    def __init__(self, number_of_doors=2):
        
        self.number_of_doors = number_of_doors
        
    def __str__(self):
        
        if self.number_of_doors == 2:
            return "I am a coupe."
        if self.number_of_doors == 4:
            return "I am a sedan."
        else:
            return "I don't know what kind of car I am."
        

An example of instantiating the Car() class with different arguments that alter it's behavior.

c = Car()
print(c)
I am a coupe.
s = Car(4)
print(s)
I am a sedan.
s = Car(3)
print(s)
I don't know what kind of car I am.

Python classes offer a way to organize your code and when used carefully can add readability and reuse to the code you write. However, if inheritance trees become too deep (classes inherit from classes, that inherit from classes, that inherit from classes, that inherit from classes, etc.) or multiple inheritance is used too frequently, the opposite can happen.

Further reading

Further reading on classes and object-oriented programming can be found in the Python documentation.