Object-Oriented Programming (OOP) is a programming paradigm based on the concept of "objects", which can contain data in the form of fields (often known as attributes or properties) and code in the form of procedures (often known as methods).
Python is a multi-paradigm programming language, and its support for OOP makes it a powerful tool for building complex and maintainable software. Let's explore the core concepts of OOP in Python.
A class is a blueprint for creating objects. It defines the properties (attributes) and behaviors (methods) that all objects of that type will have.
An object is an instance of a class. You can create multiple objects from a single class, each with its own set of attributes.
class Dog:
# Class attribute (shared by all instances)
species = "Canis familiaris"
def __init__(self, name, age):
# Instance attributes (unique to each instance)
self.name = name
self.age = age
# Instance method
def description(self):
return f"{self.name} is {self.age} years old"
# Another instance method
def speak(self, sound):
return f"{self.name} says {sound}"
# Instantiate the Dog class
my_dog = Dog("Buddy", 5)
another_dog = Dog("Lucy", 2)
# Accessing attributes
print(my_dog.name) # Output: Buddy
print(another_dog.age) # Output: 2
print(my_dog.species) # Output: Canis familiaris
# Calling methods
print(my_dog.description()) # Output: Buddy is 5 years old
print(another_dog.speak("Woof")) # Output: Lucy says Woof
Inheritance allows a new class (a child class or derived class) to inherit properties and methods from an existing class (a parent class or base class). This promotes code reuse and establishes a hierarchical relationship between classes.
class JackRussellTerrier(Dog): # Inherits from Dog
def __init__(self, name, age, coat_length):
# Call the parent class constructor
super().__init__(name, age)
self.coat_length = coat_length # New attribute
# Overriding a method from the parent class
def speak(self, sound="Arf"):
return f"{self.name} barks {sound}"
# New method specific to JackRussellTerrier
def fetch(self):
return f"{self.name} is fetching!"
# Create an instance of the derived class
my_jack = JackRussellTerrier("Milo", 3, "short")
print(my_jack.description()) # Inherited method: Milo is 3 years old
print(my_jack.speak()) # Overridden method: Milo barks Arf
print(my_jack.fetch()) # New method: Milo is fetching!
print(my_jack.species) # Inherited class attribute: Canis familiaris
Polymorphism (meaning "many forms") allows objects of different classes to be treated as objects of a common superclass. A single interface can be used for a general class of actions. For example, a method may perform different actions depending on the object it is called on.
class Cat:
def __init__(self, name):
self.name = name
def meow(self):
return f"{self.name} says Meow!"
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
return f"{self.name} says Woof!"
def animal_sound(animal):
# This function works with any object that has a sound-making method
print(animal.meow() if hasattr(animal, 'meow') else animal.bark())
my_cat = Cat("Whiskers")
my_dog = Dog("Rex")
animal_sound(my_cat) # Output: Whiskers says Meow!
animal_sound(my_dog) # Output: Rex says Woof!
Encapsulation is the bundling of data (attributes) and methods that operate on that data within a single unit (a class). It helps in data hiding and protecting the internal state of an object from direct external access. Python uses conventions for indicating attributes that are intended to be "private" (though not strictly enforced like in some other languages).
_attribute
). Intended for use within the class and its subclasses.__attribute
). Python mangles these names to prevent accidental overriding, making them harder to access from outside the class.
class Car:
def __init__(self, make, model, year):
self.make = make # Public attribute
self.model = model # Public attribute
self._year = year # Protected attribute
self.__mileage = 0 # Private attribute
def drive(self, distance):
if distance > 0:
self.__mileage += distance
print(f"Driving {distance} miles. Current mileage: {self.__mileage}")
else:
print("Distance must be positive.")
def get_mileage(self):
return self.__mileage # Accessing private attribute via a public method
# Create a Car object
my_car = Car("Toyota", "Camry", 2022)
# Accessing public attributes
print(my_car.make) # Output: Toyota
# Accessing protected attribute (conventionally not recommended directly)
print(my_car._year) # Output: 2022
# Driving the car
my_car.drive(100) # Output: Driving 100 miles. Current mileage: 100
my_car.drive(50) # Output: Driving 50 miles. Current mileage: 150
# Accessing private attribute (directly not possible/recommended)
# print(my_car.__mileage) # This would raise an AttributeError
# Accessing private attribute via a public method
print(f"Total mileage: {my_car.get_mileage()}") # Output: Total mileage: 150
Understanding these core OOP principles is crucial for writing efficient, scalable, and well-organized Python code.