Skip to content

Practical Exercises: Creating and Working with Classes and Objects in Python

Updated: at 02:12 AM

Object-oriented programming (OOP) is a fundamental programming paradigm in Python that structures code into modular and reusable blocks called objects. Classes provide the blueprint for how objects are defined, enabling developers to model real-world entities with code. This guide will walk through practical exercises for creating custom classes and working with class instances as objects in Python. We will cover key OOP concepts like abstraction, encapsulation, inheritance and polymorphism.

Table of Contents

Open Table of Contents


Python fully supports OOP, making it easy to represent objects and their interactions. Objects allow bundling data and behavior together for organized, flexible programming. Classes act as templates for creating objects with shared attributes and methods.

Some key benefits of OOP in Python include:

We will explore these advantages by incrementally developing classes to model real-world objects. Let’s start with a simple class definition.

Defining and Instantiating Classes

A class definition starts with the class keyword, followed by the class name and a colon. Indented underneath are the class contents including attributes and methods:

class Dog:

  def __init__(self, name, age): = name
    self.age = age

  def description(self):
    return f"{} is {self.age} years old"

This Dog class has two attributes - name and age, set during instantiation via the special __init__ method. It also defines a description() method to describe the dog.

We can create individual dog objects using the Dog() constructor:

dog1 = Dog("Rex", 4)
# Rex is 4 years old

The dog1 object encapsulates the name and age data along with access to the description() method. Class attributes are accessed via dot notation on the instance.

Let’s try modeling some more types of objects with custom classes.

Modeling Real-World Entities

OOP shines for representing real-world entities like people, places and things in code. We can identify common attributes and functionality to abstract into classes.

Person Class

A Person class could have properties like name, age, job and methods such as introduce() and celebrate_birthday():

class Person:

  def __init__(self, name, age, job): = name
    self.age = age
    self.job = job

  def introduce(self):
    statement = f"Hello, my name is {}. I am {self.age} years old and work as a {self.job}."

  def celebrate_birthday(self):
    self.age += 1

person1 = Person("Maria", 32, "Data Analyst")
# Hello, my name is Maria. I am 32 years old and work as a Data Analyst.

# 33

We can instantiate distinct Person objects to represent individuals with shared methods like introduce() customized via their attributes.

Vehicle Class

We can similarly model vehicles with properties like make, model, year and methods like description() and drive():

class Vehicle:

  def __init__(self, make, model, year):
    self.make = make
    self.model = model
    self.year = year

  def description(self):
    desc_str = f"{self.year} {self.make} {self.model}"

  def drive(self):
    print(f"The {self.model} is driving")

car = Vehicle('Toyota', 'Prius', 2020)
# 2020 Toyota Prius
# The Prius is driving

The Vehicle class allows us to describe and manipulate various vehicle objects with the same interface.

Product Class

Similarly, a basic Product class can represent retail items in an e-commerce app:

class Product:

  def __init__(self, name, price, qty): = name
    self.price = price
    self.qty = qty

  def get_total(self):
    total = self.price * self.qty
    print(f"Total: ${total:.2f}")

shirt = Product("T-Shirt", 15, 2)
pants = Product("Jeans", 50, 1)

# Total: $30.00

# Total: $50.00

Custom classes like these provide building blocks to construct programs modeling complex real-world systems. Next we’ll explore how to leverage inheritance between classes.

Inheriting Attributes and Behavior

A key OOP concept is inheritance - allowing child classes to inherit attributes and methods from parent classes.

For example, we can create a ElectricVehicle class that inherits from the Vehicle parent:

class ElectricVehicle(Vehicle):

  def __init__(self, make, model, year, battery_size):
    super().__init__(make, model, year)
    self.battery_size = battery_size

ev = ElectricVehicle('Tesla', 'Model 3', 2020, 75)
# 2020 Tesla Model 3

The child ElectricVehicle class inherits the __init__ constructor and description() method from Vehicle. We expand on the parent by adding a battery_size attribute in the child constructor.

The super() function calls the parent __init__ allowing us to leverage inherited behavior. Child classes can override or expand upon parent class functionality.

Let’s override the drive() method to customize it:

#...Rest of ElectricVehicle class

def drive(self):
    print("The electric vehicle is driving silently")
# The electric vehicle is driving silently

We’ve tailored the drive() logic for electric vehicles while reusing the Vehicle parent class. This demonstrates polymorphism - child classes can implement behavior specific to their type.

Creating Subclasses for Specialization

We can further specialize classes through sub-classing. For example, an ElectricCar class:

class ElectricCar(ElectricVehicle):

  def __init__(self, make, model, year):
    super().__init__(make, model, year, battery_size=75)

  def charge(self):
    print(f"Charging {self.make} {self.model}")

ecar = ElectricCar('Tesla', 'Model S', 2018)
# Charging Tesla Model S

ElectricCar inherits from ElectricVehicle allowing us to further specialize the subclass. We customize the constructor and add a charge() method specific to electric cars vs. a generic electric vehicle.

Subclassing enables extensive reuse of logic between related classes. This powerful hierarchy of inheritance is a key advantage of OOP.

Encapsulating Class Details

A key principle in OOP is encapsulation - bundling data and methods into a single class and limiting outside access. This gives control over the internal representation.

For example, we can make the battery_size attribute private:

class ElectricVehicle:

  def __init__(self, make, model, year):
    self.__battery_size = 75 # private attribute

  def get_range(self):
    range = self.__battery_size * 5
    print(f"Range is {range} miles")

ev = ElectricVehicle('Tesla','Model 3', 2018)
# Range is 375 miles

ev.__battery_size # Raises AttributeError

The private __battery_size attribute can only be accessed via the get_range() method. This prevents external code from inadvertently breaking internal object details.

Getters and setters can expand encapsulation by implementing access logic:

class ElectricVehicle:

  # ...

  def get_battery_size(self):
    print(f"Battery size: {self.__battery_size} kWh")

  def set_battery_size(self, new_size):
    if new_size < 75:
      raise ValueError("Battery too small")
      self.__battery_size = new_size

ev = ElectricVehicle('Tesla','Model 3', 2018)
# Battery size: 75 kWh

# Battery size: 100 kWh

This control over access restrictions and data validation enhances encapsulation in OOP.

Leveraging Magic Methods

Python classes have special __method__() names called magic methods that act as hooks or shortcuts for built-in behavior.

For example, the __str__() method returns a string representation of the object:

class Person:

  def __init__(self, name, age): = name
    self.age = age

  def __str__(self):
    return f"{} is {self.age} years old"

user = Person('Eric', 25)
# Eric is 25 years old

This allows customizing string conversion instead of the default <__main__.Person object>. Other useful built-ins include __add__() for + operator overloading and __len__() to override len().

Magic methods give extensive control over class behavior and integration with Python features.


In this guide, we explored practical OOP techniques in Python such as:

Object-oriented programming facilitates code reuse, maintainability and scalability using abstraction, encapsulation, inheritance and polymorphism. Mastering classes unlocks Python’s capabilities for elegantly modeling complex systems across various domains.

The class mechanics provide the building blocks - the key is creativity in analyzing problems to effectively apply OOP principles. There are infinite possibilities for leveraging classes to produce concise, expressive and extensible code.