Python Notes-Chapter 3
Python Notes-Chapter 3
Python Object
Oriented
Programming
Everything in Python is an object.
An object has a state and behaviors.
To create an object, you define a class first. And then, from the class, you
can create one or more objects. The objects are instances of a class.
Define a class
To define a class, you use the class keyword followed by the class name. For
example, the following defines a Person class:
class Person:
pass
Python is dynamic. It means that you can add an attribute to an instance of a class
dynamically at runtime.
For example, the following adds the name attribute to the person object:
pers1.name = 'Vijay'
pers1.age=23
pers2.name='sakshi'
pers2.age=21
Define constructor
In Python, the constructor method is a special method called __init__. It is
automatically called when an object of a class is created. The purpose of the
constructor is to initialize the object's attributes.
Here's an example demonstrating the use of a constructor in Python:
The following defines the Person class with two instance attributes name and age:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
In this example, __init__ is the constructor method of the Person class. It takes
three parameters: self, which is a reference to the instance being created, name,
and age. Inside the constructor, self.name and self.age are instance variables,
which are initialized with the values passed as arguments during the object
creation (person1 = Person("Avinash ", 30)).
The self parameter is required in Python class methods. It is a reference to the
current instance of the class and is used to access variables and methods
associated with the instance. When calling methods or accessing attributes within
the class, you use self to refer to the instance itself.
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
In this example, area and perimeter are instance methods of the Rectangle class.
Instance methods are defined like regular functions within the class definition,
but they always take at least one parameter, conventionally named self, which
represents the instance on which the method is called.
When calling instance methods, you do not explicitly pass the self parameter;
Python handles this automatically. When you call rectangle.area(), for example,
Python internally calls Rectangle.area(rectangle), where rectangle is the instance
of the Rectangle class.
Inside the instance methods, you can access the instance variables (attributes)
using self. In the area method, for instance, self.width and self.height are used to
access the width and height attributes of the Rectangle object respectively.
class Car:
# Class attribute
num_cars = 0
def __del__(self):
Car.num_cars -= 1 # Decrementing the class attribute when an object
is deleted
# Creating instances of the Car class
car1 = Car("Toyota", "Corolla")
car2 = Car("Honda", "Civic")
In this example, num_cars is a class attribute of the Car class. It keeps track of the
total number of car objects created. Every time a new Car object is instantiated,
num_cars is incremented by one.
A destructor in Python is a special method called __del__. It is called when an
object is about to be destroyed (i.e., garbage collected). The purpose of the
destructor is to perform any necessary cleanup actions before the object is
removed from memory. In the example above, when an instance of the Car class
is deleted using the del keyword (del car1), the __del__ method is called, and it
decrements the num_cars class attribute.
Class Method:
Class methods are methods that are called on the class itself, not on a specific
object instance. Therefore, it belongs to a class level, and all class instances share
a class method.
A class method is bound to the class and not the object of the class. It can
access only class variables.
It can modify the class state by changing the value of a class variable that
would apply across all the class objects.
@classmethod
def change_school(cls, school_name):
# class_name.class_variable
cls.school_name = school_name
# instance method
def show(self):
print(self.name, self.age, 'School:', Student.school_name)
# change school_name
Student.change_school('XYZ School')
stud1.show()
Any method we create in a class will automatically be created as an instance
method. We must explicitly tell Python that it is a class method using the
@classmethod decorator.
Class methods are defined inside a class, and it is pretty similar to defining a
regular function.
Like, inside an instance method, we use the self keyword to access or modify the
instance variables. Same inside the class method, we use the cls keyword as a
first parameter to access class variables. Therefore the class method gives us
control of changing the class state.
To make a method as class method, add @classmethod decorator before the
method definition, and add cls as the first parameter to the method.
The @classmethod decorator is a built-in function decorator. In Python, we use
the @classmethod decorator to declare a method as a class method.
Static Method:
A static method is a general utility method that performs a task in isolation. Inside
this method, we don’t use instance or class variable because this static method
doesn’t take any parameters like self and cls.
A static method is a general utility method that performs a task in isolation .
A static method is bound to the class and not the object of the class. Therefore,
we can call it using the class name.
A static method doesn’t have access to the class and instance variables because it
does not receive an implicit first argument like self and cls. Therefore it cannot
modify the state of the object or class.
Static methods are defined inside a class, and it is pretty similar to defining a
regular function. To declare a static method, use this idiom:
class C:
@staticmethod
def f(arg1, arg2, ...):
...
To make a method a static method, add @staticmethod decorator before the
method definition.
Example:
class Test :
@staticmethod
def static_method_1():
@staticmethod
def static_method_2() :
Test.static_method_1()
@classmethod
def class_method_1(cls) :
cls.static_method_2()
Test.class_method_1()
Inheritance in Python:
Inheritance is an important aspect of the object-oriented paradigm. Inheritance
provides code reusability to the program because we can use an existing class to
create a new class instead of creating it from scratch.
In inheritance, the child class acquires the properties and can access all the data
members and functions defined in the parent class. A child class can also provide
its specific implementation to the functions of the parent class.
In python, a derived class can inherit base class by just mentioning the base in the
bracket after the derived class name. Consider the following syntax to inherit a
base class into the derived class.
1. Single Inheritance:
Single inheritance in Python refers to the concept where a subclass (child class)
inherits attributes and methods from a single superclass (parent class). This
means that a child class can inherit properties and behaviors from only one
parent class.
Here's a simple example demonstrating single inheritance in Python:
# Parent class (superclass)
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name} makes a sound")
# Child class (subclass) inheriting from Animal
class Dog(Animal):
def speak(self):
print(f"{self.name} barks")
# Creating instances of both classes
In this example:
Animal is the parent class with a constructor method __init__ and a method
speak.
Dog is the child class that inherits from Animal.
Dog overrides the speak method defined in Animal with its own implementation.
Instances of both classes are created, and the speak method is called on each of
them. The output depends on which class the instance belongs to.
Output:
Generic Animal makes a sound
Tommy barks
2. Multilevel Inheritance:
Multilevel inheritance in Python refers to a scenario where a class is derived from
a derived class, i.e., a subclass inherits from another subclass. This creates a
hierarchy of classes with multiple levels of inheritance.
Here's an example demonstrating multilevel inheritance in Python:
# Parent class
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name} makes a sound")
class Labrador(Dog):
def color(self):
print(f"{self.name} is brown")
In this example:
Animal is the parent class with a constructor method __init__ and a method
speak.
Dog is a child class of Animal inheriting its attributes and methods. It overrides
the speak method with its own implementation.
Labrador is a grandchild class inheriting from Dog. It inherits both from Animal
and Dog, and it adds its own method color.
Instances of each class are created, and methods are called on them. The output
demonstrates the method calls based on the inheritance hierarchy.
Output:
Generic Animal makes a sound
Buddy barks
Max barks
Max is brown
3. Multiple Inheritance:
Multiple inheritance in Python refers to a situation where a class can inherit
attributes and methods from more than one parent class. This allows for creating
complex class hierarchies by combining features from multiple parent classes.
Here's an example demonstrating multiple inheritance in Python:
# Parent class 1
class Bird:
def __init__(self):
print("Bird is ready")
def who_is_this(self):
print("Bird")
def swim(self):
print("Swim faster")
# Parent class 2
class Mammal:
def __init__(self):
print("Mammal is ready")
def who_is_this(self):
print("Mammal")
def run(self):
print("Run faster")
Bird.__init__(self)
Mammal.__init__(self)
print("Bat is ready")
def who_is_this(self):
print("Bat")
bat = Bat()
# Calling methods on the instance
In this example:
Bird and Mammal are two parent classes, each with its own set of methods.
Bat is a child class inheriting from both Bird and Mammal.
When an instance of Bat is created, both parent class constructors (__init__
methods) are called explicitly to initialize their attributes.
The who_is_this method is overridden in the Bat class to provide its own
implementation.
Methods from both parent classes can be accessed and called on an instance of
the Bat class.
Multiple inheritance can lead to the diamond problem and other complexities, so
it's essential to use it judiciously and understand the method resolution order
(MRO) in Python.
class B(A):
def method(self):
print("Method from class B")
class C(A):
def method(self):
print("Method from class C")
obj = D()
obj.method()
print(D.__mro__)
Output:
Method from class B
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class
'__main__.A'>, <class 'object'>)
In this example:
We have four classes: A, B, C, and D.
D inherits from both B and C.
B and C both inherit from A.
When we call obj.method(), Python first searches for the method in D, then B,
then C, and finally in A.
The MRO is printed as <class '__main__.D'>, <class '__main__.B'>, <class
'__main__.C'>, <class '__main__.A'>, <class 'object'>.
Understanding the MRO is crucial for correctly resolving method calls and
attribute accesses, especially in cases of multiple inheritance. It helps avoid
ambiguity and ensures that the correct method or attribute is used based on the
class hierarchy.
4. Hierarchical Inheritance:
Hierarchical inheritance in Python refers to a scenario where one parent class has
multiple child classes, but these child classes do not inherit from each other.
Instead, they inherit directly from the same parent class, forming a hierarchical
structure.
Here's an example illustrating hierarchical inheritance in Python:
# Parent class
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name} makes a sound")
class Dog(Animal):
def speak(self):
print(f"{self.name} barks")
class Cat(Animal):
def speak(self):
print(f"{self.name} meows")
dog = Dog("Tommy")
cat = Cat("Mili")
In this example:
Animal is the parent class with a constructor method __init__ and a
method speak.
Dog and Cat are child classes of Animal, inheriting its attributes and
methods.
Each child class provides its own implementation of the speak method.
Instances of both child classes are created, and the speak method is called
on each of them. The output depends on which child class the instance
belongs to.
Hierarchical inheritance allows for creating a hierarchical structure of
classes where multiple subclasses inherit from a single parent class, but
they do not inherit from each other. Each child class can have its own
unique behavior while sharing common attributes and methods inherited
from the parent class.
5.Hybrid inheritance:
Hybrid inheritance in Python refers to a combination of multiple types of
inheritance, such as single, multiple, and multilevel inheritance, within a single
class hierarchy. It allows for creating complex class relationships by combining
features from different types of inheritance.
Here's an example illustrating hybrid inheritance in Python:
# Parent class 1
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name} makes a sound")
# Parent class 2
class Flyable:
def fly(self):
print(f"{self.name} can fly")
# super().__init__(name)
pass
# Grandchild class inheriting from Bird
class Parrot(Bird):
def speak(self):
print(f"{self.name} can talk")
class Dog(Animal):
def speak(self):
print(f"{self.name} barks")
bird = Bird("Sparrow")
parrot = Parrot("Polly")
dog = Dog("Buddy")
In this example:
Animal and Flyable are two parent classes, each with its own set of methods.
Bird is a child class inheriting from both Animal and Flyable. This demonstrates
multiple inheritance.
Parrot is a grandchild class inheriting from Bird, showing multilevel inheritance.
Dog is another grandchild class inheriting only from Animal, which demonstrates
single inheritance.
Instances of each class are created, and methods are called on them to showcase
the functionalities inherited from their parent classes.
Super() in Python:
In Python, super() is a built-in function used to access methods and attributes
from the parent class within a subclass. It provides a way to call methods of the
superclass without explicitly naming them, which makes the code more
maintainable and facilitates code reuse.
The super() function returns a proxy object that allows you to invoke methods of
the parent class.
Example:
First, define an Employee class:
class Employee:
def __init__(self, name, base_pay, bonus):
self.name = name
self.base_pay = base_pay
self.bonus = bonus
def get_pay(self):
return self.base_pay + self.bonus
The Employee class has three instance variables name, base_pay, and bonus. It
also has the get_pay() method that returns the total of base_pay and bonus.
Second, define the SalesEmployee class that inherits from the Employee class:
class SalesEmployee(Employee):
def __init__(self, name, base_pay, bonus, sales_incentive):
self.name = name
self.base_pay = base_pay
self.bonus = bonus
self.sales_incentive = sales_incentive
def get_pay(self):
return self.base_pay + self.bonus + self.sales_incentive
The SalesEmployee class has four instance variables name, base_pay, bonus, and
sales_incentive. It has the get_pay() method that overrides the get_pay() method
in the Employee class.
super().__init__()
The __init__() method of the SalesEmployee class has some parts that are the
same as the ones in the __init__() method of the Employee class.
To avoid duplication, you can call the __init__() method of Employee class from
the __init__() method of the SalesEmployee class.
To reference the Employee class in the SalesEmployee class, you use the super().
The super() returns a reference of the parent class from a child class.
The following redefines the SalesEmployee class that uses the super() to call the
__init__() method of the Employee class:
class SalesEmployee(Employee):
def __init__(self, name, base_pay, bonus, sales_incentive):
super().__init__(name, base_pay, bonus)
self.sales_incentive = sales_incentive
def get_pay(self):
return self.base_pay + self.bonus + self.sales_incentive
When you create an instance of the SalesEmployee class, Python will execute the
__init__() method in the SalesEmployee class. In turn, this __init__() method calls
the __init__() method of the Employee class to initialize the name, base_pay,
and bonus.
Delegating to other methods in the parent class
The get_pay() method of the SalesEmployee class has some logic that is already
defined in the get_pay() method of the Employee class. Therefore, you can reuse
this logic in the get_pay() method of the SalesEmployee class.
To do that, you can call the get_pay() method of the Employee class in the
get_pay() method of SalesEmployee class as follows:
class SalesEmployee(Employee):
def __init__(self, name, base_pay, bonus, sales_incentive):
super().__init__(name, base_pay, bonus)
self.sales_incentive = sales_incentive
def get_pay(self):
return super().get_pay() + self.sales_incentive
The following calls the get_pay() method of the Employee class from the
get_pay() method in the SalesEmployee class:
super().get_pay()
When you call the get_pay() method from an instance of the SalesEmployee
class, it calls the get_pay() method from the parent class (Employee) and return
the sum of the result of the super().get_pay() method with the sales_incentive.
Complete Program:
class Employee:
def __init__(self, name, base_pay, bonus):
self.name = name
self.base_pay = base_pay
self.bonus = bonus
def get_pay(self):
return self.base_pay + self.bonus
class SalesEmployee(Employee):
def __init__(self, name, base_pay, bonus, sales_incentive):
super().__init__(name, base_pay, bonus)
self.sales_incentive = sales_incentive
def get_pay(self):
return super().get_pay() + self.sales_incentive
if __name__ == '__main__':
sales_employee = SalesEmployee('John', 5000, 1000, 2000)
print(sales_employee.get_pay()) # 8000
Deligation in python:
Delegation in Python refers to the practice of passing the responsibility for a
particular task to another object. It is a design pattern where an object forwards
method calls and attribute access to another object, which is responsible for
handling them. Delegation is commonly used to achieve code reuse and
modularization by composing objects rather than inheriting behavior from a
superclass.
Here's a simple example to illustrate delegation in Python:
class Printer:
def print_document(self, document):
print(f"Printing document: {document}")
class Scanner:
def scan_document(self, document):
print(f"Scanning document: {document}")
class Copier:
def __init__(self):
self.printer = Printer()
self.scanner = Scanner()
copier = Copier()
copier.copy_document("Sample Document")
In this example:
We have three classes: Printer, Scanner, and Copier.
Printer and Scanner represent objects responsible for printing and scanning
documents, respectively.
Copier is a class that delegates printing and scanning tasks to instances of Printer
and Scanner.
In the Copier class, __init__ method initializes instances of Printer and Scanner.
The copy_document method in the Copier class delegates the scanning and
printing tasks to the Scanner and Printer objects, respectively.