Python is an object-oriented programming language, meaning it allows developers to create and manipulate objects. Objects are instances of classes, which act as blueprints for how objects should behave and interact. In this blog, we will explore the fundamentals of objects and classes in Python, breaking down how they work and how to implement them effectively.
What are Objects and Classes in Python (OOPS)?
Object-Oriented Programming (OOP) in Python is a programming paradigm that revolves around the concept of objects and classes. In OOP, data and functions are bundled into units called objects, which represent real-world entities. Python allows developers to create classes that serve as blueprints for these objects, defining their attributes (data) and methods (functions). Through OOP, Python provides powerful features like inheritance, encapsulation, and polymorphism, enabling code reuse, better organization, and scalability. This approach helps in building complex systems by breaking them down into smaller, manageable components, making code easier to understand, maintain, and extend.
Classes: A class is like a blueprint for creating objects. It defines the properties (attributes) and behaviors (methods) that the objects created from it will have.
Objects: An object is an instance of a class. It’s a self-contained entity that consists of both data (attributes) and methods (functions that manipulate the data).
For example, if we were modeling a car in Python, the Car class would define attributes like color and speed, while the actual cars we create (instances) would be the objects.
Creating a Class in Python
Let’s start by creating a simple Car class with some attributes and methods.
class Car:
# Constructor to initialize the object
def __init__(self, brand, model, color):
self.brand = brand # Instance attribute
self.model = model # Instance attribute
self.color = color # Instance attribute
# Method to display car details
def display_info(self):
print(f"Car: {self.brand} {self.model}, Color: {self.color}")
# Method to simulate starting the car
def start_engine(self):
print(f"The engine of {self.brand} {self.model} is now running.")
Key Concepts
init method: This is the class constructor. It’s called when an object is instantiated. The parameters passed to init are used to initialize the object’s attributes.
Attributes: In the example, brand, model, and color are instance attributes, which are unique to each object (car).
Methods: Functions defined inside a class are known as methods. Here, display_info and start_engine are methods that belong to the Car class.
Creating Objects from a Class
Now that we have a class, let's create objects (instances) from it:
# Creating objects
car1 = Car("Toyota", "Corolla", "Red")
car2 = Car("Honda", "Civic", "Blue")
# Calling methods on objects
car1.display_info()
car1.start_engine()
car2.display_info()
car2.start_engine()
Output:
Car: Toyota Corolla, Color: Red
The engine of Toyota Corolla is now running.
Car: Honda Civic, Color: Blue
The engine of Honda Civic is now running.
Here, we created two different car objects (car1 and car2), each having its own unique attributes (brand, model, and color). When we call the display_info and start_engine methods on them, each object behaves independently.
Understanding self in Python Classes
The self parameter is a reference to the current object (instance) of the class. It is used to access instance variables and methods. When you create an object, self helps Python differentiate between the attributes and methods of different objects.
In the method definition:
def display_info(self):
...
self is automatically passed when calling the method on an object.
Class Variables vs Instance Variables
There are two types of variables in a class:
Instance Variables: These are unique to each object, as we saw in the example above. Each car object has its own brand, model, and color.
Class Variables: These are shared across all instances of the class. Let's modify our Car class to include a class variable:
class Car:
num_of_wheels = 4 # Class variable, shared by all instances
def __init__(self, brand, model, color):
self.brand = brand
self.model = model
self.color = color
def display_info(self):
print(f"Car: {self.brand} {self.model}, Color: {self.color}, Wheels: {Car.num_of_wheels}")
Here, num_of_wheels is a class variable that is shared by all car objects. No matter how many instances we create, num_of_wheels will always be the same unless we modify it at the class level.
car1 = Car("Ford", "Mustang", "Black")
car2 = Car("BMW", "X5", "White")
car1.display_info() # Output will include 4 wheels
car2.display_info() # Output will also include 4 wheels
Output:
Car: Ford Mustang, Color: Black, Wheels: 4
Car: BMW X5, Color: White, Wheels: 4
Inheritance in Python
Inheritance allows a class to inherit properties and methods from another class, making code reusable and easier to maintain.
Let's create a ElectricCar class that inherits from the Car class:
class ElectricCar(Car):
def __init__(self, brand, model, color, battery_size):
super().__init__(brand, model, color) # Call the parent class constructor
self.battery_size = battery_size # New attribute for ElectricCar
# Overriding the start_engine method
def start_engine(self):
print(f"The electric engine of {self.brand} {self.model} is now running silently.")
def display_battery(self):
print(f"The battery size of {self.brand} {self.model} is {self.battery_size} kWh.")
Here, ElectricCar inherits attributes and methods from Car. We have also overridden the start_engine method to provide a specific behavior for electric cars.
electric_car = ElectricCar("Tesla", "Model 3", "Silver", 75)
electric_car.display_info()
electric_car.start_engine()
electric_car.display_battery()
Output:
Car: Tesla Model 3, Color: Silver, Wheels: 4
The electric engine of Tesla Model 3 is now running silently.
The battery size of Tesla Model 3 is 75 kWh.
Encapsulation and Data Hiding
Encapsulation is the principle of restricting direct access to some of an object's attributes and methods. Python uses a naming convention to indicate that a variable should not be accessed directly:
Single underscore (_): A weak "internal use" indicator.
Double underscore (__): Strongly suggests the attribute is private.
For example:
class Car:
def __init__(self, brand, model, color):
self._brand = brand # Weakly private
self.__engine_status = "off" # Strongly private
def start_engine(self):
self.__engine_status = "on"
print(f"The engine is now {self.__engine_status}")
Here, __engine_status should not be accessed directly outside the class, while _brand is a soft private variable.
Conclusion
Classes and objects form the foundation of Python’s object-oriented capabilities. With concepts like inheritance, encapsulation, and methods, you can build complex and scalable programs that are easier to maintain and expand. Understanding how to structure your code with classes allows you to model real-world entities and their interactions, making your applications more intuitive and flexible. By mastering objects and classes, you'll be able to write cleaner, more efficient Python code that mirrors real-world concepts. As you continue to explore more advanced topics like polymorphism, decorators, and design patterns, you’ll see how powerful object-oriented programming can be for developing both small scripts and large-scale systems. Whether you're building simple applications or tackling complex machine learning projects, understanding these fundamentals will provide a strong foundation for your Python development journey.
Comments