0% found this document useful (0 votes)
4 views67 pages

Python Notes Endsem

The document provides comprehensive study material on Python programming, covering fundamentals such as identifiers, keywords, control flow, data types, and operator precedence. It includes examples of syntax, indentation, comments, and various programming constructs like loops and conditionals. Additionally, it introduces advanced features like the match statement and command-line arguments, making it a useful resource for learners at different levels.

Uploaded by

sarac10
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)
4 views67 pages

Python Notes Endsem

The document provides comprehensive study material on Python programming, covering fundamentals such as identifiers, keywords, control flow, data types, and operator precedence. It includes examples of syntax, indentation, comments, and various programming constructs like loops and conditionals. Additionally, it introduces advanced features like the match statement and command-line arguments, making it a useful resource for learners at different levels.

Uploaded by

sarac10
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

PYTHON PROGRAMMING - STUDY MATERIAL

UNIT I: PYTHON FUNDAMENTALS & CONTROL FLOW


1. IDENTIFIERS & KEYWORDS
Theory:
Identifiers are names given to variables, functions, classes, etc. Keywords are reserved words with special meaning in
Python.

Rules for Identifiers:


Start with letter (a-z, A-Z) or underscore (_)
Can contain letters, digits, underscores
Case-sensitive
Cannot be keywords

Important Definitions (Viva):


Identifier: User-defined name for variables, functions, classes
Keyword: Reserved word with predefined meaning (if, for, while, etc.)
Valid identifier: Follows naming rules and isn't a keyword

Examples:
# Valid identifiers
name = "Python"
_age = 25
student1 = "John"
my_variable = 100

# Invalid identifiers
# 1name = "Error" # Starts with digit
# if = 5 # Keyword used
# my-var = 10 # Hyphen not allowed

Python Keywords (35 total):


import keyword
print([Link])
# ['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await',
# 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except',
# 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is',
# 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return',
# 'try', 'while', 'with', 'yield']

2. INDENTATION & COMMENTS


Theory:
Python uses indentation (spaces/tabs) to define code blocks instead of braces {} .

Indentation Rules:
Use 4 spaces (PEP 8 standard)

PYTHON PROGRAMMING - STUDY MATERIAL 1


Be consistent throughout the program
All statements at same level must have same indentation

Types of Comments:
1. Single-line: # This is a comment

2. Multi-line: """This is a multi-line comment"""

3. Documentation strings (docstrings): Function/class documentation

Examples:
# Single-line comment
def greet(name):
"""
This is a docstring.
Function to greet a person.
"""
if name: # Check if name exists
print(f"Hello, {name}!")
else:
print("Hello, World!")

# Multi-line comment
"""
This program demonstrates
different types of comments
in Python
"""

3. UNICODE & ENCODING


Theory:
Python 3 uses Unicode by default for string handling. Encoding converts text to bytes.

Important Definitions:
Unicode: Universal character encoding standard
UTF-8: Variable-length encoding for Unicode
Encoding: Converting text to bytes
Decoding: Converting bytes to text

Examples:
# Unicode strings (default in Python 3)
text = "Hello 🐍
Python"
hindi = "नमस्ते"
emoji = " 😊🚀"

# Encoding and decoding


message = "Hello World"
encoded = [Link]('utf-8') # Convert to bytes
decoded = [Link]('utf-8') # Convert back to string

print(f"Original: {message}")

PYTHON PROGRAMMING - STUDY MATERIAL 2


print(f"Encoded: {encoded}")
print(f"Decoded: {decoded}")

4. DATA TYPES & TYPE HINTS


Theory:
Python has built-in data types. Type hints provide optional static typing information.

Built-in Data Types:


# Numeric types
integer_num = 42 # int
float_num = 3.14 # float
complex_num = 3 + 4j # complex

# Text type
text = "Hello" # str

# Boolean type
is_valid = True # bool

# Sequence types
my_list = [1, 2, 3] # list
my_tuple = (1, 2, 3) # tuple
my_range = range(5) # range

# Mapping type
my_dict = {"a": 1} # dict

# Set types
my_set = {1, 2, 3} # set
frozen = frozenset([1,2]) # frozenset

Type Hints:
from typing import List, Dict, Optional

def calculate_average(numbers: List[int]) -> float:


"""Calculate average of a list of numbers."""
return sum(numbers) / len(numbers)

def get_student_info(name: str, age: int) -> Dict[str, str]:


"""Return student information."""
return {"name": name, "age": str(age)}

# Optional type (can be None)


def greet(name: Optional[str] = None) -> str:
if name:
return f"Hello, {name}!"
return "Hello, World!"

5. OBJECT IDENTITY VS EQUALITY


Theory:

PYTHON PROGRAMMING - STUDY MATERIAL 3


Identity ( is ): Checks if two variables point to same object in memory
Equality ( == ): Checks if two objects have same value

Important Definitions:
Object identity: Unique identifier of an object in memory
Object equality: Comparison of object values
id() function: Returns object's identity (memory address)

Examples:
# Identity vs Equality
a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(f"a == b: {a == b}") # True (same values)


print(f"a is b: {a is b}") # False (different objects)
print(f"a is c: {a is c}") # True (same object)

print(f"id(a): {id(a)}")
print(f"id(b): {id(b)}")
print(f"id(c): {id(c)}")

# Special case with small integers


x = 5
y = 5
print(f"x is y: {x is y}") # True (Python caches small integers)

# With None
value = None
print(f"value is None: {value is None}") # Correct way
print(f"value == None: {value == None}") # Works but not recommended

6. OPERATOR PRECEDENCE & ASSOCIATIVITY


Theory:
Operator precedence determines order of operations. Associativity determines order when operators have same
precedence.

Precedence (High to Low):


1. () - Parentheses
2. * - Exponentiation
3. +x, -x, ~x - Unary operators
4. , /, //, % - Multiplication, Division
5. +, - - Addition, Subtraction
6. <<, >> - Bitwise shifts
7. & - Bitwise AND
8. ^ - Bitwise XOR
9. | - Bitwise OR
10. ==, !=, <, >, <=, >=, is, in - Comparisons
11. not - Boolean NOT

PYTHON PROGRAMMING - STUDY MATERIAL 4


12. and - Boolean AND
13. or - Boolean OR

Examples:
# Precedence examples
result1 = 2 + 3 * 4 # 14 (not 20)
result2 = (2 + 3) * 4 # 20
result3 = 2 ** 3 ** 2 # 512 (right associative)
result4 = (2 ** 3) ** 2 # 64

# Boolean operators
result5 = True or False and False # True (and has higher precedence)
result6 = (True or False) and False # False

print(f"2 + 3 * 4 = {result1}")
print(f"(2 + 3) * 4 = {result2}")
print(f"2 ** 3 ** 2 = {result3}")
print(f"(2 ** 3) ** 2 = {result4}")

7. INPUT/OUTPUT & FORMATTING


Theory:
Python provides various ways to format output: f-strings (modern), .format() , and old % formatting.

F-strings (Recommended):
name = "Alice"
age = 25
score = 95.67

# Basic f-string
print(f"Name: {name}, Age: {age}")

# With expressions
print(f"Next year, {name} will be {age + 1}")

# Formatting numbers
print(f"Score: {score:.2f}%") # 2 decimal places
print(f"Score: {score:.0f}%") # No decimal places

# Alignment and width


print(f"{'Name':<10} {'Age':>5}") # Left align, right align
print(f"{name:<10} {age:>5}")

Format Method:
# Using .format()
template = "Name: {}, Age: {}"
print([Link](name, age))

# With indices
template2 = "Name: {0}, Age: {1}, Name again: {0}"
print([Link](name, age))

# With keywords

PYTHON PROGRAMMING - STUDY MATERIAL 5


template3 = "Name: {n}, Age: {a}"
print([Link](n=name, a=age))

Old % Formatting:
# % formatting (legacy)
print("Name: %s, Age: %d" % (name, age))
print("Score: %.2f%%" % score) # %% for literal %

Input:
# Getting user input
user_name = input("Enter your name: ")
user_age = int(input("Enter your age: ")) # Convert to int

print(f"Hello {user_name}, you are {user_age} years old!")

8. COMMAND-LINE ARGUMENTS
Theory:
Use [Link] to access command-line arguments passed to Python script.

Examples:
import sys

# [Link]
print(f"Script name: {[Link][0]}")
print(f"Number of arguments: {len([Link])}")
print(f"Arguments: {[Link]}")

if len([Link]) > 1:
print(f"First argument: {[Link][1]}")

# Usage: python [Link] arg1 arg2 arg3


# Output:
# Script name: [Link]
# Number of arguments: 4
# Arguments: ['[Link]', 'arg1', 'arg2', 'arg3']
# First argument: arg1

Practical Example:
import sys

def calculator():
if len([Link]) != 4:
print("Usage: python [Link] <num1> <operator> <num2>")
return

try:
num1 = float([Link][1])
operator = [Link][2]
num2 = float([Link][3])

PYTHON PROGRAMMING - STUDY MATERIAL 6


if operator == '+':
result = num1 + num2
elif operator == '-':
result = num1 - num2
elif operator == '*':
result = num1 * num2
elif operator == '/':
result = num1 / num2
else:
print("Invalid operator")
return

print(f"{num1} {operator} {num2} = {result}")


except ValueError:
print("Invalid numbers")

if __name__ == "__main__":
calculator()

9. CONTROL FLOW: IF, ELIF, ELSE


Theory:
Conditional statements execute different code blocks based on conditions.

Syntax:
if condition1:
# code block 1
elif condition2:
# code block 2
else:
# code block 3

Examples:
# Simple if-else
age = 18
if age >= 18:
print("You can vote!")
else:
print("You cannot vote yet.")

# Multiple conditions
score = 85
if score >= 90:
grade = 'A'
elif score >= 80:
grade = 'B'
elif score >= 70:
grade = 'C'
elif score >= 60:
grade = 'D'
else:
grade = 'F'

print(f"Your grade is: {grade}")

PYTHON PROGRAMMING - STUDY MATERIAL 7


# Nested conditions
num = 15
if num > 0:
if num % 2 == 0:
print("Positive even number")
else:
print("Positive odd number")
elif num < 0:
print("Negative number")
else:
print("Zero")

10. MATCH STATEMENT (PEP 634)


Theory:
Python 3.10+ introduced match statement (similar to switch-case in other languages).

Basic Syntax:
match value:
case pattern1:
# code
case pattern2:
# code
case _: # default case
# code

Examples:
# Basic match
def handle_status(status):
match status:
case 200:
return "OK"
case 404:
return "Not Found"
case 500:
return "Internal Server Error"
case _:
return "Unknown Status"

# Match with conditions


def categorize_number(x):
match x:
case n if n < 0:
return "Negative"
case 0:
return "Zero"
case n if n > 0 and n <= 10:
return "Small positive"
case n if n > 10:
return "Large positive"

# Match with data structures


def process_data(data):

PYTHON PROGRAMMING - STUDY MATERIAL 8


match data:
case []:
return "Empty list"
case [x]:
return f"Single item: {x}"
case [x, y]:
return f"Two items: {x}, {y}"
case [x, *rest]:
return f"First: {x}, Rest: {rest}"
case {"name": name, "age": age}:
return f"Person: {name}, Age: {age}"
case _:
return "Unknown format"

11. LOOPS: FOR & WHILE


For Loops:
# Basic for loop
for i in range(5):
print(f"Iteration {i}")

# Loop through list


fruits = ["apple", "banana", "orange"]
for fruit in fruits:
print(f"I like {fruit}")

# Loop through string


for char in "Python":
print(char)

# Loop through dictionary


student = {"name": "Alice", "age": 20, "grade": "A"}
for key, value in [Link]():
print(f"{key}: {value}")

While Loops:
# Basic while loop
count = 0
while count < 5:
print(f"Count: {count}")
count += 1

# While with condition


user_input = ""
while user_input.lower() != "quit":
user_input = input("Enter 'quit' to exit: ")
if user_input.lower() != "quit":
print(f"You entered: {user_input}")

12. BREAK, CONTINUE, ELSE WITH LOOPS


Theory:
break: Exit loop completely

