Python
Creating Classes

Introduction to Classes in Python

The Concept of Object-Oriented Programming (OOP)

Object-oriented programming (OOP) is a programming paradigm that provides a means of structuring programs so that properties and behaviors are bundled into individual objects. For instance, an object could represent a person with properties like a name, age, and address and behaviors such as walking, talking, breathing, and running. Or it could represent an email with properties like a recipient list, subject, and body and behaviors like adding attachments and sending.

OOP models real-world entities as software objects, which have some data associated with them and can perform certain functions. The key concepts of the OOP paradigm include:

  • Classes/Objects: A class is a blueprint for creating objects (a particular data structure), providing initial values for state (member variables or attributes), and implementations of behavior (member functions or methods). The user-defined objects are created using the class structure.

  • Inheritance: It is a way of creating a new class for using details of an existing class without modifying it.

  • Encapsulation: It describes the idea of bundling data and methods that work on that data within one unit. This puts restrictions on accessing variables and methods directly and can prevent the accidental modification of data.

  • Polymorphism: This is an ability (in OOP) to use a common interface for multiple forms (data types).

The concept of OOP in Python focuses on creating reusable code. This concept is also known as DRY (Don't Repeat Yourself).

Syntax for Class Declaration

A class is a blueprint or template that defines the structure, attributes, and behaviors of objects. A class serves as a blueprint for creating instances (objects) that share the same characteristics and functionalities.

A class is declared using the keyword class, followed by the class name with the first letter capitalized by convention, and a colon : The body of the class is indented, similar to how the body of a function or a loop is indented. The syntax to create a class is:

class ClassName:
    <statement-1>
    .
    .
    .
    <statement-N>

Here, ClassName is the name of the class and <statement-1> to <statement-N> are any valid Python statements. These statements usually represent the attributes (variables) and methods (functions) of a class.

Creating a Class

Let's create a simple class named Person. This class will have two attributes: name and age, and one method greet

class Person:
    """
    Represents a person with a name and age.
 
    This class defines a Person object with attributes for name and age. It also provides a method 'greet'
    to display a greeting message that includes the person's name and age.
 
    Attributes:
        name (str): The name of the person.
        age (int): The age of the person.
 
    Methods:
        greet(): Display a greeting message with the person's name and age.
 
    Example:
        person = Person("John", 30)
        person.greet()
        # Output: Hello, my name is John and I am 30 years old.
    """
    def __init__(self, name, age):
        """
        Initialize a Person instance.
 
        Parameters:
            name (str): The name of the person.
            age (int): The age of the person.
        """
        self.name = name
        self.age = age
 
    def greet(self):
        """
        Display a greeting message.
 
        Prints a greeting message that includes the person's name and age.
 
        Returns:
            None: This method does not return a value; it simply prints the greeting message.
        """
        print(f"Hello, my name is {self.name} and I am {self.age} years old.")
 

Class Attributes and Methods

Class is well defined with the docstring, however, here is a short summary of what this class does:

  • In the Person class above, name and age are attributes. Attributes are the characteristics of the class that help to differentiate it from other classes.
  • Methods are functions that are part of the class. They define the behaviors of the class. In the Person class , greet is a method. It uses the print function to print a greeting that includes the person's name and age.
  • The use of __init__ method and self parameter will be explained in the next section.

Creating Objects

Creating an object of a class is known as instantiation. You instantiate an object from a class by calling the class name as if it were a function, passing any required arguments to the __init__ method.

Let's instantiate an object from the Person class we defined earlier:

# Instantiate an object of the Person class
p = Person('Alice', 25)

Here, p is an object of the Person class. We've passed 'Alice' and 25 as arguments, which are used to initialize the name and age attributes of p.

The __init__ Method for Initializing Objects

The __init__ method is a special method in Python that plays a crucial role when you create objects from a class. It's also known as the "constructor" method. This method is automatically called when you create a new object, and it's used to initialize the object's attributes.

Think of the __init__ method as the moment when an object is being "born." Just like a baby might get a name and some initial characteristics when it's born, an object gets its initial attributes through the __init__ method.

Here's a breakdown of how the __init__ method works:

  1. Syntax: The __init__ method is defined within a class. Its name is always __init__ (two underscores followed by "init")

  2. Parameters: The self parameter is the first parameter of the __init__ method. It represents the object being created. By using self, you can access and modify the attributes of the object. Other parameters can also be added to the __init__ method to provide initial values for the object's attributes. In our Person class, the __init__ method takes name and age as parameters

  3. Initialization: Inside the __init__ method, you can set up the initial values for the object's attributes. These attributes define what makes each object unique. You can assign values to attributes using the self.attribute_name = value syntax.

Accessing Attributes and Methods of Objects

Once you've created an object, you can access its attributes and methods using dot notation. Here's how you can access the name and age attributes and the greet method of the p object:

# Access the name and age attributes
print(p.name)  # Outputs: Alice
print(p.age)   # Outputs: 25
 
# Call the greet method
p.greet()  # Outputs: Hello, my name is Alice and I am 25 years old.

In this code, p.name and p.age give us the name and age of p, and p.greet() calls the greet method of p. When we call p.greet(), the greet method is executed with self referring to p, which is why it can access the name and age of p.

Example 2 of Class

class Dog:
    def __init__(self, name, age, breed):
        self.name = name
        self.age = age
        self.breed = breed
 
    def bark(self):
        print(f"{self.name} is barking!")
 
    def eat(self):
        print(f"{self.name} is eating!")
 
    def sleep(self):
        print(f"{self.name} is sleeping!")

This Dog class has the following components:

  1. Constructor (__init__ method): The constructor initializes the attributes of the class when a new object is created. In this case, the constructor takes three parameters: name, age, and breed. These parameters are used to initialize the respective attributes self.name, self.age, and self.breed.

  2. Methods (bark, eat, and sleep): These are methods that define behaviors associated with dogs. The bark method prints a message indicating that the dog is barking, the eat method indicates that the dog is eating, and the sleep method indicates that the dog is sleeping. Each method uses the self.name attribute to include the dog's name in the message.

Now, let's go through the instance creation and method calls:

# Create an instance of the Dog class with name "Buddy", age 3, and breed "Golden Retriever"
dog_instance = Dog("Buddy", 3, "Golden Retriever")
 
# Accessing attributes of the dog_instance
print(dog_instance.name)   # Output: Buddy
print(dog_instance.age)    # Output: 3
print(dog_instance.breed)  # Output: Golden Retriever
 
 
# Calling methods on the dog_instance
dog_instance.bark()   # Output: Buddy is barking!
dog_instance.eat()    # Output: Buddy is eating!
dog_instance.sleep()  # Output: Buddy is sleeping!
  • We first create an instance of the Dog class named dog_instance with the attributes "Buddy", 3, and "Golden Retriever".
  • Each attribute is accessed using dot notation (instance_name.attribute_name). For example, to access the name attribute of the dog_instance, you use dog_instance.name.
  • Then, we call the bark, eat, and sleep methods on the dog_instance, which in turn print out messages indicating the actions the dog is performing, including its name.

Example 3 of Class

class Car:
    def __init__(self, brand, model, year):
        self.brand = brand
        self.model = model
        self.year = year
 
    def start_engine(self):
        print(f"The {self.brand} {self.model}'s engine is starting.")
 
    def stop_engine(self):
        print(f"The {self.brand} {self.model}'s engine is stopping.")
 
    def honk(self):
        print(f"The {self.brand} {self.model} is honking!")

In the above code, we have defined a class called Car. Here's what each component of the class does:

  1. Constructor (__init__ method): The constructor initializes the attributes of the class when an instance is created. It takes three parameters: brand, model, and year. These parameters are used to initialize the attributes self.brand, self.model, and self.year.

  2. Methods (start_engine, stop_engine, and honk): These methods represent actions associated with cars. The start_engine method indicates that the car's engine is starting, the stop_engine method indicates that the engine is stopping, and the honk method indicates that the car is honking. Each method uses the self.brand and self.model attributes to provide information about the car.

Now, let's create an instance of this class and access its attributes and methods

# Create an instance of the Car class
car_instance = Car("Toyota", "Camry", 2022)
 
# Accessing attributes of the car_instance
print(car_instance.brand)  # Output: Toyota
print(car_instance.model)  # Output: Camry
print(car_instance.year)   # Output: 2022
 
# Calling methods on the car_instance
car_instance.start_engine()  # Output: The Toyota Camry's engine is starting.
car_instance.stop_engine()   # Output: The Toyota Camry's engine is stopping.
car_instance.honk()          # Output: The Toyota Camry is honking!
  • We first create an instance of the Car class named car_instance with the attributes "Toyota", "Camry", and 2022.
  • By using dot notation, you access the attributes (brand, model, year) and call the methods (start_engine, stop_engine, honk) associated with the car_instance. This allows you to interact with and utilize the attributes and methods defined in the class.

Importance and Use of Classes in Python

Classes are a fundamental part of object-oriented programming (OOP) in Python. They provide structure for creating more complex data structures and allow us to model real-world things and situations. Here are some reasons why classes are important:

  1. Modularity: Classes provide a way of bundling data and functionality together. Creating a new class creates a new type of object, allowing new instances of that type to be made.

  2. Code Reusability and Recycling: Classes support inheritance and composition, fundamental paradigms of OOP. This means that we can create subclasses from a base class without modifying it, or we can use instances of other classes as attributes in a class, promoting code reusability.

  3. Simplicity: When working with complex systems, it can be much simpler to create a class that contains all the necessary attributes and behaviors. This way, you can create an instance of a class and work with it, instead of managing a large number of individual variables and functions.