0% found this document useful (0 votes)
2 views49 pages

Unit 2 Python

Uploaded by

Shweta Verma
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views49 pages

Unit 2 Python

Uploaded by

Shweta Verma
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 49

Great!

Let’s break down the OOPs (Object-Oriented Programming)


concepts in Python in a simple and detailed way — perfect for
understanding, interview prep, or adding to your notes/project report.

OOPs Concepts in Python


Object-Oriented Programming (OOP) is a programming paradigm based
on the concept of "objects", which can contain data (attributes) and
functions (methods). Python supports OOP fully.

Main OOP Concepts in Python:

Concept Description

Blueprint or template for creating objects. Defines


1. Class
attributes and methods.

2. Object Instance of a class. Represents real-world entities.

Allows one class to inherit the properties and methods


3. Inheritance
of another.

4. Hides internal data by restricting direct access using


Encapsulation private/protected members.

5. Ability to take many forms – same method name


Polymorphism behaving differently in different contexts.

Hides complex implementation and shows only the


6. Abstraction
essential features.
1. Class and Object
class Student:
def __init__(self, name, roll):
self.name = name
self.roll = roll

def show(self):
print(f"Name: {self.name}, Roll: {self.roll}")

# Object
s1 = Student("Shweta", 101)
s1.show()

2. Inheritance
class Animal:
def speak(self):
print("Animal speaks")

class Dog(Animal): # Inheriting Animal


def bark(self):
print("Dog barks")

d = Dog()
d.speak()
d.bark()

3. Encapsulation
class Account:
def __init__(self):
self.__balance = 0 # private attribute

def deposit(self, amount):


self.__balance += amount

def get_balance(self):
return self.__balance

a = Account()
a.deposit(1000)
print(a.get_balance())
4. Polymorphism
class Cat:
def sound(self):
print("Meow")

class Dog:
def sound(self):
print("Bark")

# Same method name but different behavior


for animal in (Cat(), Dog()):
animal.sound()

5. Abstraction (using ABC module)


from abc import ABC, abstractmethod

class Vehicle(ABC):
@abstractmethod
def start(self):
pass
class Car(Vehicle):
def start(self):
print("Car started")

c = Car()
c.start()

Why Use OOP in Python?


• Organizes code better
• Promotes reusability and scalability
• Mimics real-world structure
• Makes maintenance easier

Sure Shweta! Here’s a detailed explanation of “Classes in Python”,


perfect for understanding, interviews, and adding to your final project
report or notes.

Classes in Python (Detailed Explanation)

Definition:
A class in Python is a user-defined blueprint or prototype from which
objects are created. Classes encapsulate data (attributes) and functions
(methods) that operate on the data.

Key Concepts of a Class:

Term Description

class
Used to define a new class.
keyword

Special method (constructor) that runs when a new


__init__()
object is created.

self keyword Refers to the current instance of the class.

Attributes Variables that belong to the class.

Functions that belong to the class and define behaviors of


Methods
the objects.

Basic Syntax:
class ClassName:
def __init__(self, attribute1, attribute2):
self.attribute1 = attribute1
self.attribute2 = attribute2

def method1(self):
print("This is a method")

Example: Student Class


class Student:
def __init__(self, name, roll):
self.name = name # attribute
self.roll = roll

def display_info(self): # method


print(f"Name: {self.name}, Roll No: {self.roll}")

Creating Objects:
s1 = Student("Shweta", 101)
s2 = Student("Amit", 102)

s1.display_info()
s2.display_info()

Output:
Name: Shweta, Roll No: 101
Name: Amit, Roll No: 102

Why Use Classes?


• Organizes code into reusable structures
• Models real-world entities (like Student, Car, Employee)
• Enables OOP principles: inheritance, encapsulation, etc.
• Easier to manage large programs

Types of Methods in a Class:

Method Type Description

Instance
Works with instance (object) data. Most common type.
Method

Works with class-level data using @classmethod


Class Method
decorator.

Independent of class or instance. Uses @staticmethod


Static Method
decorator.

