Module: Object Oriented Programming

Classes

Python Classes: A Deep Dive into Object-Oriented Programming

Classes are the fundamental building blocks of object-oriented programming (OOP) in Python. They serve as blueprints for creating objects, which are instances of the class. This allows you to model real-world entities and their behaviors in your code.

1. Defining a Class

The class keyword is used to define a class. By convention, class names are capitalized (PascalCase).

class Dog:
    """
    A simple class representing a dog.
    """
    # Class attributes (shared by all instances)
    species = "Canis familiaris"

    # Constructor (initializer)
    def __init__(self, name, breed, age):
        # Instance attributes (unique to each instance)
        self.name = name
        self.breed = breed
        self.age = age

    # Instance methods (functions that operate on instances)
    def bark(self):
        """Prints a barking sound."""
        print("Woof!")

    def describe(self):
        """Prints a description of the dog."""
        print(f"My name is {self.name}, I am a {self.age}-year-old {self.breed}.")

    def birthday(self):
        """Increments the dog's age."""
        self.age += 1
        print(f"Happy Birthday, {self.name}! You are now {self.age} years old.")

Explanation:

  • class Dog:: Defines a class named Dog.
  • """Docstring""": A documentation string that describes the class. Good practice to include.
  • species = "Canis familiaris": A class attribute. This attribute is shared by all instances of the Dog class. All dogs are of the same species.
  • __init__(self, name, breed, age):: The constructor or initializer method. It's automatically called when you create a new instance of the class.
    • self: A reference to the instance being created. It's the first argument to all instance methods.
    • name, breed, age: Parameters that are passed when creating a new Dog object.
    • self.name = name: Assigns the value of the name parameter to the instance attribute self.name. This creates a unique name attribute for each Dog object. The same applies to breed and age.
  • bark(self):, describe(self):, birthday(self):: Instance methods. These are functions that operate on the instance of the class. They always take self as the first argument.

2. Creating Instances (Objects)

To create an instance of a class, you call the class name like a function, passing in the required arguments for the constructor.

my_dog = Dog("Buddy", "Golden Retriever", 3)
your_dog = Dog("Lucy", "Poodle", 5)

Now, my_dog and your_dog are objects (instances) of the Dog class. Each has its own unique name, breed, and age attributes. They both share the species class attribute.

3. Accessing Attributes and Calling Methods

You can access an object's attributes using the dot notation (.). You can call an object's methods in the same way.

print(my_dog.name)  # Output: Buddy
print(your_dog.breed) # Output: Poodle
print(Dog.species)   # Output: Canis familiaris (accessing class attribute)

my_dog.bark()       # Output: Woof!
your_dog.describe()  # Output: My name is Lucy, I am a 5-year-old Poodle.
my_dog.birthday()    # Output: Happy Birthday, Buddy! You are now 4 years old.
print(my_dog.age)    # Output: 4

4. Class vs. Instance Attributes

  • Class Attributes: Belong to the class itself. They are shared by all instances of the class. You access them using ClassName.attribute_name. Useful for constants or data that applies to all objects of the class.
  • Instance Attributes: Belong to individual instances of the class. Each instance has its own copy of these attributes. You access them using instance_name.attribute_name. Useful for data that is specific to each object.

5. __str__ and __repr__ Methods

These special methods allow you to control how an object is represented as a string.

  • __str__(self): Returns a human-readable string representation of the object. Used by print() and str().
  • __repr__(self): Returns a string representation of the object that is intended to be unambiguous and can be used to recreate the object. Used by the interactive interpreter and repr().
class Dog:
    # ... (previous code) ...

    def __str__(self):
        return f"Dog(name={self.name}, breed={self.breed}, age={self.age})"

    def __repr__(self):
        return f"Dog('{self.name}', '{self.breed}', {self.age})"

Now, if you print(my_dog), you'll get a more informative output. __repr__ is particularly useful for debugging.

6. Inheritance

Inheritance allows you to create new classes (child classes) that inherit attributes and methods from existing classes (parent classes). This promotes code reuse and creates a hierarchy of classes.

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        print("Generic animal sound")

class Cat(Animal):
    def __init__(self, name, breed):
        super().__init__(name)  # Call the parent class's constructor
        self.breed = breed

    def speak(self):
        print("Meow!")

    def purr(self):
        print("Purr...")

my_cat = Cat("Whiskers", "Siamese")
print(my_cat.name)  # Output: Whiskers (inherited from Animal)
my_cat.speak()      # Output: Meow! (overridden method)
my_cat.purr()       # Output: Purr... (unique method)

Explanation:

  • class Cat(Animal):: Defines a class Cat that inherits from the Animal class.
  • super().__init__(name): Calls the constructor of the parent class (Animal) to initialize the name attribute. This ensures that the inherited attributes are properly initialized.
  • speak(self): The speak method is overridden in the Cat class. This means that when you call my_cat.speak(), the Cat class's speak method is executed instead of the Animal class's speak method.

7. Key OOP Concepts Illustrated by Classes

  • Encapsulation: Bundling data (attributes) and methods that operate on that data within a class.
  • Abstraction: Hiding complex implementation details and exposing only the essential features of an object.
  • Inheritance: Creating new classes based on existing classes, promoting code reuse and creating a hierarchy.
  • Polymorphism: The ability of objects of different classes to respond to the same method call in their own way (e.g., the speak method in Animal and Cat).

Classes are a powerful tool for organizing and structuring your Python code, making it more maintainable, reusable, and easier to understand. Mastering classes is essential for becoming a proficient Python programmer.