PYTHON PROGRAMMING - STUDY MATERIAL 9


continue: Skip current iteration
else: Executes when loop completes normally (not broken)

Examples:
# Break example
for i in range(10):
if i == 5:
break
print(i) # Prints 0, 1, 2, 3, 4

# Continue example
for i in range(10):
if i % 2 == 0:
continue
print(i) # Prints 1, 3, 5, 7, 9

# Else with loops


def find_number(numbers, target):
for num in numbers:
if num == target:
print(f"Found {target}!")
break
else:
print(f"{target} not found!")

find_number([1, 2, 3, 4, 5], 3) # Found 3!


find_number([1, 2, 3, 4, 5], 6) # 6 not found!

13. ITERATOR PROTOCOL


Theory:
Iterator protocol defines how objects can be iterated. Objects must implement __iter__() and __next__() methods.

Important Definitions:
Iterator: Object that implements iterator protocol
Iterable: Object that can return an iterator
__iter__() : Returns iterator object
__next__() : Returns next item in sequence

Examples:
# Creating custom iterator
class NumberIterator:
def __init__(self, max_num):
self.max_num = max_num
[Link] = 0

def __iter__(self):
return self

def __next__(self):
if [Link] < self.max_num:
[Link] += 1
return [Link]

PYTHON PROGRAMMING - STUDY MATERIAL 10


else:
raise StopIteration

# Using the iterator


numbers = NumberIterator(5)
for num in numbers:
print(num) # Prints 1, 2, 3, 4, 5

# Manual iteration
numbers2 = NumberIterator(3)
iterator = iter(numbers2)
print(next(iterator)) # 1
print(next(iterator)) # 2
print(next(iterator)) # 3
# print(next(iterator)) # StopIteration error

14. COMPREHENSIONS
List Comprehensions:
# Basic list comprehension
squares = [x**2 for x in range(10)]
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# With condition
even_squares = [x**2 for x in range(10) if x % 2 == 0]
print(even_squares) # [0, 4, 16, 36, 64]

# With if-else
numbers = [x if x % 2 == 0 else -x for x in range(10)]
print(numbers) # [0, -1, 2, -3, 4, -5, 6, -7, 8, -9]

Set Comprehensions:
# Set comprehension
unique_squares = {x**2 for x in range(-5, 6)}
print(unique_squares) # {0, 1, 4, 9, 16, 25}

Dictionary Comprehensions:
# Dictionary comprehension
square_dict = {x: x**2 for x in range(5)}
print(square_dict) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# With condition
even_square_dict = {x: x**2 for x in range(10) if x % 2 == 0}
print(even_square_dict) # {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}

Nested Comprehensions:
# Nested list comprehension
matrix = [[i*j for j in range(3)] for i in range(3)]
print(matrix) # [[0, 0, 0], [0, 1, 2], [0, 2, 4]]

# Flattening nested list

PYTHON PROGRAMMING - STUDY MATERIAL 11


nested = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [item for sublist in nested for item in sublist]
print(flattened) # [1, 2, 3, 4, 5, 6, 7, 8, 9]

15. UNPACKING & ZIPPING


Unpacking:
# Tuple unpacking
point = (3, 4)
x, y = point
print(f"x: {x}, y: {y}")

# List unpacking
numbers = [1, 2, 3, 4, 5]
first, *middle, last = numbers
print(f"First: {first}, Middle: {middle}, Last: {last}")

# Function argument unpacking


def greet(first, last):
print(f"Hello, {first} {last}!")

name = ("John", "Doe")


greet(*name) # Unpacking tuple

# Dictionary unpacking
def create_profile(name, age, city):
return f"{name}, {age} years old, from {city}"

info = {"name": "Alice", "age": 25, "city": "Mumbai"}


profile = create_profile(**info) # Unpacking dictionary
print(profile)

Zipping:
# Basic zip
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 35]
cities = ["Mumbai", "Delhi", "Bangalore"]

# Zip multiple lists


combined = list(zip(names, ages, cities))
print(combined) # [('Alice', 25, 'Mumbai'), ('Bob', 30, 'Delhi'), ('Charlie', 35, 'Bangalor
e')]

# Unzip
unzipped_names, unzipped_ages, unzipped_cities = zip(*combined)
print(unzipped_names) # ('Alice', 'Bob', 'Charlie')

# Creating dictionary from zip


person_dict = dict(zip(names, ages))
print(person_dict) # {'Alice': 25, 'Bob': 30, 'Charlie': 35}

16. ENUMERATE, ZIP, RANGE

PYTHON PROGRAMMING - STUDY MATERIAL 12


Enumerate:
# Basic enumerate
fruits = ["apple", "banana", "orange"]
for index, fruit in enumerate(fruits):
print(f"{index}: {fruit}")

# Starting from different number


for index, fruit in enumerate(fruits, start=1):
print(f"{index}. {fruit}")

# Creating dictionary with enumerate


fruit_dict = {index: fruit for index, fruit in enumerate(fruits)}
print(fruit_dict) # {0: 'apple', 1: 'banana', 2: 'orange'}

Range:
# Basic range
print(list(range(5))) # [0, 1, 2, 3, 4]
print(list(range(2, 8))) # [2, 3, 4, 5, 6, 7]
print(list(range(0, 10, 2))) # [0, 2, 4, 6, 8]
print(list(range(10, 0, -1))) # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

# Range with float-like behavior


def float_range(start, stop, step):
while start < stop:
yield start
start += step

for x in float_range(0, 1, 0.1):


print(f"{x:.1f}")

🎯 PROGRAMMING QUESTIONS
EASY LEVEL (2-3 marks)
Q1: Write a program to check if a number is even or odd using if-else.

def check_even_odd(num):
"""Check if a number is even or odd."""
if num % 2 == 0:
return f"{num} is even"
else:
return f"{num} is odd"

# Test
number = int(input("Enter a number: "))
result = check_even_odd(number)
print(result)

# Output example:
# Enter a number: 7
# 7 is odd

Q2: Create a program to print multiplication table using for loop.

PYTHON PROGRAMMING - STUDY MATERIAL 13


def multiplication_table(num, limit=10):
"""Print multiplication table for given number."""
print(f"Multiplication table for {num}:")
for i in range(1, limit + 1):
result = num * i
print(f"{num} × {i} = {result}")

# Test
number = int(input("Enter number: "))
multiplication_table(number)

# Output example:
# Multiplication table for 5:
# 5 × 1 = 5
# 5 × 2 = 10
# ...

Q3: Write a program to count vowels in a string.

def count_vowels(text):
"""Count number of vowels in given text."""
vowels = "aeiouAEIOU"
count = 0
vowel_list = []

for char in text:


if char in vowels:
count += 1
vowel_list.append(char)

return count, vowel_list

# Test
text = input("Enter a string: ")
count, vowels_found = count_vowels(text)
print(f"Number of vowels: {count}")
print(f"Vowels found: {vowels_found}")

# Output example:
# Enter a string: Hello World
# Number of vowels: 3
# Vowels found: ['e', 'o', 'o']

MEDIUM LEVEL (5-7 marks)


Q1: Write a program to find all prime numbers up to n using list comprehension.

def is_prime(num):
"""Check if a number is prime."""
if num < 2:
return False
for i in range(2, int(num**0.5) + 1):
if num % i == 0:
return False
return True

def find_primes(n):

PYTHON PROGRAMMING - STUDY MATERIAL 14


"""Find all prime numbers up to n using list comprehension."""
primes = [num for num in range(2, n + 1) if is_prime(num)]
return primes

# Alternative using filter


def find_primes_filter(n):
"""Find primes using filter function."""
return list(filter(is_prime, range(2, n + 1)))

# Test
limit = int(input("Enter limit: "))
primes = find_primes(limit)
print(f"Prime numbers up to {limit}: {primes}")
print(f"Total primes: {len(primes)}")

# Output example:
# Enter limit: 20
# Prime numbers up to 20: [2, 3, 5, 7, 11, 13, 17, 19]
# Total primes: 8

Q2: Create a program to implement a simple calculator using match statement.

def calculator():
"""Simple calculator using match statement (Python 3.10+)."""
print("Simple Calculator")
print("Operations: +, -, *, /, %, **")

try:
num1 = float(input("Enter first number: "))
operator = input("Enter operator: ")
num2 = float(input("Enter second number: "))

match operator:
case '+':
result = num1 + num2
case '-':
result = num1 - num2
case '*':
result = num1 * num2
case '/':
if num2 == 0:
return "Error: Division by zero!"
result = num1 / num2
case '%':
if num2 == 0:
return "Error: Division by zero!"
result = num1 % num2
case '**':
result = num1 ** num2
case _:
return "Error: Invalid operator!"

return f"{num1} {operator} {num2} = {result}"

except ValueError:
return "Error: Invalid input!"

# Test

PYTHON PROGRAMMING - STUDY MATERIAL 15


print(calculator())

# Output example:
# Simple Calculator
# Operations: +, -, *, /, %, **
# Enter first number: 10
# Enter operator: *
# Enter second number: 5
# 10.0 * 5.0 = 50.0

Q3: Write a program to create a custom iterator for Fibonacci sequence.

class FibonacciIterator:
"""Custom iterator for Fibonacci sequence."""

def __init__(self, max_count):


self.max_count = max_count
[Link] = 0
[Link] = 0
self.next_val = 1

def __iter__(self):
return self

def __next__(self):
if [Link] < self.max_count:
if [Link] == 0:
[Link] += 1
return [Link]
elif [Link] == 1:
[Link] += 1
return self.next_val
else:
fib_val = [Link] + self.next_val
[Link] = self.next_val
self.next_val = fib_val
[Link] += 1
return fib_val
else:
raise StopIteration

# Test
print("First 10 Fibonacci numbers:")
fib_iter = FibonacciIterator(10)
for num in fib_iter:
print(num, end=" ")
print()

# Manual iteration
print("\\nManual iteration:")
fib_iter2 = FibonacciIterator(5)
iterator = iter(fib_iter2)
for _ in range(5):
print(next(iterator))

# Output:
# First 10 Fibonacci numbers:
# 0 1 1 2 3 5 8 13 21 34

PYTHON PROGRAMMING - STUDY MATERIAL 16


# Manual iteration:
# 0
# 1
# 1
# 2
# 3

EXAM LEVEL (8-10 marks)


Q1: Write a comprehensive program that demonstrates all types of comprehensions with nested structures.

def comprehensive_demo():
"""Demonstrate all types of comprehensions with complex examples."""

# Sample data
students = [
{"name": "Alice", "subjects": {"Math": 85, "Science": 92, "English": 78}},
{"name": "Bob", "subjects": {"Math": 76, "Science": 88, "English": 82}},
{"name": "Charlie", "subjects": {"Math": 94, "Science": 79, "English": 91}},
{"name": "Diana", "subjects": {"Math": 67, "Science": 85, "English": 89}}
]

# 1. List comprehension - Get all students with average > 80


high_performers = [
{
"name": student["name"],
"average": sum(student["subjects"].values()) / len(student["subjects"])
}
for student in students
if sum(student["subjects"].values()) / len(student["subjects"]) > 80
]

print("High Performers (Average > 80):")


for student in high_performers:
print(f" {student['name']}: {student['average']:.2f}")

# 2. Dictionary comprehension - Subject-wise class averages


all_subjects = set()
for student in students:
all_subjects.update(student["subjects"].keys())

subject_averages = {
subject: sum(student["subjects"][subject] for student in students) / len(students)
for subject in all_subjects
}

print(f"\\nSubject-wise Class Averages:")


for subject, avg in subject_averages.items():
print(f" {subject}: {avg:.2f}")

# 3. Set comprehension - All unique grades


all_grades = {
grade
for student in students
for grade in student["subjects"].values()
}

print(f"\\nAll Unique Grades: {sorted(all_grades)}")

PYTHON PROGRAMMING - STUDY MATERIAL 17