Example with All Method Types:


class Example:
class_variable = "Class Level"

def __init__(self, instance_var):


self.instance_var = instance_var
def instance_method(self):
print("Instance Variable:", self.instance_var)

@classmethod
def class_method(cls):
print("Class Variable:", cls.class_variable)

@staticmethod
def static_method():
print("Static method doesn’t access class or instance data")

Great question, Shweta! Let’s dive into Constructors in Python and


their types — explained clearly with examples, suitable for interviews,
notes, or project reports.

Constructors in Python

What is a Constructor?
A constructor is a special method in a class that is automatically called
when an object of the class is created.
In Python, the constructor method is always named: __init__().

Purpose:
• Initialize the object with default or user-defined values.
• Set up necessary variables or states at the time of object creation.

Syntax of Constructor:
class ClassName:
def __init__(self): # Constructor
# initialization code

Types of Constructors in Python


Python supports three types of constructors:

1⃣ Default Constructor
• No parameters except self
• Used when no initialization is needed or values are hardcoded.
class Demo:
def __init__(self): # Default constructor
print("This is a default constructor")

obj = Demo()

2⃣ Parameterized Constructor
• Takes arguments during object creation.
• Used to initialize object with specific values.
class Student:
def __init__(self, name, roll): # Parameterized constructor
self.name = name
self.roll = roll

def show(self):
print(f"Name: {self.name}, Roll: {self.roll}")

s = Student("Shweta", 101)
s.show()

3⃣ Constructor Overloading (Not directly supported)


Python does not support method overloading (same name, different
parameters) directly like Java or C++.
But we can simulate it using default arguments or *args.
class Example:
def __init__(self, name="Guest"): # Simulated constructor
overloading
self.name = name
def greet(self):
print(f"Hello, {self.name}")

e1 = Example()
e2 = Example("Shweta")

e1.greet() # Hello, Guest


e2.greet() # Hello, Shweta

Summary Table:

Type Description Example Use

Default Initializing values like


No parameters, basic setup
Constructor counters, lists

Takes parameters to Student name, roll


Parameterized
initialize object attributes number, etc.

Simulated Using default values or Flexible initialization


Overloading *args to mimic overload based on arguments

Absolutely Shweta! Here's a clear and detailed explanation of Data


Hiding in Python, ideal for your notes, interviews, or project report.
Data Hiding in Python

What is Data Hiding?


Data hiding is an Object-Oriented Programming (OOP) concept that
restricts access to internal object details — like variables or methods —
to prevent direct modification or misuse from outside the class.
It helps achieve encapsulation and increases security and control over
the data.

Why is Data Hiding Important?


• Protects sensitive data
• Prevents accidental changes
• Allows controlled access via methods (getters and setters)

How Data Hiding Works in Python


Python does not have access specifiers like private, public, or protected,
but it uses naming conventions:

Syntax
Type Access Level
Example

Public Variable self.name Accessible from anywhere

Protected Suggests internal use only (can be


self._name
Variable accessed)
Syntax
Type Access Level
Example

Not accessible directly from outside


Private Variable self.__name
the class

Example of Data Hiding:


class Student:
def __init__(self, name, marks):
self.__name = name # private attribute
self.__marks = marks # private attribute

def display(self):
print(f"Name: {self.__name}, Marks: {self.__marks}")

def update_marks(self, new_marks):


if new_marks > 0:
self.__marks = new_marks
else:
print("Invalid marks!")

s = Student("Shweta", 85)
s.display()

# Trying to access private data directly (will cause error)

# print(s.__marks)

# Correct way to update


s.update_marks(90)
s.display()

Accessing Private Variables (if needed):


Even though private variables can’t be accessed directly, you can still
access them using:
print(s._Student__marks) # Not recommended, but possible
This is called Name Mangling — Python changes __marks to
_ClassName__marks.

Best Practice:
Always use getter and setter methods to access or update private data.
class BankAccount:
def __init__(self, balance):
self.__balance = balance
def get_balance(self):
return self.__balance

