Chapter 5

Classes and Objects in Python

Object-oriented programming (OOP) is a powerful paradigm in Python that allows us to represent real-world entities as objects. Python achieves this through classes and objects, which provide a structured way to store data and define functions that operate on that data. In this chapter, we will explore all aspects of OOP in Python, including key concepts like abstraction, encapsulation, inheritance, and polymorphism, using classes and objects as the foundation.


What Are Classes and Objects?

  • Class: A class is a blueprint for creating objects. It defines the attributes (data) and methods (functions) that an object of that class can have.

  • Object: An object is an instance of a class. It contains specific values for the data defined in the class, and it can use the methods provided by the class.

In Python, everything is an object. Even primitive data types such as integers, strings, and lists are instances of their respective classes.

Example: A Simple Class

Let’s start by defining a class for 2D points:

class Point:
    def __init__(self, a=0, b=0):
        self.x = a  # x-coordinate
        self.y = b  # y-coordinate

    def translate(self, deltax, deltay):
        self.x += deltax
        self.y += deltay

    def odistance(self):
        import math
        return math.sqrt(self.x**2 + self.y**2)  # Distance from origin
  • The __init__ method is a constructor that initializes an object of the class.

  • The translate method shifts the point by (deltax, deltay).

  • The odistance method calculates the distance of the point from the origin (0, 0).

Example: Creating an Object

You can create an instance (object) of this class as follows:

p = Point(3, 4)  # Creates a Point object at (3, 4)
print(p.odistance())  # Outputs: 5.0 (distance from origin)

Here, p is an object of the Point class.


Key Concepts in OOP

1. Encapsulation:

Encapsulation is the process of restricting access to the internal details of an object. In Python, this is often done using private variables (conventionally prefixed with an underscore) and providing access to them through methods.

Example: Encapsulation in Python

class Point:
    def __init__(self, a=0, b=0):
        self._x = a  # Private variable
        self._y = b  # Private variable

    def get_coordinates(self):
        return (self._x, self._y)

    def set_coordinates(self, a, b):
        self._x = a
        self._y = b

Here, the coordinates are stored in private variables _x and _y. The methods get_coordinates and set_coordinates control access to these variables, providing encapsulation.

2. Abstraction:

Abstraction means hiding the implementation details of an object and only exposing the essential functionality. In the Point class, the user doesn’t need to know how the odistance method calculates the distance from the origin; they only need to know that calling odistance will give them the correct result.


3. Inheritance:

Inheritance allows a class to inherit attributes and methods from another class. This helps in code reuse and the creation of hierarchical structures.

class Shape:
    def __init__(self, color="red"):
        self.color = color

    def display(self):
        print(f"This shape is {self.color}")

class Circle(Shape):
    def __init__(self, radius, color="red"):
        super().__init__(color)  # Call the constructor of the base class
        self.radius = radius

    def area(self):
        import math
        return math.pi * self.radius**2

# Usage
c = Circle(5, "blue")
c.display()  # Outputs: This shape is blue
print(c.area())  # Outputs: Area of the circle

Here, Circle is a subclass of Shape, inheriting its properties and methods.

4. Polymorphism:

Polymorphism allows objects of different classes to be treated as objects of a common superclass. It is implemented through method overriding, where a method in the child class can have the same name as a method in the parent class but with a different implementation.

class Shape:
    def area(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        import math
        return math.pi * self.radius**2

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

# Polymorphism in action
shapes = [Circle(5), Rectangle(3, 4)]

for shape in shapes:
    print(shape.area())  # Will print area of Circle and Rectangle

Special Methods in Python Classes

Python allows classes to have special methods (also known as magic methods) that allow objects to behave in certain ways, like adding two objects together or converting an object to a string.

1. __str__() Method

The __str__ method allows you to define how an object is represented as a string. It is called automatically when the object is passed to print().

class Point:
    def __init__(self, a=0, b=0):
        self.x = a
        self.y = b

    def __str__(self):
        return f"({self.x}, {self.y})"

# Usage
p = Point(3, 4)
print(p)  # Outputs: (3, 4)

2. Operator Overloading with __add__()

In Python, you can define how objects of a class behave when used with operators like +, -, *, etc., by implementing special methods like __add__() for addition.

class Point:
    def __init__(self, a=0, b=0):
        self.x = a
        self.y = b

    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)

# Usage
p1 = Point(1, 2)
p2 = Point(3, 4)
p3 = p1 + p2  # Uses __add__ method
print(p3)  # Outputs: (4, 6)

Example: 2D Point with Polar Coordinates

Let's expand the previous example by using polar coordinates in addition to Cartesian coordinates:

import math

class Point:
    def __init__(self, a=0, b=0):
        self.r = math.sqrt(a**2 + b**2)
        self.theta = math.atan2(b, a)  # Handle cases where a = 0

    def odistance(self):
        return self.r  # Distance from origin in polar coordinates

    def translate(self, deltax, deltay):
        x = self.r * math.cos(self.theta)
        y = self.r * math.sin(self.theta)
        x += deltax
        y += deltay
        self.r = math.sqrt(x**2 + y**2)
        self.theta = math.atan2(y, x)

    def __str__(self):
        return f"(r={self.r:.2f}, θ={math.degrees(self.theta):.2f}°)"

# Usage
p = Point(3, 4)
print(p)  # Outputs: (r=5.00, θ=53.13°)
p.translate(1, 1)
print(p)  # Outputs the new coordinates after translation

In this version:

  • Point is represented in polar coordinates, and we translate it in Cartesian coordinates while keeping the interface consistent.

  • The user need not be aware of whether the internal representation is in Cartesian or polar coordinates.


Conclusion

Classes and objects are fundamental to structuring complex programs in Python. They allow you to encapsulate data and functionality, reuse code through inheritance, and implement abstraction and polymorphism to create flexible and maintainable programs. Python’s support for operator overloading, special methods, and exceptions makes classes even more powerful, allowing objects to integrate seamlessly with Python’s language constructs.

In this chapter, we’ve explored how to define classes and objects, implement methods, use special functions, and leverage object-oriented principles to build efficient and readable code.

Last updated