# 4. Nested comprehension - Grade matrix
grade_matrix = [
[student["subjects"][subject] for subject in sorted(all_subjects)]

UNIT II: FUNCTIONS, MODULES & COLLECTIONS


1. DEFINING AND CALLING FUNCTIONS
Theory:
Functions are reusable blocks of code that perform specific tasks. They help in code organization, reusability, and
modularity.

Function Syntax:
def function_name(parameters):
"""Docstring (optional)"""
# Function body
return value # Optional

Important Definitions (Viva):


Function: Named block of code that performs a specific task
Parameter: Variable in function definition
Argument: Actual value passed to function
Return value: Value sent back by function
Docstring: Documentation string describing function purpose

Examples:
# Simple function
def greet():
"""Function to greet user."""
print("Hello, World!")

# Function with parameters


def greet_person(name):
"""Greet a specific person."""
return f"Hello, {name}!"

# Function with multiple parameters


def add_numbers(a, b):
"""Add two numbers and return result."""
result = a + b
return result

# Function with default return (None)


def display_info(name, age):
"""Display person information."""
print(f"Name: {name}")
print(f"Age: {age}")
# No return statement = returns None

# Calling functions
greet() # Hello, World!

PYTHON PROGRAMMING - STUDY MATERIAL 18


message = greet_person("Alice") # Returns string
print(message) # Hello, Alice!
sum_result = add_numbers(5, 3) # Returns 8
display_info("Bob", 25) # Returns None

2. FUNCTION ARGUMENTS
Theory:
Python supports different types of arguments: positional, keyword, default, and variable-length arguments.

Types of Arguments:
Positional Arguments:
def calculate_area(length, width):
"""Calculate rectangle area."""
return length * width

# Order matters
area = calculate_area(5, 3) # length=5, width=3
print(area) # 15

Keyword Arguments:
def create_profile(name, age, city):
"""Create user profile."""
return f"{name}, {age} years old, from {city}"

# Order doesn't matter with keywords


profile1 = create_profile(name="Alice", city="Mumbai", age=25)
profile2 = create_profile(age=30, name="Bob", city="Delhi")
print(profile1) # Alice, 25 years old, from Mumbai
print(profile2) # Bob, 30 years old, from Delhi

Default Arguments:
def greet_user(name, greeting="Hello", punctuation="!"):
"""Greet user with customizable greeting."""
return f"{greeting}, {name}{punctuation}"

# Using defaults
print(greet_user("Alice")) # Hello, Alice!
print(greet_user("Bob", "Hi")) # Hi, Bob!
print(greet_user("Charlie", "Hey", ".")) # Hey, Charlie.

*Variable-Length Arguments (*args, kwargs):


# *args - Variable positional arguments
def sum_all(*numbers):
"""Sum all given numbers."""
total = 0
for num in numbers:
total += num
return total

PYTHON PROGRAMMING - STUDY MATERIAL 19


print(sum_all(1, 2, 3)) # 6
print(sum_all(1, 2, 3, 4, 5)) # 15

# **kwargs - Variable keyword arguments


def create_student(**details):
"""Create student record with flexible attributes."""
student = {}
for key, value in [Link]():
student[key] = value
return student

student1 = create_student(name="Alice", age=20, grade="A")


student2 = create_student(name="Bob", age=21, city="Mumbai", course="CS")
print(student1) # {'name': 'Alice', 'age': 20, 'grade': 'A'}
print(student2) # {'name': 'Bob', 'age': 21, 'city': 'Mumbai', 'course': 'CS'}

# Combining all argument types


def flexible_function(required, *args, default="default", **kwargs):
"""Function demonstrating all argument types."""
print(f"Required: {required}")
print(f"Args: {args}")
print(f"Default: {default}")
print(f"Kwargs: {kwargs}")

flexible_function("must_have", 1, 2, 3, default="custom", extra="info")

3. RECURSION
Theory:
Recursion is when a function calls itself. Every recursive function needs a base case (stopping condition) and recursive
case.

Important Definitions:
Recursion: Function calling itself
Base case: Condition that stops recursion
Recursive case: Function calling itself with modified parameters
Stack overflow: Error when recursion goes too deep

Examples:
Factorial:
def factorial(n):
"""Calculate factorial using recursion."""
# Base case
if n == 0 or n == 1:
return 1
# Recursive case
else:
return n * factorial(n - 1)

# Test
print(factorial(5)) # 120
print(factorial(0)) # 1

PYTHON PROGRAMMING - STUDY MATERIAL 20


# Trace: factorial(5)
# 5 * factorial(4)
# 5 * 4 * factorial(3)
# 5 * 4 * 3 * factorial(2)
# 5 * 4 * 3 * 2 * factorial(1)
# 5 * 4 * 3 * 2 * 1 = 120

Fibonacci:
def fibonacci(n):
"""Calculate nth Fibonacci number."""
# Base cases
if n <= 0:
return 0
elif n == 1:
return 1
# Recursive case
else:
return fibonacci(n-1) + fibonacci(n-2)

# Test
for i in range(10):
print(f"F({i}) = {fibonacci(i)}")

# Optimized version with memoization


def fibonacci_memo(n, memo={}):
"""Optimized Fibonacci with memoization."""
if n in memo:
return memo[n]

if n <= 0:
return 0
elif n == 1:
return 1
else:
memo[n] = fibonacci_memo(n-1, memo) + fibonacci_memo(n-2, memo)
return memo[n]

Binary Search:
def binary_search(arr, target, left=0, right=None):
"""Binary search using recursion."""
if right is None:
right = len(arr) - 1

# Base case - not found


if left > right:
return -1

mid = (left + right) // 2

# Base case - found


if arr[mid] == target:
return mid
# Recursive cases
elif arr[mid] > target:
return binary_search(arr, target, left, mid - 1)

PYTHON PROGRAMMING - STUDY MATERIAL 21


else:
return binary_search(arr, target, mid + 1, right)

# Test
numbers = [1, 3, 5, 7, 9, 11, 13, 15]
print(binary_search(numbers, 7)) # 3
print(binary_search(numbers, 4)) # -1

4. LAMBDA FUNCTIONS
Theory:
Lambda functions are anonymous functions defined using lambda keyword. They're used for short, simple functions.

Syntax:
lambda arguments: expression

Examples:
Basic Lambda:
# Regular function
def square(x):
return x ** 2

# Lambda equivalent
square_lambda = lambda x: x ** 2

print(square(5)) # 25
print(square_lambda(5)) # 25

# Multiple arguments
add = lambda x, y: x + y
print(add(3, 4)) # 7

# With default arguments


greet = lambda name, greeting="Hello": f"{greeting}, {name}!"
print(greet("Alice")) # Hello, Alice!
print(greet("Bob", "Hi")) # Hi, Bob!

Lambda with Built-in Functions:


numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# With filter
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers) # [2, 4, 6, 8, 10]

# With map
squares = list(map(lambda x: x ** 2, numbers))
print(squares) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

# With sorted
students = [("Alice", 85), ("Bob", 90), ("Charlie", 78)]

PYTHON PROGRAMMING - STUDY MATERIAL 22


sorted_by_grade = sorted(students, key=lambda student: student[1])
print(sorted_by_grade) # [('Charlie', 78), ('Alice', 85), ('Bob', 90)]

5. MAP, FILTER, REDUCE


Theory:
These are functional programming tools that work with iterables and functions.

MAP Function:
Applies function to every item in iterable.

# Basic map
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, numbers))
print(squared) # [1, 4, 9, 16, 25]

# Map with multiple iterables


list1 = [1, 2, 3]
list2 = [4, 5, 6]
added = list(map(lambda x, y: x + y, list1, list2))
print(added) # [5, 7, 9]

# Map with regular function


def celsius_to_fahrenheit(celsius):
return (celsius * 9/5) + 32

celsius_temps = [0, 20, 30, 40]


fahrenheit_temps = list(map(celsius_to_fahrenheit, celsius_temps))
print(fahrenheit_temps) # [32.0, 68.0, 86.0, 104.0]

# Map with string methods


words = ["hello", "world", "python"]
capitalized = list(map([Link], words))
print(capitalized) # ['HELLO', 'WORLD', 'PYTHON']

FILTER Function:
Filters items based on condition.

# Basic filter
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers) # [2, 4, 6, 8, 10]

# Filter with regular function


def is_positive(x):
return x > 0

mixed_numbers = [-3, -1, 0, 2, 5, -7, 8]


positive_numbers = list(filter(is_positive, mixed_numbers))
print(positive_numbers) # [2, 5, 8]

# Filter strings
words = ["apple", "banana", "cherry", "date"]
long_words = list(filter(lambda word: len(word) > 5, words))
print(long_words) # ['banana', 'cherry']

PYTHON PROGRAMMING - STUDY MATERIAL 23


# Filter None values
data = [1, None, 3, None, 5, 0, 7]
clean_data = list(filter(None, data)) # Removes falsy values
print(clean_data) # [1, 3, 5, 7]

REDUCE Function:
Applies function cumulatively to items in sequence.

from functools import reduce

# Basic reduce - sum


numbers = [1, 2, 3, 4, 5]
total = reduce(lambda x, y: x + y, numbers)
print(total) # 15

# Reduce with initial value


total_with_initial = reduce(lambda x, y: x + y, numbers, 10)
print(total_with_initial) # 25

# Find maximum
maximum = reduce(lambda x, y: x if x > y else y, numbers)
print(maximum) # 5

# String concatenation
words = ["Hello", " ", "World", "!"]
sentence = reduce(lambda x, y: x + y, words)
print(sentence) # "Hello World!"

# Factorial using reduce


def factorial_reduce(n):
return reduce(lambda x, y: x * y, range(1, n + 1), 1)

print(factorial_reduce(5)) # 120

6. ANY, ALL, SORTED, MIN, MAX


ANY and ALL:
# ANY - returns True if any element is True
numbers = [0, 0, 1, 0]
print(any(numbers)) # True (1 is truthy)

empty_list = []
print(any(empty_list)) # False

# Check if any number is even


numbers = [1, 3, 5, 7, 8]
has_even = any(x % 2 == 0 for x in numbers)
print(has_even) # True

# ALL - returns True if all elements are True


numbers = [1, 2, 3, 4]
print(all(numbers)) # True (all are truthy)

numbers_with_zero = [1, 2, 0, 4]

PYTHON PROGRAMMING - STUDY MATERIAL 24


print(all(numbers_with_zero)) # False (0 is falsy)

# Check if all numbers are positive


numbers = [1, 2, 3, 4, 5]
all_positive = all(x > 0 for x in numbers)
print(all_positive) # True

SORTED with Custom Keys:


# Basic sorting
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
sorted_numbers = sorted(numbers)
print(sorted_numbers) # [1, 1, 2, 3, 4, 5, 6, 9]

# Reverse sorting
reverse_sorted = sorted(numbers, reverse=True)
print(reverse_sorted) # [9, 6, 5, 4, 3, 2, 1, 1]

# Sorting strings by length


words = ["python", "java", "c", "javascript", "go"]
by_length = sorted(words, key=len)
print(by_length) # ['c', 'go', 'java', 'python', 'javascript']

# Sorting tuples
students = [("Alice", 85), ("Bob", 90), ("Charlie", 78), ("Diana", 92)]
by_grade = sorted(students, key=lambda student: student[1])
print(by_grade) # [('Charlie', 78), ('Alice', 85), ('Bob', 90), ('Diana', 92)]

# Sorting dictionaries
people = [
{"name": "Alice", "age": 25},
{"name": "Bob", "age": 30},
{"name": "Charlie", "age": 20}
]
by_age = sorted(people, key=lambda person: person["age"])
print(by_age)

MIN and MAX with Custom Keys:


numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print(min(numbers)) # 1
print(max(numbers)) # 9

# With strings
words = ["python", "java", "c", "javascript"]
shortest = min(words, key=len)
longest = max(words, key=len)
print(f"Shortest: {shortest}") # c
print(f"Longest: {longest}") # javascript

# With complex data


students = [("Alice", 85), ("Bob", 90), ("Charlie", 78)]
best_student = max(students, key=lambda student: student[1])
worst_student = min(students, key=lambda student: student[1])
print(f"Best: {best_student}") # ('Bob', 90)
print(f"Worst: {worst_student}") # ('Charlie', 78)

PYTHON PROGRAMMING - STUDY MATERIAL 25