def set_balance(self, amount):


if amount >= 0:
self.__balance = amount
else:
print("Invalid balance!")

acc = BankAccount(5000)
print(acc.get_balance())
acc.set_balance(6000)

Summary Table:

Concept Keyword/Syntax Example

Public self.name Accessible everywhere

Protected self._name Use within class/subclass

Private self.__name Only within class, uses name mangling


Creating classes in Python is a key part of object-oriented programming
(OOP). A class is like a blueprint for creating objects (instances), which
bundle data and functionality together.
Here’s a basic example to show you how to create and use a class in
Python:

Basic Class Syntax


class Dog:
def __init__(self, name, age):
self.name = name # instance variable
self.age = age

def bark(self):
print(f"{self.name} says woof!")

def get_age(self):
return self.age

Creating and Using Objects


my_dog = Dog("Buddy", 3)
my_dog.bark() # Output: Buddy says woof!
print(my_dog.get_age()) # Output: 3
Explanation:
• class Dog: – Defines a class named Dog.
• __init__ – The constructor method, called automatically when a
new object is created.
• self – Refers to the current instance of the class.
• bark and get_age – Methods (functions defined inside a class).

Some Extra Notes:


• You can add more methods and attributes.
• Use self.attribute to access instance variables.
• Python doesn’t have explicit access modifiers like public or private
(though you can use _ or __ to indicate they shouldn’t be accessed
externally).

Great! Let's dive into instance methods in Python

What Are Instance Methods?


Instance methods are functions defined inside a class that operate on
an instance of that class. They have access to both the instance's data
(self) and other methods.
Syntax of an Instance Method:
class MyClass:
def instance_method(self):
# method body
pass
The first parameter is always self, which represents the instance calling
the method.

Example:
class Dog:
def __init__(self, name):
self.name = name # instance variable

def greet(self):
print(f"Hi, I'm {self.name}!")

Usage:
d = Dog("Max")
d.greet() # Output: Hi, I'm Max!

Key Points:
• self gives you access to the instance's attributes and other
methods.
• You must call the method on an instance (e.g., d.greet()), not on
the class unless you pass an instance manually.
• You can have multiple instance methods that interact with each
other.

Another Example:
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance

def deposit(self, amount):


self.balance += amount
print(f"${amount} deposited. New balance: ${self.balance}")

def withdraw(self, amount):


if amount > self.balance:
print("Insufficient funds.")
else:
self.balance -= amount
print(f"${amount} withdrawn. New balance: ${self.balance}")
acc = BankAccount("Alice", 1000)
acc.deposit(200) # $200 deposited. New balance: $1200
acc.withdraw(1500) # Insufficient funds.

Sure Shweta! Here's a complete explanation of Special Methods in


Python, also called Magic Methods or Dunder Methods — perfect for
interviews, notes, or your project report.

Special Methods in Python

What Are Special Methods?


Special methods are predefined methods in Python that have double
underscores at the beginning and end of their names.
They are also called Magic Methods or Dunder Methods (DUal
UNDERscore).
These methods customize the behavior of your objects in built-in
operations like printing, adding, comparing, etc.

Common Special Methods with Examples

Method Name Purpose / Use

__init__() Constructor – initializes object state


Method Name Purpose / Use

__str__() Returns a string representation of the object

__repr__() Returns an official string representation (for debugging)

__len__() Used by len() function

__add__() Used to define behavior for + operator

__eq__() Compares two objects with ==

__del__() Destructor – called when an object is deleted

1. __init__() – Constructor
class Student:
def __init__(self, name):
self.name = name

2. __str__() – For Print


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

def __str__(self):
return f"Student name is {self.name}"
s = Student("Shweta")
print(s) # Automatically calls __str__()

3. __len__() – Customize len()


class MyList:
def __init__(self, data):
self.data = data

def __len__(self):
return len(self.data)

ml = MyList([1, 2, 3])
print(len(ml)) # Output: 3

4. __add__() – Operator Overloading


class Marks:
def __init__(self, score):
self.score = score