7. FIRST-CLASS FUNCTIONS
Theory:
In Python, functions are first-class objects, meaning they can be:
Assigned to variables
Passed as arguments
Returned from functions
Stored in data structures

Examples:
Functions as Variables:
def greet(name):
return f"Hello, {name}!"

def farewell(name):
return f"Goodbye, {name}!"

# Assign function to variable


my_function = greet
print(my_function("Alice")) # Hello, Alice!

# Store functions in list


functions = [greet, farewell]
for func in functions:
print(func("Bob"))
# Hello, Bob!
# Goodbye, Bob!

# Store functions in dictionary


operations = {
"add": lambda x, y: x + y,
"subtract": lambda x, y: x - y,
"multiply": lambda x, y: x * y,
"divide": lambda x, y: x / y if y != 0 else "Cannot divide by zero"
}

print(operations["add"](5, 3)) # 8
print(operations["multiply"](4, 7)) # 28

Functions as Arguments:
def apply_operation(func, x, y):
"""Apply given function to two numbers."""
return func(x, y)

def add(x, y):


return x + y

def multiply(x, y):


return x * y

# Pass functions as arguments


result1 = apply_operation(add, 5, 3) # 8
result2 = apply_operation(multiply, 4, 7) # 28

PYTHON PROGRAMMING - STUDY MATERIAL 26


result3 = apply_operation(lambda x, y: x ** y, 2, 3) # 8

print(f"Results: {result1}, {result2}, {result3}")

Functions Returning Functions:


def create_multiplier(factor):
"""Return a function that multiplies by factor."""
def multiplier(x):
return x * factor
return multiplier

# Create specific multiplier functions


double = create_multiplier(2)
triple = create_multiplier(3)

print(double(5)) # 10
print(triple(4)) # 12

# More complex example


def create_validator(min_length, max_length):
"""Create a password validator function."""
def validate(password):
if len(password) < min_length:
return f"Password too short (minimum {min_length} characters)"
elif len(password) > max_length:
return f"Password too long (maximum {max_length} characters)"
else:
return "Password length is valid"
return validate

# Create validators
basic_validator = create_validator(6, 12)
strict_validator = create_validator(8, 16)

print(basic_validator("hello")) # Password too short


print(strict_validator("password123")) # Password length is valid

8. CLOSURES
Theory:
A closure is created when a nested function references variables from its enclosing scope. The nested function "closes
over" these variables.

Important Definitions:
Closure: Nested function that captures variables from enclosing scope
Enclosing scope: The scope of the outer function
Free variables: Variables from enclosing scope used in nested function

Examples:
Basic Closure:
def outer_function(x):
"""Outer function with local variable."""

PYTHON PROGRAMMING - STUDY MATERIAL 27


def inner_function(y):
"""Inner function accessing outer variable."""
return x + y # x is from enclosing scope

return inner_function

# Create closure
add_10 = outer_function(10)
print(add_10(5)) # 15

# x=10 is "captured" in the closure


add_20 = outer_function(20)
print(add_20(5)) # 25

Closure with State:


def create_counter(initial=0):
"""Create a counter function with state."""
count = initial

def counter():
nonlocal count # Modify variable in enclosing scope
count += 1
return count

return counter

# Create independent counters


counter1 = create_counter()
counter2 = create_counter(100)

print(counter1()) # 1
print(counter1()) # 2
print(counter2()) # 101
print(counter1()) # 3
print(counter2()) # 102

Practical Closure Example:


def create_account(initial_balance=0):
"""Create a bank account with closure."""
balance = initial_balance

def deposit(amount):
nonlocal balance
if amount > 0:
balance += amount
return f"Deposited ₹{amount}. New balance: ₹{balance}"
return "Invalid deposit amount"

def withdraw(amount):
nonlocal balance
if 0 < amount <= balance:
balance -= amount
return f"Withdrew ₹{amount}. New balance: ₹{balance}"
return "Invalid withdrawal amount or insufficient funds"

PYTHON PROGRAMMING - STUDY MATERIAL 28


def get_balance():
return f"Current balance: ₹{balance}"

# Return dictionary of functions


return {
"deposit": deposit,
"withdraw": withdraw,
"balance": get_balance
}

# Create account
account = create_account(1000)
print(account["balance"]()) # Current balance: ₹1000
print(account["deposit"](500)) # Deposited ₹500. New balance: ₹1500
print(account["withdraw"](200)) # Withdrew ₹200. New balance: ₹1300

9. DECORATORS
Theory:
Decorators are functions that modify or extend the behavior of other functions without changing their code. They use the
@ syntax.

Important Definitions:
Decorator: Function that takes another function and extends its behavior
Wrapper function: Inner function in decorator that wraps the original function
@syntax: Syntactic sugar for applying decorators

Basic Decorator:
def my_decorator(func):
"""Basic decorator that adds functionality."""
def wrapper():
print("Something before the function")
func()
print("Something after the function")
return wrapper

# Using decorator with @ syntax


@my_decorator
def say_hello():
print("Hello!")

# Equivalent to: say_hello = my_decorator(say_hello)

say_hello()
# Output:
# Something before the function
# Hello!
# Something after the function

Decorator with Arguments:


def timing_decorator(func):
"""Decorator to measure function execution time."""

PYTHON PROGRAMMING - STUDY MATERIAL 29


import time

def wrapper(*args, **kwargs):


start_time = [Link]()
result = func(*args, **kwargs)
end_time = [Link]()
print(f"{func.__name__} took {end_time - start_time:.4f} seconds")
return result
return wrapper

@timing_decorator
def slow_function():
"""Function that takes some time."""
import time
[Link](1)
return "Done!"

result = slow_function()
print(result)
# Output:
# slow_function took 1.0041 seconds
# Done!

Decorator with Parameters:


def repeat(times):
"""Decorator that repeats function execution."""
def decorator(func):
def wrapper(*args, **kwargs):
for i in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator

@repeat(3)
def greet(name):
print(f"Hello, {name}!")

greet("Alice")
# Output:
# Hello, Alice!
# Hello, Alice!
# Hello, Alice!

Practical Decorators:
def login_required(func):
"""Decorator to check if user is logged in."""
def wrapper(*args, **kwargs):
# Simulate checking login status
is_logged_in = True # This would be actual check

if is_logged_in:
return func(*args, **kwargs)
else:
return "Please log in first!"

PYTHON PROGRAMMING - STUDY MATERIAL 30


return wrapper

def cache_result(func):
"""Decorator to cache function results."""
cache = {}

def wrapper(*args, **kwargs):


# Create cache key from arguments
key = str(args) + str(kwargs)

if key in cache:
print(f"Cache hit for {func.__name__}")
return cache[key]

print(f"Computing {func.__name__}")
result = func(*args, **kwargs)
cache[key] = result
return result

return wrapper

@cache_result
def expensive_calculation(n):
"""Simulate expensive calculation."""
import time
[Link](1) # Simulate work
return n * n

# Test caching
print(expensive_calculation(5)) # Computing expensive_calculation
print(expensive_calculation(5)) # Cache hit for expensive_calculation

10. GENERATORS
Theory:
Generators are functions that return an iterator object. They use yield instead of return and can pause and resume
execution.

Important Definitions:
Generator: Function that yields values one at a time
yield: Keyword that pauses function and returns value
Generator object: Iterator returned by generator function
send(): Method to send values to generator

Basic Generator:
def simple_generator():
"""Simple generator that yields three values."""
print("Starting generator")
yield 1
print("Between yields")
yield 2
print("Before last yield")
yield 3
print("Generator finished")

PYTHON PROGRAMMING - STUDY MATERIAL 31


# Create generator object
gen = simple_generator()
print(type(gen)) # <class 'generator'>

# Get values one by one


print(next(gen)) # Starting generator, then 1
print(next(gen)) # Between yields, then 2
print(next(gen)) # Before last yield, then 3
# print(next(gen)) # Generator finished, then StopIteration

Generator with Loop:


def number_generator(n):
"""Generate numbers from 0 to n-1."""
for i in range(n):
print(f"Generating {i}")
yield i

# Use in for loop


for num in number_generator(5):
print(f"Received: {num}")

# Convert to list
numbers = list(number_generator(3))
print(numbers) # [0, 1, 2]

Fibonacci Generator:
def fibonacci_generator(limit):
"""Generate Fibonacci numbers up to limit."""
a, b = 0, 1
while a < limit:
yield a
a, b = b, a + b

# Use generator
fib_gen = fibonacci_generator(100)
fib_numbers = list(fib_gen)
print(fib_numbers) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

Generator with send():


def echo_generator():
"""Generator that echoes sent values."""
value = None
while True:
received = yield value
if received is not None:
value = f"Echo: {received}"

# Use send method


gen = echo_generator()
next(gen) # Prime the generator
print([Link]("Hello")) # Echo: Hello
print([Link]("World")) # Echo: World

PYTHON PROGRAMMING - STUDY MATERIAL 32


Generator Expression:
# Generator expression (like list comprehension but with parentheses)
squares_gen = (x**2 for x in range(10))
print(type(squares_gen)) # <class 'generator'>

# Use generator expression


for square in squares_gen:
print(square, end=" ") # 0 1 4 9 16 25 36 49 64 81
print()

# Memory efficient - doesn't create entire list


even_squares = (x**2 for x in range(1000000) if x % 2 == 0)
print(sum(even_squares)) # Sum without storing all values

11. ITERATORS VS GENERATORS


Theory:
Both provide ways to iterate over data, but they differ in implementation and use cases.

Comparison:
Aspect Iterator Generator
Implementation Class with __iter__ and __next__ Function with yield

Memory Can store state in attributes Automatic state management


Complexity More code required Simpler syntax
Performance Slightly faster Slightly slower
Use case Complex iteration logic Simple iteration patterns

Iterator Example:
class CountDown:
"""Iterator class for countdown."""

def __init__(self, start):


[Link] = start

def __iter__(self):
return self

def __next__(self):
if [Link] <= 0:
raise StopIteration
[Link] -= 1
return [Link] + 1

# Use iterator
countdown = CountDown(5)
for num in countdown:
print(num) # 5, 4, 3, 2, 1

Generator Equivalent:
def countdown_generator(start):
"""Generator function for countdown."""

PYTHON PROGRAMMING - STUDY MATERIAL 33


while start > 0:
yield start
start -= 1

# Use generator
for num in countdown_generator(5):
print(num) # 5, 4, 3, 2, 1

12. MODULES AND IMPORTS


Theory:
Modules are Python files containing functions, classes, and variables. They help organize code and promote reusability.

Types of Imports:
# 1. Import entire module
import math
print([Link]) # 3.141592653589793
print([Link](16)) # 4.0

# 2. Import with alias


import math as m
print([Link])

# 3. Import specific functions


from math import pi, sqrt
print(pi) # 3.141592653589793
print(sqrt(16)) # 4.0

# 4. Import all (not recommended)


from math import *
print(sin(pi/2)) # 1.0

# 5. Import with alias


from math import pi as PI
print(PI)

Module Search Path:


import sys
print("Module search path:")
for path in [Link]:
print(f" {path}")

# Add custom path


[Link]("/custom/path")

Creating User-Defined Modules:


File: [Link]

"""
Custom math module demonstrating module creation.
"""

PI = 3.14159

PYTHON PROGRAMMING - STUDY MATERIAL 34


def area_circle(radius):
"""Calculate area of circle."""
return PI * radius ** 2

def area_rectangle(length, width):


"""Calculate area of rectangle."""
return length * width

def factorial(n):
"""Calculate factorial."""
if n <= 1:
return 1
return n * factorial(n - 1)

class Calculator:
"""Simple calculator class."""

def add(self, x, y):


return x + y

def subtract(self, x, y):


return x - y

# Code that runs when module is executed directly


if __name__ == "__main__":
print("Testing mymath module")
print(f"Circle area (r=5): {area_circle(5)}")
print(f"Factorial of 5: {factorial(5)}")

Using the module:

# Import custom module


import mymath

# Use module functions


print(mymath.area_circle(5))
print(mymath.

📚 UNIT III: OBJECT-ORIENTED PROGRAMMING & FILE HANDLING


1. CLASSES AND OBJECTS
Theory:
Object-Oriented Programming (OOP) is a programming paradigm based on objects and classes. A class is a blueprint for
creating objects, and an object is an instance of a class.

Important Definitions (Viva):


Class: Blueprint or template for creating objects
Object: Instance of a class with specific values
Instance: Another term for object
Instantiation: Process of creating an object from a class
Attribute: Variable that belongs to a class or object
Method: Function that belongs to a class

PYTHON PROGRAMMING - STUDY MATERIAL 35


Basic Class Syntax:
class ClassName:
"""Class docstring"""
# Class body
pass

Examples:
Simple Class:
class Student:
"""A simple Student class."""

# Class attribute (shared by all instances)


school_name = "ABC University"

def display_info(self):
"""Method to display student information."""
print(f"Student from {self.school_name}")

# Creating objects (instances)


student1 = Student()
student2 = Student()

# Calling methods
student1.display_info() # Student from ABC University
student2.display_info() # Student from ABC University

# Accessing class attribute


print(Student.school_name) # ABC University
print(student1.school_name) # ABC University

Class with Instance Attributes:


class Person:
"""Person class with instance attributes."""

def __init__(self, name, age):


"""Constructor method."""
[Link] = name # Instance attribute
[Link] = age # Instance attribute

def introduce(self):
"""Method to introduce the person."""
return f"Hi, I'm {[Link]} and I'm {[Link]} years old."

def have_birthday(self):
"""Method to increment age."""
[Link] += 1
return f"Happy birthday! {[Link]} is now {[Link]} years old."

# Creating objects with different attributes


person1 = Person("Alice", 25)
person2 = Person("Bob", 30)

print([Link]()) # Hi, I'm Alice and I'm 25 years old.

PYTHON PROGRAMMING - STUDY MATERIAL 36


print([Link]()) # Hi, I'm Bob and I'm 30 years old.

print(person1.have_birthday()) # Happy birthday! Alice is now 26 years old.

2. CLASS AND INSTANCE ATTRIBUTES


Theory:
Class attributes: Shared by all instances of the class
Instance attributes: Unique to each instance

Examples:
class BankAccount:
"""Bank account class demonstrating class and instance attributes."""

# Class attributes
bank_name = "Python Bank"
interest_rate = 0.05
total_accounts = 0

def __init__(self, account_holder, initial_balance=0):


"""Initialize bank account."""
# Instance attributes
self.account_holder = account_holder
[Link] = initial_balance
self.account_number = BankAccount.total_accounts + 1

# Modify class attribute


BankAccount.total_accounts += 1

def deposit(self, amount):


"""Deposit money to account."""
if amount > 0:
[Link] += amount
return f"Deposited ₹{amount}. New balance: ₹{[Link]}"
return "Invalid deposit amount"

def get_account_info(self):
"""Get account information."""
return f"Account {self.account_number}: {self.account_holder}, Balance: ₹{[Link]
e}"

@classmethod
def get_bank_info(cls):
"""Class method to get bank information."""
return f"Bank: {cls.bank_name}, Total Accounts: {cls.total_accounts}"

# Create accounts
account1 = BankAccount("Alice", 1000)
account2 = BankAccount("Bob", 2000)

print(account1.get_account_info()) # Account 1: Alice, Balance: ₹1000


print(account2.get_account_info()) # Account 2: Bob, Balance: ₹2000

print(BankAccount.get_bank_info()) # Bank: Python Bank, Total Accounts: 2

PYTHON PROGRAMMING - STUDY MATERIAL 37


# Accessing attributes
print(f"Bank name: {BankAccount.bank_name}") # Class attribute
print(f"Alice's balance: {[Link]}") # Instance attribute

Attribute Access Priority:


class Demo:
class_var = "Class Variable"

def __init__(self):
self.instance_var = "Instance Variable"

demo = Demo()

# Instance attribute takes priority over class attribute


print(demo.instance_var) # Instance Variable
print(demo.class_var) # Class Variable

# If we create instance attribute with same name as class attribute


demo.class_var = "Modified Class Variable"
print(demo.class_var) # Modified Class Variable (instance attribute)
print(Demo.class_var) # Class Variable (original class attribute)

3. METHODS AND SELF


Theory:
self: Reference to the current instance of the class
Instance methods: Methods that operate on instance data
Method: Function defined inside a class

Examples:
class Rectangle:
"""Rectangle class demonstrating methods and self."""

def __init__(self, length, width):


"""Initialize rectangle with length and width."""
[Link] = length
[Link] = width

def area(self):
"""Calculate area of rectangle."""
return [Link] * [Link]

def perimeter(self):
"""Calculate perimeter of rectangle."""
return 2 * ([Link] + [Link])

def scale(self, factor):


"""Scale rectangle by given factor."""
[Link] *= factor
[Link] *= factor

def is_square(self):
"""Check if rectangle is a square."""

PYTHON PROGRAMMING - STUDY MATERIAL 38


return [Link] == [Link]

def __str__(self):
"""String representation of rectangle."""
return f"Rectangle({[Link]} x {[Link]})"

# Create rectangle
rect = Rectangle(5, 3)

print(rect) # Rectangle(5 x 3)
print(f"Area: {[Link]()}") # Area: 15
print(f"Perimeter: {[Link]()}") # Perimeter: 16
print(f"Is square: {rect.is_square()}") # Is square: False

# Scale rectangle
[Link](2)
print(rect) # Rectangle(10 x 6)

# Create square
square = Rectangle(4, 4)
print(f"Is square: {square.is_square()}") # Is square: True

Method Types:
class MyClass:
"""Demonstrating different types of methods."""

class_variable = "I'm a class variable"

def __init__(self, value):


self.instance_variable = value

def instance_method(self):
"""Instance method - has access to self."""
return f"Instance method called. Value: {self.instance_variable}"

@classmethod
def class_method(cls):
"""Class method - has access to cls (class itself)."""
return f"Class method called. Class variable: {cls.class_variable}"

@staticmethod
def static_method():
"""Static method - no access to self or cls."""
return "Static method called. No access to instance or class data."

# Usage
obj = MyClass("Hello")

print(obj.instance_method()) # Instance method called. Value: Hello


print(MyClass.class_method()) # Class method called. Class variable: I'm a class variable
print(MyClass.static_method()) # Static method called. No access to instance or class data.

# Can also call class and static methods on instance


print(obj.class_method()) # Works but not recommended
print(obj.static_method()) # Works but not recommended

PYTHON PROGRAMMING - STUDY MATERIAL 39


4. CONSTRUCTOR (init)
Theory:
The __init__ method is a special method called when an object is created. It initializes the object's attributes.

Important Definitions:
Constructor: Special method that initializes new objects
__init__ : Python's constructor method
Initialization: Setting initial values for object attributes

Examples:
Basic Constructor:
class Book:
"""Book class with constructor."""

def __init__(self, title, author, pages):


"""Initialize book with title, author, and pages."""
[Link] = title
[Link] = author
[Link] = pages
self.is_read = False # Default value

def mark_as_read(self):
"""Mark book as read."""
self.is_read = True

def __str__(self):
"""String representation."""
status = "Read" if self.is_read else "Unread"
return f"'{[Link]}' by {[Link]} ({[Link]} pages) - {status}"

# Create books
book1 = Book("1984", "George Orwell", 328)
book2 = Book("To Kill a Mockingbird", "Harper Lee", 376)

print(book1) # '1984' by George Orwell (328 pages) - Unread


book1.mark_as_read()
print(book1) # '1984' by George Orwell (328 pages) - Read

Constructor with Default Parameters:


class Employee:
"""Employee class with default parameters."""

def __init__(self, name, position="Intern", salary=25000, department="General"):


"""Initialize employee with default values."""
[Link] = name
[Link] = position
[Link] = salary
[Link] = department
self.employee_id = self._generate_id()

def _generate_id(self):
"""Private method to generate employee ID."""

PYTHON PROGRAMMING - STUDY MATERIAL 40


import random
return f"EMP{[Link](1000, 9999)}"

def give_raise(self, amount):


"""Give salary raise."""
[Link] += amount
return f"{[Link]} received a raise of ₹{amount}. New salary: ₹{[Link]}"

def promote(self, new_position):


"""Promote employee to new position."""
old_position = [Link]
[Link] = new_position
return f"{[Link]} promoted from {old_position} to {new_position}"

def __str__(self):
return f"{[Link]} ({self.employee_id}) - {[Link]}, ₹{[Link]}"

# Create employees with different parameters


emp1 = Employee("Alice") # Uses all defaults except name
emp2 = Employee("Bob", "Developer", 60000, "IT") # All parameters specified
emp3 = Employee("Charlie", salary=45000) # Mixed parameters

print(emp1) # Alice (EMP1234) - Intern, ₹25000


print(emp2) # Bob (EMP5678) - Developer, ₹60000
print(emp3) # Charlie (EMP9012) - Intern, ₹45000

print(emp1.give_raise(5000)) # Alice received a raise of ₹5000. New salary: ₹30000


print([Link]("Junior Developer")) # Alice promoted from Intern to Junior Developer

Constructor with Validation:


class Temperature:
"""Temperature class with validation in constructor."""

def __init__(self, celsius):


"""Initialize temperature with validation."""
if not isinstance(celsius, (int, float)):
raise TypeError("Temperature must be a number")

if celsius < -273.15:


raise ValueError("Temperature cannot be below absolute zero (-273.15°C)")

self._celsius = celsius

@property
def celsius(self):
"""Get temperature in Celsius."""
return self._celsius

@[Link]
def celsius(self, value):
"""Set temperature in Celsius with validation."""
if not isinstance(value, (int, float)):
raise TypeError("Temperature must be a number")

if value < -273.15:


raise ValueError("Temperature cannot be below absolute zero (-273.15°C)")

PYTHON PROGRAMMING - STUDY MATERIAL 41


self._celsius = value

@property
def fahrenheit(self):
"""Get temperature in Fahrenheit."""
return (self._celsius * 9/5) + 32

@property
def kelvin(self):
"""Get temperature in Kelvin."""
return self._celsius + 273.15

def __str__(self):
return f"{self._celsius}°C ({[Link]:.1f}°F, {[Link]:.1f}K)"

# Valid temperature
temp1 = Temperature(25)
print(temp1) # 25°C (77.0°F, 298.1K)

# Invalid temperatures
try:
temp2 = Temperature(-300) # Below absolute zero
except ValueError as e:
print(f"Error: {e}")

try:
temp3 = Temperature("hot") # Not a number
except TypeError as e:
print(f"Error: {e}")

5. ATTRIBUTE ACCESS CONTROL


Theory:
Python uses naming conventions to indicate attribute visibility:
Public: Normal attributes (no underscore prefix)
Protected: Single underscore prefix ( _attribute )
Private: Double underscore prefix ( __attribute )

Examples:
class BankAccount:
"""Demonstrating attribute access control."""

def __init__(self, account_holder, initial_balance):


self.account_holder = account_holder # Public
self._balance = initial_balance # Protected (convention)
self.__pin = 1234 # Private (name mangling)

def deposit(self, amount):


"""Public method to deposit money."""
if amount > 0:
self._balance += amount
return f"Deposited ₹{amount}"
return "Invalid amount"

PYTHON PROGRAMMING - STUDY MATERIAL 42


def _validate_pin(self, pin):
"""Protected method (convention - internal use)."""
return pin == self.__pin

def __encrypt_data(self, data):


"""Private method (name mangling)."""
return f"encrypted_{data}"

def withdraw(self, amount, pin):


"""Public method using protected and private methods."""
if not self._validate_pin(pin):
return "Invalid PIN"

if amount > self._balance:


return "Insufficient funds"

self._balance -= amount
# Using private method
transaction_id = self.__encrypt_data(f"withdraw_{amount}")
return f"Withdrew ₹{amount}. Transaction: {transaction_id}"

def get_balance(self, pin):


"""Get balance with PIN verification."""
if self._validate_pin(pin):
return f"Balance: ₹{self._balance}"
return "Invalid PIN"

# Create account
account = BankAccount("Alice", 1000)