def __add__(self, other):


return self.score + other.score

m1 = Marks(40)
m2 = Marks(60)
print(m1 + m2) # Output: 100

5. __eq__() – Comparison with ==


class Person:
def __init__(self, age):
self.age = age

def __eq__(self, other):


return self.age == other.age

p1 = Person(21)
p2 = Person(21)
print(p1 == p2) # True

6. __del__() – Destructor
class Sample:
def __del__(self):
print("Object is being deleted")

obj = Sample()
del obj

Summary Table:

Method Triggered When You... Purpose

__init__() Create an object Initialize object

__str__() Print the object Return user-friendly string

__repr__() Use in debugging (e.g. IDE) Return official representation

__len__() Use len() Define object length

__add__() Use + Add two objects

__eq__() Use == Compare two objects

__del__() Delete an object Clean-up (optional)

Absolutely, Shweta! Here's a clear and detailed explanation of Class


Variables in Python, great for your notes, interviews, or your project
report.

Class Variables in Python


Definition:
A class variable is a variable that is shared by all objects (instances) of a
class.
It is defined inside the class, but outside any instance methods (like
__init__()).

Key Points:

Feature Description

All instances share the same class variable unless


Shared Value
specifically overridden.

Defined Declared directly inside the class but outside any


Location method.

Access Accessed using ClassName.variable_name or


Method self.variable_name

Syntax Example:
class Student:
school = "JEC College" # Class variable

def __init__(self, name):


self.name = name # Instance variable
s1 = Student("Shweta")
s2 = Student("Amit")

print(s1.school) # Output: JEC College


print(s2.school) # Output: JEC College

Modifying Class Variable:


• You should change it using ClassName.variable, not self.variable,
or it will create a new instance variable.
Student.school = "KV School"

print(s1.school) # KV School
print(s2.school) # KV School

Class Variable vs Instance Variable

Feature Class Variable Instance Variable

Belongs To Class Individual object

Shared Among No (each object has its


Yes
Objects own copy)

Outside all methods,


Defined Inside __init__() using self
inside class
Feature Class Variable Instance Variable

Accessed Via ClassName or object Only via object using self

Example With Both Variables:


class Employee:
company = "TCS" # Class variable

def __init__(self, name, salary):


self.name = name # Instance variable
self.salary = salary # Instance variable

emp1 = Employee("Shweta", 50000)


emp2 = Employee("Amit", 60000)

print(emp1.company) # TCS
print(emp2.company) # TCS

Employee.company = "Infosys" # Change class variable

print(emp1.company) # Infosys
print(emp2.company) # Infosys
Summary:
• Use class variables when a value should be common to all objects
(like school, company, etc.).
• Use instance variables for object-specific data (like name, marks,
etc.).

Absolutely Shweta! Here's a complete and clear explanation of


Inheritance and its Types in Python — perfect for your interview prep,
project report, or handwritten notes.

Inheritance in Python

Definition:
Inheritance is an Object-Oriented Programming (OOP) concept where
one class (called child or derived class) inherits the properties and
methods of another class (called parent or base class).