# Public attribute access


print(account.account_holder) # Alice

# Protected attribute (accessible but not recommended)


print(account._balance) # 1000

# Private attribute (name mangling - not directly accessible)


# print(account.__pin) # AttributeError
print(account._BankAccount__pin) # 1234 (name mangled - not recommended)

# Using methods
print([Link](500)) # Deposited ₹500
print(account.get_balance(1234)) # Balance: ₹1500
print([Link](200, 1234)) # Withdrew ₹200. Transaction: encrypted_withdraw_200
print([Link](200, 1111)) # Invalid PIN

Property Decorators for Access Control:


class Circle:
"""Circle class with property decorators."""

def __init__(self, radius):


self._radius = radius

@property
def radius(self):

PYTHON PROGRAMMING - STUDY MATERIAL 43


"""Getter for radius."""
return self._radius

@[Link]
def radius(self, value):
"""Setter for radius with validation."""
if value <= 0:
raise ValueError("Radius must be positive")
self._radius = value

@property
def area(self):
"""Read-only property for area."""
return 3.14159 * self._radius ** 2

@property
def circumference(self):
"""Read-only property for circumference."""
return 2 * 3.14159 * self._radius

# Usage
circle = Circle(5)
print(f"Radius: {[Link]}") # 5
print(f"Area: {[Link]:.2f}") # 78.54
print(f"Circumference: {[Link]:.2f}") # 31.42

# Modify radius
[Link] = 10
print(f"New area: {[Link]:.2f}") # 314.16

# Try invalid radius


try:
[Link] = -5
except ValueError as e:
print(f"Error: {e}") # Error: Radius must be positive

# Try to modify read-only property


try:
[Link] = 100
except AttributeError as e:
print(f"Error: can't set attribute")

6. CLASS VARIABLES AND METHODS


Theory:
Class variables: Shared by all instances
Class methods: Methods that work with class data
Static methods: Methods that don't need class or instance data

Examples:
class Student:
"""Student class demonstrating class variables and methods."""

# Class variables
school_name = "Python University"

PYTHON PROGRAMMING - STUDY MATERIAL 44


total_students = 0
grade_scale = {"A": 90, "B": 80, "C": 70, "D": 60, "F": 0}

def __init__(self, name, student_id):


"""Initialize student."""
[Link] = name
self.student_id = student_id
[Link] = []

# Increment class variable


Student.total_students += 1

def add_grade(self, score):


"""Add grade to student."""
[Link](score)

def get_average(self):
"""Get student's average grade."""
if [Link]:
return sum([Link]) / len([Link])
return 0

def get_letter_grade(self):
"""Get letter grade based on average."""
avg = self.get_average()
for letter, min_score in Student.grade_scale.items():
if avg >= min_score:
return letter
return "F"

@classmethod
def get_school_info(cls):
"""Class method to get school information."""
return f"School: {cls.school_name}, Total Students: {cls.total_students}"

@classmethod
def change_school_name(cls, new_name):
"""Class method to change school name."""
old_name = cls.school_name
cls.school_name = new_name
return f"School name changed from '{old_name}' to '{new_name}'"

@staticmethod
def is_passing_grade(score):
"""Static method to check if score is passing."""
return score >= 60

@staticmethod
def calculate_gpa(grades):
"""Static method to calculate GPA."""
if not grades:
return 0.0

grade_points = {"A": 4.0, "B": 3.0, "C": 2.0, "D": 1.0, "F": 0.0}
total_points = sum(grade_points.get(grade, 0.0) for grade in grades)
return total_points / len(grades)

def __str__(self):

PYTHON PROGRAMMING - STUDY MATERIAL 45


avg = self.get_average()
letter = self.get_letter_grade()
return f"Student: {[Link]} (ID: {self.student_id}), Average: {avg:.1f} ({letter})"

# Create students
student1 = Student("Alice", "S001")
student2 = Student("Bob", "S002")
student3 = Student("Charlie", "S003")

# Add grades
student1.add_grade(85)
student1.add_grade(92)
student1.add_grade(78)

student2.add_grade(76)
student2.add_grade(82)
student2.add_grade(88)

print(student1) # Student: Alice (ID: S001), Average: 85.0 (B)


print(student2) # Student: Bob (ID: S002), Average: 82.0 (B)

# Class method usage


print(Student.get_school_info()) # School: Python University, Total Students: 3
print(Student.change_school_name("Advanced Python University"))

# Static method usage


print(f"Is 75 passing? {Student.is_passing_grade(75)}") # True
print(f"Is 45 passing? {Student.is_passing_grade(45)}") # False

# Calculate GPA
letter_grades = ["A", "B", "B", "A", "C"]
gpa = Student.calculate_gpa(letter_grades)
print(f"GPA for {letter_grades}: {gpa:.2f}") # GPA for ['A', 'B', 'B', 'A', 'C']: 3.00

7. INHERITANCE
Theory:
Inheritance allows a class to inherit attributes and methods from another class. The inheriting class is called child/derived
class, and the inherited class is called parent/base class.

Important Definitions:
Inheritance: Mechanism to create new class based on existing class
Parent/Base class: Class being inherited from
Child/Derived class: Class that inherits from parent class
IS-A relationship: Relationship expressed by inheritance

Basic Inheritance:
class Animal:
"""Base class for all animals."""

def __init__(self, name, species):


[Link] = name
[Link] = species
self.is_alive = True

PYTHON PROGRAMMING - STUDY MATERIAL 46


def eat(self):
return f"{[Link]} is eating."

def sleep(self):
return f"{[Link]} is sleeping."

def make_sound(self):
return f"{[Link]} makes a sound."

def __str__(self):
return f"{[Link]} the {[Link]}"

class Dog(Animal):
"""Dog class inheriting from Animal."""

def __init__(self, name, breed):


super().__init__(name, "Dog") # Call parent constructor
[Link] = breed

def make_sound(self): # Method overriding


return f"{[Link]} barks: Woof! Woof!"

def fetch(self): # New method specific to Dog


return f"{[Link]} fetches the ball."

def __str__(self):
return f"{[Link]} the {[Link]} dog"

class Cat(Animal):
"""Cat class inheriting from Animal."""

def __init__(self, name, color):


super().__init__(name, "Cat")
[Link] = color

def make_sound(self): # Method overriding


return f"{[Link]} meows: Meow! Meow!"

def climb(self): # New method specific to Cat


return f"{[Link]} climbs the tree."

def __str__(self):
return f"{[Link]} the {[Link]} cat"

# Create objects
dog = Dog("Buddy", "Golden Retriever")
cat = Cat("Whiskers", "Orange")

print(dog) # Buddy the Golden Retriever dog


print(cat) # Whiskers the Orange cat

# Inherited methods
print([Link]()) # Buddy is eating.
print([Link]()) # Whiskers is sleeping.

# Overridden methods
print(dog.make_sound()) # Buddy barks: Woof! Woof!

PYTHON PROGRAMMING - STUDY MATERIAL 47


print(cat.make_sound()) # Whiskers meows: Meow! Meow!

# Child-specific methods
print([Link]()) # Buddy fetches the ball.
print([Link]()) # Whiskers climbs the tree.

# Check inheritance
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True
print(isinstance(cat, Dog)) # False

Multi-level Inheritance:
class Vehicle:
"""Base vehicle class."""

def __init__(self, make, model, year):


[Link] = make
[Link] = model
[Link] = year

def start(self):
return f"{[Link]} {[Link]} {[Link]} is starting."

def stop(self):
return f"{[Link]} {[Link]} {[Link]} has stopped."

class Car(Vehicle):
"""Car class inheriting from Vehicle."""

def __init__(self, make, model, year, doors):


super().__init__(make, model, year)
[Link] = doors

def honk(self):
return f"{[Link]} {[Link]} honks: Beep! Beep!"

class ElectricCar(Car):
"""Electric car inheriting from Car."""

def __init__(self, make, model, year, doors, battery_capacity):


super().__init__(make, model, year, doors)
self.battery_capacity = battery_capacity
self.charge_level = 100

def charge(self):
self.charge_level = 100
return f"{[Link]} {[Link]} is fully charged."

def start(self): # Override start method


if self.charge_level > 0:
return f"{[Link]} {[Link]} {[Link]} starts silently."
return f"{[Link]} {[Link]} needs charging."

# Create electric car


tesla = ElectricCar("Tesla", "Model 3", 2023, 4, "75 kWh")

PYTHON PROGRAMMING - STUDY MATERIAL 48


print([Link]()) # 2023 Tesla Model 3 starts silently.
print([Link]()) # Tesla Model 3 honks: Beep! Beep!
print([Link]()) # Tesla Model 3 is fully charged.

# Check inheritance hierarchy


print(isinstance(tesla, ElectricCar)) # True
print(isinstance(tesla, Car)) # True
print(isinstance(tesla, Vehicle)) # True

8. METHOD OVERRIDING
Theory:
Method overriding allows a child class to provide a specific implementation of a method that is already defined in its parent
class.

Examples:
class Shape:
"""Base shape class."""

def __init__(self, name):


[Link] = name

def area(self):
"""Base implementation - to be overridden."""
return 0

def perimeter(self):
"""Base implementation - to be overridden."""
return 0

def describe(self):
"""Method that uses overridden methods."""
return f"{[Link]}: Area = {[Link]()}, Perimeter = {[Link]()}"

class Rectangle(Shape):
"""Rectangle class with method overriding."""

def __init__(self, length, width):


super().__init__("Rectangle")
[Link] = length
[Link] = width

def area(self): # Override parent method


return [Link] * [Link]

def perimeter(self): # Override parent method


return 2 * ([Link] + [Link])

class Circle(Shape):
"""Circle class with method overriding."""

def __init__(self, radius):


super().__init__("Circle")
[Link] = radius

PYTHON PROGRAMMING - STUDY MATERIAL 49


def area(self): # Override parent method
return 3.14159 * [Link] ** 2

def perimeter(self): # Override parent method


return 2 * 3.14159 * [Link]

class Triangle(Shape):
"""Triangle class with method overriding."""

def __init__(self, side1, side2, side3):


super().__init__("Triangle")
self.side1 = side1
self.side2 = side2
self.side3 = side3

def area(self): # Override parent method


# Using Heron's formula
s = [Link]() / 2
return (s * (s - self.side1) * (s - self.side2) * (s - self.side3)) ** 0.5

def perimeter(self): # Override parent method


return self.side1 + self.side2 + self.side3

# Create shapes
rectangle = Rectangle(5, 3)
circle = Circle(4)
triangle = Triangle(3, 4, 5)

# Polymorphism - same method call, different behavior


shapes = [rectangle, circle, triangle]

for shape in shapes:


print([Link]())

# Output:
# Rectangle: Area = 15, Perimeter = 16
# Circle: Area = 50.26544, Perimeter = 25.13272
# Triangle: Area = 6.0, Perimeter = 12

Method Overriding with super():


class Employee:
"""Base employee class."""

def __init__(self, name, salary):


[Link] = name
[Link] = salary

def get_details(self):
return f"Employee: {[Link]}, Salary: ₹{[Link]}"

def calculate_bonus(self):
return [Link] * 0.1 # 10% bonus

class Manager(Employee):
"""Manager class extending Employee."""

PYTHON PROGRAMMING - STUDY MATERIAL 50


def __init__(self, name, salary, team_size):
super().__init__(name, salary) # Call parent constructor
self.team_size = team_size

def get_details(self): # Override with additional info


base_details = super().get_details() # Call parent method
return f"{base_details}, Team Size: {self.team_size}"

def calculate_bonus(self): # Override bonus calculation


base_bonus = super().calculate_bonus() # Call parent method
team_bonus = self.team_size * 1000 # Additional team bonus
return base_bonus + team_bonus

class Developer(Employee):
"""Developer class extending Employee."""

def __init__(self, name, salary, programming_languages):


super().__init__(name, salary)
self.programming_languages = programming_languages

def get_details(self): # Override with additional info


base_details = super().get_details()
languages = ", ".join(self.programming_languages)
return f"{base_details}, Languages: {languages}"

def calculate_bonus(self): # Override bonus calculation


base_bonus = super().calculate_bonus()
skill_bonus = len(self.programming_languages) * 2000 # Bonus per language
return base_bonus + skill_bonus

# Create employees
manager = Manager("Alice", 80000, 5)
developer = Developer("Bob", 70000, ["Python", "JavaScript", "Java"])

print(manager.get_details())
print(f"Manager bonus: ₹{manager.calculate_bonus()}")

print(developer.get_details())
print(f"Developer bonus: ₹{developer.calculate_bonus()}")

# Output:
# Employee: Alice, Salary: ₹80000, Team Size: 5
# Manager bonus: ₹13000
# Employee: Bob, Salary: ₹70000, Languages: Python, JavaScript, Java
# Developer bonus: ₹13000

📚 UNIT IV: DATA STRUCTURES, ALGORITHMS & PERFORMANCE ANALYSIS


1. MODELING WITH PYTHON DATA STRUCTURES
Theory:
Python provides built-in data structures that can be used to model real-world problems efficiently. Understanding when
and how to use each structure is crucial for effective programming.

Important Definitions (Viva):


Data Structure: Way of organizing and storing data for efficient access and modification

PYTHON PROGRAMMING - STUDY MATERIAL 51


Abstract Data Type (ADT): Mathematical model for data types defined by behavior
Linear Data Structure: Elements arranged in sequence (list, stack, queue)
Non-linear Data Structure: Elements not arranged in sequence (tree, graph)

Choosing the Right Data Structure:


Use Case Best Structure Why
Ordered collection with duplicates List Maintains order, allows duplicates
Unique elements Set Fast membership testing, no duplicates
Key-value mapping Dictionary O(1) average lookup time
Immutable sequence Tuple Cannot be modified, hashable
LIFO operations List (as stack) append() and pop() are O(1)
FIFO operations [Link] popleft() is O(1)

Examples:
Student Management System:
from collections import defaultdict, Counter
from datetime import datetime

class StudentManagementSystem:
"""Complete student management system using various data structures."""

def __init__(self):
# Dictionary for O(1) student lookup by ID
[Link] = {}

# Set for unique student IDs


self.student_ids = set()

# Dictionary with lists for grouping by department


[Link] = defaultdict(list)

# List for maintaining enrollment order


self.enrollment_order = []

# Dictionary for grade tracking


[Link] = defaultdict(list)

def add_student(self, student_id, name, department, email):


"""Add new student to the system."""
if student_id in self.student_ids:
return f"Student ID {student_id} already exists!"

# Create student record


student = {
'id': student_id,
'name': name,
'department': department,
'email': email,
'enrollment_date': [Link]().strftime("%Y-%m-%d")
}

# Update all data structures


[Link][student_id] = student

PYTHON PROGRAMMING - STUDY MATERIAL 52


self.student_ids.add(student_id)
[Link][department].append(student_id)
self.enrollment_order.append(student_id)

return f"Student {name} added successfully!"

def add_grade(self, student_id, subject, grade):


"""Add grade for a student."""
if student_id not in self.student_ids:
return f"Student ID {student_id} not found!"

[Link][student_id].append({
'subject': subject,
'grade': grade,
'date': [Link]().strftime("%Y-%m-%d")
})

return f"Grade {grade} added for {subject}"

def get_student_info(self, student_id):


"""Get complete student information."""
if student_id not in self.student_ids:
return f"Student ID {student_id} not found!"

student = [Link][student_id]
grades = [Link](student_id, [])

info = f"Student: {student['name']} (ID: {student_id})\\n"


info += f"Department: {student['department']}\\n"
info += f"Email: {student['email']}\\n"
info += f"Enrolled: {student['enrollment_date']}\\n"

if grades:
info += "Grades:\\n"
for grade_record in grades:
info += f" {grade_record['subject']}: {grade_record['grade']}\\n"

# Calculate average
avg = sum(g['grade'] for g in grades) / len(grades)
info += f"Average: {avg:.2f}\\n"

return info

def get_department_stats(self, department):


"""Get statistics for a department."""
if department not in [Link]:
return f"Department {department} not found!"

student_ids = [Link][department]
total_students = len(student_ids)

# Collect all grades for department


all_grades = []
for sid in student_ids:
all_grades.extend([g['grade'] for g in [Link](sid, [])])

if not all_grades:
return f"Department: {department}\\nStudents: {total_students}\\nNo grades record

PYTHON PROGRAMMING - STUDY MATERIAL 53


ed yet."

avg_grade = sum(all_grades) / len(all_grades)


grade_distribution = Counter(all_grades)

stats = f"Department: {department}\\n"


stats += f"Total Students: {total_students}\\n"
stats += f"Average Grade: {avg_grade:.2f}\\n"
stats += f"Grade Distribution: {dict(grade_distribution)}\\n"

return stats

def get_top_students(self, n=5):


"""Get top N students by average grade."""
student_averages = []

for student_id in self.student_ids:


grades = [Link](student_id, [])
if grades:
avg = sum(g['grade'] for g in grades) / len(grades)
student_name = [Link][student_id]['name']
student_averages.append((avg, student_name, student_id))

# Sort by average grade (descending)


student_averages.sort(reverse=True)

top_students = student_averages[:n]

result = f"Top {n} Students:\\n"


for i, (avg, name, sid) in enumerate(top_students, 1):
result += f"{i}. {name} (ID: {sid}) - Average: {avg:.2f}\\n"

return result

# Example usage
sms = StudentManagementSystem()

# Add students
print(sms.add_student("CS001", "Alice Johnson", "Computer Science", "alice@[Link]"))
print(sms.add_student("CS002", "Bob Smith", "Computer Science", "bob@[Link]"))
print(sms.add_student("EE001", "Charlie Brown", "Electrical Engineering", "charlie@[Link]
m"))

# Add grades
sms.add_grade("CS001", "Python Programming", 95)
sms.add_grade("CS001", "Data Structures", 88)
sms.add_grade("CS001", "Algorithms", 92)

sms.add_grade("CS002", "Python Programming", 78)


sms.add_grade("CS002", "Data Structures", 85)

sms.add_grade("EE001", "Circuit Analysis", 90)


sms.add_grade("EE001", "Digital Logic", 87)

# Get information
print("\\n" + "="*50)
print(sms.get_student_info("CS001"))
print("="*50)

PYTHON PROGRAMMING - STUDY MATERIAL 54


print(sms.get_department_stats("Computer Science"))
print("="*50)
print(sms.get_top_students(3))

E-commerce Inventory System:


from collections import defaultdict, deque
import heapq
from datetime import datetime, timedelta

class InventorySystem:
"""E-commerce inventory management using various data structures."""

def __init__(self):
# Dictionary for product information
[Link] = {}

# Dictionary for category-wise grouping


[Link] = defaultdict(set)

# Priority queue for low stock alerts (min-heap)


self.low_stock_alerts = []

# Deque for recent transactions (FIFO)


self.recent_transactions = deque(maxlen=100)

# Dictionary for supplier information


[Link] = defaultdict(list)

# Set for tracking product IDs


self.product_ids = set()

def add_product(self, product_id, name, category, price, stock, supplier, min_stock=10):


"""Add new product to inventory."""
if product_id in self.product_ids:
return f"Product ID {product_id} already exists!"

product = {
'id': product_id,
'name': name,
'category': category,
'price': price,
'stock': stock,
'supplier': supplier,
'min_stock': min_stock,
'added_date': [Link]().strftime("%Y-%m-%d %H:%M:%S")
}

# Update data structures


[Link][product_id] = product
self.product_ids.add(product_id)
[Link][category].add(product_id)
[Link][supplier].append(product_id)

# Check for low stock


if stock <= min_stock:
[Link](self.low_stock_alerts, (stock, product_id, name))

PYTHON PROGRAMMING - STUDY MATERIAL 55


# Record transaction
self.recent_transactions.append({
'type': 'ADD_PRODUCT',
'product_id': product_id,
'quantity': stock,
'timestamp': [Link]().strftime("%Y-%m-%d %H:%M:%S")
})

return f"Product {name} added successfully!"

def update_stock(self, product_id, quantity_change, transaction_type="MANUAL"):


"""Update product stock."""
if product_id not in self.product_ids:
return f"Product ID {product_id} not found!"

product = [Link][product_id]
old_stock = product['stock']
new_stock = old_stock + quantity_change

if new_stock < 0:
return f"Insufficient stock! Current: {old_stock}, Requested: {abs(quantity_chang
e)}"

product['stock'] = new_stock

# Update low stock alerts


if new_stock <= product['min_stock']:
[Link](self.low_stock_alerts, (new_stock, product_id, product['name']))

# Record transaction
self.recent_transactions.append({
'type': transaction_type,
'product_id': product_id,
'quantity_change': quantity_change,
'old_stock': old_stock,
'new_stock': new_stock,
'timestamp': [Link]().strftime("%Y-%m-%d %H:%M:%S")
})

return f"Stock updated: {product['name']} - {old_stock} → {new_stock}"

def sell_product(self, product_id, quantity):


"""Sell product and update stock."""
return self.update_stock(product_id, -quantity, "SALE")

def restock_product(self, product_id, quantity):


"""Restock product."""
return self.update_stock(product_id, quantity, "RESTOCK")

def get_low_stock_alerts(self, limit=10):


"""Get products with low stock."""
alerts = []
temp_heap = []

# Extract alerts while preserving heap


for _ in range(min(limit, len(self.low_stock_alerts))):
if self.low_stock_alerts:

PYTHON PROGRAMMING - STUDY MATERIAL 56


alert = [Link](self.low_stock_alerts)
[Link](alert)
temp_heap.append(alert)

# Restore heap
for alert in temp_heap:
[Link](self.low_stock_alerts, alert)

if not alerts:
return "No low stock alerts!"

result = "LOW STOCK ALERTS:\\n"


for stock, product_id, name in alerts:
min_stock = [Link][product_id]['min_stock']
result += f"⚠️ {name} (ID: {product_id}) - Stock: {stock} (Min: {min_stock})\\n"

return result

def get_category_report(self, category):


"""Get report for specific category."""
if category not in [Link]:
return f"Category {category} not found!"

product_ids = [Link][category]
total_products = len(product_ids)
total_value = 0
total_stock = 0

products_info = []

for pid in product_ids:


product = [Link][pid]
stock = product['stock']
price = product['price']
value = stock * price

total_stock += stock
total_value += value

products_info.append({
'name': product['name'],
'stock': stock,
'price': price,
'value': value
})

# Sort by value (descending)


products_info.sort(key=lambda x: x['value'], reverse=True)

report = f"CATEGORY REPORT: {category}\\n"


report += f"Total Products: {total_products}\\n"
report += f"Total Stock: {total_stock} units\\n"
report += f"Total Value: ₹{total_value:,.2f}\\n\\n"
report += "Products (sorted by value):\\n"

for product in products_info:


report += f" {product['name']}: {product['stock']} units @ ₹{product['price']} =
₹{product['value']:,.2f}\\n"

PYTHON PROGRAMMING - STUDY MATERIAL 57


return report

def get_recent_transactions(self, limit=10):


"""Get recent transactions."""
if not self.recent_transactions:
return "No recent transactions!"

transactions = list(self.recent_transactions)[-limit:]

result = f"RECENT TRANSACTIONS (Last {len(transactions)}):\\n"


for transaction in reversed(transactions):
result += f"{transaction['timestamp']} - {transaction['type']}: "
result += f"Product {transaction['product_id']}"

if 'quantity_change' in transaction:
change = transaction['quantity_change']
result += f" ({'+' if change > 0 else ''}{change} units)"
result += f" [{transaction['old_stock']} → {transaction['new_stock']}]"

result += "\\n"

return result

def search_products(self, query):


"""Search products by name or ID."""
results = []
query_lower = [Link]()

for product_id, product in [Link]():


if (query_lower in product['name'].lower() or
query_lower in product_id.lower()):
[Link](product)

if not results:
return f"No products found for '{query}'"

result = f"SEARCH RESULTS for '{query}':\\n"


for product in results:
result += f" {product['name']} (ID: {product['id']}) - "
result += f"Stock: {product['stock']}, Price: ₹{product['price']}\\n"