Purpose of Inheritance:
• Code reusability
• Extensibility
• Helps follow the DRY (Don't Repeat Yourself) principle

Basic Syntax:
class Parent:
def display(self):
print("This is Parent class")

class Child(Parent): # Inheriting from Parent


def show(self):
print("This is Child class")

c = Child()
c.display() # Inherited from Parent
c.show() # Own method

Types of Inheritance in Python

Type Description Example Classes

1. Single One child inherits from one parent A→B

One child inherits from multiple


2. Multiple A, B → C
parents

3. Multilevel Inheritance in a chain of classes A→B→C

4. Multiple children inherit from the


A → B, A → C
Hierarchical same parent
Type Description Example Classes

Combination of multiple inheritance (Mix of above


5. Hybrid
types types)

1. Single Inheritance
class Parent:
def parent_method(self):
print("Parent class")

class Child(Parent):
def child_method(self):
print("Child class")

c = Child()
c.parent_method()

2. Multiple Inheritance
class Father:
def skills(self):
print("Gardening")
class Mother:
def hobbies(self):
print("Cooking")

class Child(Father, Mother):


pass

c = Child()
c.skills()
c.hobbies()

3. Multilevel Inheritance
class Grandparent:
def gp_method(self):
print("Grandparent")

class Parent(Grandparent):
def p_method(self):
print("Parent")

class Child(Parent):
def c_method(self):
print("Child")

c = Child()
c.gp_method()
c.p_method()

4. Hierarchical Inheritance
class Parent:
def p_method(self):
print("Parent class")

class Child1(Parent):
def c1_method(self):
print("Child1")

class Child2(Parent):
def c2_method(self):
print("Child2")

c1 = Child1()
c2 = Child2()
c1.p_method()
c2.p_method()

5. Hybrid Inheritance
Hybrid inheritance is a mix of multiple and multilevel (or any
combination). Python uses Method Resolution Order (MRO) to manage
this.
class A:
def method_a(self):
print("Class A")

class B(A):
def method_b(self):
print("Class B")

class C:
def method_c(self):
print("Class C")

class D(B, C):


def method_d(self):
print("Class D")

obj = D()
obj.method_a()
obj.method_c()

Summary Table:

Inheritance Type Key Feature Example Classes

Single One parent, one child A→B

Multiple Multiple parents, one child A, B → C

Multilevel Chain of inheritance A→B→C

Hierarchical One parent, multiple children A → B, A → C

Hybrid Mixed types A → B, A → C → D

Nice one! Let’s talk about Type Identification in Python

What is Type Identification?


Type identification means figuring out (or checking) the data type of a
variable or value during runtime.

Common Tools for Type Identification:


1. type() – Built-in function to get the type of a variable
x = 42
print(type(x)) # <class 'int'>

y = "hello"
print(type(y)) # <class 'str'>

2. isinstance() – To check if an object is of a specific type


x = 3.14
print(isinstance(x, float)) # True
print(isinstance(x, int)) # False

You can check against multiple types at once:


print(isinstance(x, (int, float))) # True

3. __class__ attribute – Less common, but gives the type directly


x = [1, 2, 3]
print(x.__class__) # <class 'list'>
Type Identification with Custom Classes:
class Animal:
pass

class Dog(Animal):
pass

d = Dog()

print(type(d)) # <class '__main__.Dog'>


print(isinstance(d, Dog)) # True
print(isinstance(d, Animal)) # True (because Dog inherits from Animal)

type() vs isinstance():

Feature type() isinstance()

Exact match Yes No (allows subclasses)

Inheritance Ignores subclass Aware of inheritance

Use case Checking strict type Validating types with flexibility


Awesome choice! Let’s talk about Polymorphism in Python – a key
concept in object-oriented programming (OOP)

What is Polymorphism?
Polymorphism means "many forms". In Python, it refers to the ability
to use the same method name across different classes, with each class
having its own implementation.
In short: same interface, different behavior.

Basic Example:
class Dog:
def speak(self):
return "Woof!"

class Cat:
def speak(self):
return "Meow!"

def make_sound(animal):
print(animal.speak())
d = Dog()
c = Cat()
make_sound(d) # Output: Woof!
make_sound(c) # Output: Meow!

Even though Dog and Cat are different classes, both have a .speak()
method — that's polymorphism!

Real-World Analogy:
Think of a "drive" method:
• A Car drives on roads.
• A Boat drives on water.

• A Plane drives in the air (kind of ).


But they all respond to .drive() — differently!

Polymorphism with Inheritance:


class Animal:
def speak(self):
return "Some sound"

class Dog(Animal):
def speak(self):
return "Bark"
class Cat(Animal):
def speak(self):
return "Purr"
animals = [Dog(), Cat(), Animal()]

for a in animals:
print(a.speak())

Output:
Bark
Purr
Some sound

Duck Typing in Python (Dynamic Polymorphism)


Python uses duck typing: “If it walks like a duck and quacks like a
duck…”
It doesn’t care about the type, only whether the method exists:
class Duck:
def quack(self):
print("Quack!")
class Person:
def quack(self):
print("I'm pretending to be a duck!")

def make_it_quack(thing):
thing.quack()

make_it_quack(Duck()) # Quack!
make_it_quack(Person()) # I'm pretending to be a duck!

Recap:

• Polymorphism = Same method name, different behavior

• Can be used via inheritance or duck typing

• Helps in writing clean, extensible, and flexible code

Nice! You're hitting all the big topics in Python


Let’s break down Iterators, Generators, and Decorators — three
powerful features that really level up your Python game.

1. Iterators

What’s an Iterator?
An iterator is an object that lets you loop through items, one at a time.
To be an iterator, an object must implement:
• __iter__() → returns the iterator object itself
• __next__() → returns the next item (raises StopIteration when
done)

Example:
nums = [1, 2, 3]
it = iter(nums)

print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
# next(it) would raise StopIteration
You can also make your own custom iterator:
class CountUpTo:
def __init__(self, max):
self.max = max
self.current = 1

def __iter__(self):
return self
def __next__(self):
if self.current > self.max:
raise StopIteration
val = self.current
self.current += 1
return val

for num in CountUpTo(3):


print(num)

2. Generators

What’s a Generator?
A generator is a simpler way to create an iterator using the yield
keyword.

Example:
def count_up_to(n):
current = 1
while current <= n:
yield current
current += 1
for num in count_up_to(3):
print(num)

When you call a generator function, it returns a generator object


you can iterate over.

Why Use Generators?


• Memory-efficient (doesn’t store all items in memory)
• Great for large datasets or infinite sequences

3. Decorators

What’s a Decorator?
A decorator is a function that wraps another function to extend or
modify its behavior — like adding toppings to a pizza .

Basic Decorator:
def my_decorator(func):
def wrapper():
print("Before the function runs")
func()
print("After the function runs")
return wrapper
@my_decorator
def say_hello():
print("Hello!")

say_hello()

Output:
Before the function runs
Hello!
After the function runs

Decorators with Arguments:


def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator

@repeat(3)
def greet():
print("Hi!")

greet()

Decorators Are Used For:


• Logging
• Timing
• Authorization
• Caching
• Validation
• Flask/Django routes

TL;DR:

Feature Purpose Key Word(s)

Iterator Looping manually/custom sequences __iter__, __next__

Generator Yield values one at a time (on demand) yield

Decorator Wrap/extend function behavior @decorator


Ooh, custom exceptions — now you’re really stepping into pro Python
dev territory

Custom Exception Classes in Python


Python has many built-in exceptions like ValueError, TypeError, etc. But
sometimes, you want your own specific errors to make your code more
readable, reusable, and easier to debug.

How to Create a Custom Exception


You just define a class that inherits from Exception (or a subclass of it):
class MyCustomError(Exception):
pass
Then you can raise it like any other exception:
raise MyCustomError("Something went wrong!")

Example: Custom Exception with Extra Info


class BalanceTooLowError(Exception):
def __init__(self, message="Balance is too low!", balance=None):
super().__init__(message)
self.balance = balance
def withdraw(amount, balance):
if amount > balance:
raise BalanceTooLowError(balance=balance)
return balance - amount

try:
withdraw(200, 100)
except BalanceTooLowError as e:
print(e) # Output: Balance is too low!
print(e.balance) # Output: 100

Why Use Custom Exceptions?


• Makes your code more expressive
• Helps with precise error handling
• Cleaner than using general-purpose exceptions for everything

You Can Also Inherit from Built-in Errors


If your custom error is similar to a built-in one, inherit from that:
class InvalidAgeError(ValueError):
pass

Pro Tip: Create a Hierarchy


class AppError(Exception): # Base app exception
pass

class DatabaseError(AppError):
pass

class ValidationError(AppError):
pass
This way you can catch all your app-specific errors with one block:
try:
# something risky
pass
except AppError as e:
print(f"Custom app error: {e}")

You might also like