return result

# Example usage
inventory = InventorySystem()

# Add products
print(inventory.add_product("LAPTOP001", "Gaming Laptop", "Electronics", 75000, 5, "TechCor
p", 3))
print(inventory.add_product("MOUSE001", "Wireless Mouse", "Electronics", 1500, 25, "TechCor
p", 10))
print(inventory.add_product("BOOK001", "Python Programming", "Books", 800, 50, "BookPublishe
r", 5))
print(inventory.add_product("CHAIR001", "Office Chair", "Furniture", 8000, 2, "FurnitureCor
p", 5))

print("\\n" + "="*60)

PYTHON PROGRAMMING - STUDY MATERIAL 58


# Perform some transactions
print(inventory.sell_product("LAPTOP001", 2))
print(inventory.sell_product("MOUSE001", 8))
print(inventory.restock_product("CHAIR001", 10))

print("\\n" + "="*60)

# Get reports
print(inventory.get_low_stock_alerts())
print("="*60)
print(inventory.get_category_report("Electronics"))
print("="*60)
print(inventory.get_recent_transactions(5))
print("="*60)
print(inventory.search_products("laptop"))

2. STACKS AND QUEUES


Theory:
Stack follows LIFO (Last In, First Out) principle. Queue follows FIFO (First In, First Out) principle.

Important Definitions:
Stack: Linear data structure with LIFO access
Queue: Linear data structure with FIFO access
Push: Add element to stack
Pop: Remove element from stack
Enqueue: Add element to queue
Dequeue: Remove element from queue

Stack Implementation:
class Stack:
"""Stack implementation using list."""

def __init__(self):
[Link] = []

def push(self, item):


"""Add item to top of stack."""
[Link](item)

def pop(self):
"""Remove and return top item."""
if self.is_empty():
raise IndexError("Stack is empty")
return [Link]()

def peek(self):
"""Return top item without removing."""
if self.is_empty():
raise IndexError("Stack is empty")
return [Link][-1]

PYTHON PROGRAMMING - STUDY MATERIAL 59


def is_empty(self):
"""Check if stack is empty."""
return len([Link]) == 0

def size(self):
"""Return number of items in stack."""
return len([Link])

def __str__(self):
return f"Stack({[Link]})"

# Stack applications
def balanced_parentheses(expression):
"""Check if parentheses are balanced using stack."""
stack = Stack()
opening = "({["
closing = ")}]"
pairs = {"(": ")", "{": "}", "[": "]"}

for char in expression:


if char in opening:
[Link](char)
elif char in closing:
if stack.is_empty():
return False

top = [Link]()
if pairs[top] != char:
return False

return stack.is_empty()

def evaluate_postfix(expression):
"""Evaluate postfix expression using stack."""
stack = Stack()
operators = {'+', '-', '*', '/', '//', '%', '**'}

tokens = [Link]()

for token in tokens:


if token in operators:
if [Link]() < 2:
raise ValueError("Invalid postfix expression")

b = [Link]()
a = [Link]()

if token == '+':
result = a + b
elif token == '-':
result = a - b
elif token == '*':
result = a * b
elif token == '/':
result = a / b
elif token == '//':
result = a // b
elif token == '%':

PYTHON PROGRAMMING - STUDY MATERIAL 60


result = a % b
elif token == '**':
result = a ** b

[Link](result)
else:
try:
number = float(token)
[Link](number)
except ValueError:
raise ValueError(f"Invalid token: {token}")

if [Link]() != 1:
raise ValueError("Invalid postfix expression")

return [Link]()

def infix_to_postfix(expression):
"""Convert infix to postfix using stack."""
stack = Stack()
output = []

# Operator precedence
precedence = {'+': 1, '-': 1, '*': 2, '/': 2, '//': 2, '%': 2, '**': 3}
right_associative = {'**'}

tokens = [Link]('(', ' ( ').replace(')', ' ) ').split()

for token in tokens:


if [Link]() or [Link]('.', '').isdigit():
[Link](token)
elif token == '(':
[Link](token)
elif token == ')':
while not stack.is_empty() and [Link]() != '(':
[Link]([Link]())
if not stack.is_empty():
[Link]() # Remove '('
elif token in precedence:
while (not stack.is_empty() and
[Link]() != '(' and
[Link]() in precedence and
(precedence[[Link]()] > precedence[token] or
(precedence[[Link]()] == precedence[token] and
token not in right_associative))):
[Link]([Link]())
[Link](token)

while not stack.is_empty():


[Link]([Link]())

return ' '.join(output)

# Test stack applications


print("=== STACK APPLICATIONS ===")
print(f"Balanced '((()))': {balanced_parentheses('((()))')}") # True
print(f"Balanced '([)]': {balanced_parentheses('([)]')}") # False

PYTHON PROGRAMMING - STUDY MATERIAL 61


print(f"Postfix '3 4 + 2 *': {evaluate_postfix('3 4 + 2 *')}") # 14
print(f"Infix to Postfix '3 + 4 * 2': {infix_to_postfix('3 + 4 * 2')}") # 3 4 2 * +

Queue Implementation:
from collections import deque

class Queue:
"""Queue implementation using deque for efficiency."""

def __init__(self):
[Link] = deque()

def enqueue(self, item):


"""Add item to rear of queue."""
[Link](item)

def dequeue(self):
"""Remove and return front item."""
if self.is_empty():
raise IndexError("Queue is empty")
return [Link]()

def front(self):
"""Return front item without removing."""
if self.is_empty():
raise IndexError("Queue is empty")
return [Link][0]

def rear(self):
"""Return rear item without removing."""
if self.is_empty():
raise IndexError("Queue is empty")
return [Link][-1]

def is_empty(self):
"""Check if queue is empty."""
return len([Link]) == 0

def size(self):
"""Return number of items in queue."""
return len([Link])

def __str__(self):
return f"Queue({list([Link])})"

class PriorityQueue:
"""Priority queue implementation using heap."""

def __init__(self):
[Link] = []
[Link] = 0

def enqueue(self, item, priority):


"""Add item with priority."""
import heapq
[Link]([Link], (priority, [Link], item))

PYTHON PROGRAMMING - STUDY MATERIAL 62


[Link] += 1

def dequeue(self):
"""Remove and return highest priority item."""
if self.is_empty():
raise IndexError("Priority queue is empty")
import heapq
priority, index, item = [Link]([Link])
return item

def is_empty(self):
"""Check if priority queue is empty."""
return len([Link]) == 0

def size(self):
"""Return number of items."""
return len([Link])

# Queue applications
def hot_potato_game(names, num):
"""Hot potato game using queue."""
queue = Queue()

# Add all names to queue


for name in names:
[Link](name)

while [Link]() > 1:


# Pass the potato
for _ in range(num):
[Link]([Link]())

# Remove the person with potato


eliminated = [Link]()
print(f"{eliminated} is eliminated!")

return [Link]()

def breadth_first_search(graph, start):


"""BFS traversal using queue."""
visited = set()
queue = Queue()
result = []

[Link](start)
[Link](start)

while not queue.is_empty():


vertex = [Link]()
[Link](vertex)

for neighbor in [Link](vertex, []):


if neighbor not in visited:
[Link](neighbor)
[Link](neighbor)

return result

PYTHON PROGRAMMING - STUDY MATERIAL 63


def task_scheduler():
"""Task scheduling using priority queue."""
pq = PriorityQueue()

# Add tasks with priorities (lower number = higher priority)


[Link]("Critical Bug Fix", 1)
[Link]("Code Review", 3)
[Link]("Documentation", 5)
[Link]("Security Update", 1)
[Link]("Feature Development", 4)

print("Task execution order:")


while not pq.is_empty():
task = [Link]()
print(f"Executing: {task}")

# Test queue applications


print("\\n=== QUEUE APPLICATIONS ===")
winner = hot_potato_game(["Alice", "Bob", "Charlie", "Diana", "Eve"], 3)
print(f"Winner: {winner}")

print("\\nBFS Traversal:")
graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E']
}
bfs_result = breadth_first_search(graph, 'A')
print(f"BFS from A: {bfs_result}")

print("\\nTask Scheduling:")
task_scheduler()

3. TREES: NODE-BASED AND LIST-BASED REPRESENTATIONS


Theory:
Trees are hierarchical data structures with nodes connected by edges. Each tree has a root node, and each node can have
child nodes.

Important Definitions:
Tree: Hierarchical data structure with nodes and edges
Root: Top node of the tree
Leaf: Node with no children
Parent: Node with children
Child: Node connected to parent
Subtree: Tree formed by a node and its descendants
Height: Maximum depth of the tree
Depth: Distance from root to a node

Node-based Tree Implementation:

PYTHON PROGRAMMING - STUDY MATERIAL 64


class TreeNode:
"""Node for binary tree."""

def __init__(self, data):


[Link] = data
[Link] = None
[Link] = None

def __str__(self):
return str([Link])

class BinaryTree:
"""Binary tree implementation using nodes."""

def __init__(self, root_data=None):


if root_data is not None:
[Link] = TreeNode(root_data)
else:
[Link] = None

def insert_left(self, parent_node, data):


"""Insert left child."""
if parent_node.left is None:
parent_node.left = TreeNode(data)
else:
new_node = TreeNode(data)
new_node.left = parent_node.left
parent_node.left = new_node
return parent_node.left

def insert_right(self, parent_node, data):


"""Insert right child."""
if parent_node.right is None:
parent_node.right = TreeNode(data)
else:
new_node = TreeNode(data)
new_node.right = parent_node.right
parent_node.right = new_node
return parent_node.right

def height(self, node=None):


"""Calculate height of tree."""
if node is None:
node = [Link]

if node is None:
return -1

if [Link] is None and [Link] is None:


return 0

left_height = [Link]([Link]) if [Link] else -1


right_height = [Link]([Link]) if [Link] else -1

return 1 + max(left_height, right_height)

def count_nodes(self, node=None):

PYTHON PROGRAMMING - STUDY MATERIAL 65


"""Count total nodes in tree."""
if node is None:
node = [Link]

if node is None:
return 0

return 1 + self.count_nodes([Link]) + self.count_nodes([Link])

def count_leaves(self, node=None):


"""Count leaf nodes."""
if node is None:
node = [Link]

if node is None:
return 0

if [Link] is None and [Link] is None:


return 1

return self.count_leaves([Link]) + self.count_leaves([Link])

def find_node(self, data, node=None):


"""Find node with given data."""
if node is None:
node = [Link]

if node is None:
return None

if [Link] == data:
return node

# Search in left subtree


left_result = self.find_node(data, [Link])
if left_result:
return left_result

# Search in right subtree


return self.find_node(data, [Link])

def level_order_traversal(self):
"""Level order traversal using queue."""
if [Link] is None:
return []

result = []
queue = Queue()
[Link]([Link])

while not queue.is_empty():


node = [Link]()
[Link]([Link])

if [Link]:
[Link]([Link])
if [Link]:
[Link]([Link])

PYTHON PROGRAMMING - STUDY MATERIAL 66


return result

def print_tree(self, node=None, level=0, prefix="Root: "):


"""Print tree structure."""
if node is None:
node = [Link]

if node is not None:


print(" " * (level * 4) + prefix + str([Link]))
if [Link] is not None or [Link] is not None:
if [Link]:
self.print_tree([Link], level + 1, "L--- ")
else:
print(" " * ((level + 1) * 4) + "L--- None")

if [Link]:
self.print_tree([Link], level + 1, "R--- ")
else:
print(" " * ((level + 1) * 4) + "R--- None")

# Example: Building expression tree


def build_expression_tree():
"""Build expression tree for (3 + 4) * 2."""
tree = BinaryTree('*')

# Left subtree: (3 + 4)
plus_node = tree.insert_left([Link], '+')
tree.insert_left(plus_node, 3)
tree.insert_right(plus_node, 4)

# Right subtree: 2
tree.insert_right([Link], 2)

return tree

def evaluate_expression_tree(node):
"""Evaluate expression tree."""
if node is None:
return 0

# Leaf node

PYTHON PROGRAMMING - STUDY MATERIAL 67

You might also like