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

Main Python[1]

The document contains multiple Python programs and explanations covering various topics such as reversing words in a file, creating and updating a MongoDB collection, converting decimal numbers to octal and hexadecimal, and validating email addresses. It also discusses data structures like ArrayList and LinkedList, cloning methods, deadlock avoidance in threads, and object serialization. Additionally, it includes a program to reverse a number and find the sum of its digits, along with guidelines on when to use modules in code.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views34 pages

Main Python[1]

The document contains multiple Python programs and explanations covering various topics such as reversing words in a file, creating and updating a MongoDB collection, converting decimal numbers to octal and hexadecimal, and validating email addresses. It also discusses data structures like ArrayList and LinkedList, cloning methods, deadlock avoidance in threads, and object serialization. Additionally, it includes a program to reverse a number and find the sum of its digits, along with guidelines on when to use modules in code.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 34

Q3 (a) Program to Reverse Each Word of "data.

txt" File
# Function to reverse each word in a file
def reverse_words_in_file(filename):
try:
with open(filename, 'r') as file:
lines = file.readlines()

with open(filename, 'w') as file:


for line in lines:
words = line.split()
reversed_line = ' '.join(word[::-1] for word in words) + '\n'
file.write(reversed_line)
print("Words reversed successfully!")
except FileNotFoundError:
print("File not found.")

# Call the function


reverse_words_in_file("data.txt")

Q3 (b) Python Program Using MongoDB to Create "Books" Collection


from pymongo import MongoClient

# Connect to MongoDB
client = MongoClient("mongodb://localhost:27017/")
db = client["Library"]
collection = db["Books"]

# Accepting input from the user


title = input("Enter book title: ")
authors = input("Enter authors (comma separated): ").split(",")
publisher = input("Enter publisher: ")
pub_address = {
"area": input("Enter area: "),
"city": input("Enter city: "),
"country": input("Enter country: ")
}
price = float(input("Enter price: "))
isbn = input("Enter ISBN: ")

# Document to insert into the collection


book = {
"title": title,
"authors": authors,
"publisher": publisher,
"pub_address": pub_address,
"price": price,
"isbn": isbn
}

# Inserting document into the collection


collection.insert_one(book)
print("Book document inserted successfully.")
Q3 (c) MongoDB Program to Update "Books" Collection
from pymongo import MongoClient

# Connect to MongoDB
client = MongoClient("mongodb://localhost:27017/")
db = client["Library"]
collection = db["Books"]

# Accept ISBN and new price to update


isbn_to_update = input("Enter ISBN to update: ")
new_price = float(input("Enter new price: "))

# Update the price in the Books collection


collection.update_one(
{"isbn": isbn_to_update},
{"$set": {"price": new_price}}
)

print(f"Book with ISBN {isbn_to_update} updated successfully.")

Q3 (d) Program to Accept Decimal Number and Print Its Octal and exadecimal
Equivalent
# Function to convert decimal to octal and hexadecimal

def convert_decimal(num):
octal = oct(num)
hexadecimal = hex(num)
print(f"Octal: {octal}, Hexadecimal: {hexadecimal}")

# Accept decimal input from user


try:
decimal_number = int(input("Enter a decimal number: "))
convert_decimal(decimal_number)
except ValueError:
print("Invalid input! Please enter a valid decimal number.")

Explanation:
· The program takes a decimal number as input and prints its octal and hexadecimal equivalents
using Python's built-in oct() and hex() functions.
1) Difference Between ArrayList and LinkedList
Feature ArrayList LinkedList
Resizable array (uses an array Doubly linked list (each element points to
Data Structure
internally) both previous and next)
Non-contiguous memory allocation (each
Memory Allocation Contiguous memory allocation
node is stored separately)
Access Time Fast for random access (O(1)) Slower for random access (O(n))
Slow for insertion/deletion in the Faster for insertion/deletion (O(1)) at the ends
Insertion/Deletion
middle (O(n)) or in the middle (if the node is known)
Better for frequent access
Performance Better for frequent insertions and deletions
operations like get()
Suitable for applications that Suitable for applications that require frequent
Usage
require fast random access insertion and deletion
Resizes automatically when No resizing is required, grows as elements are
Resizing
elements exceed capacity added
Underlying Data
Array Doubly Linked List
Structure

Explanation:
· ArrayList is more suitable for scenarios where you need fast access to elements (such as reading
an element by its index). However, insertions and deletions, especially in the middle, can be
costly as elements need to be shifted.

· LinkedList is ideal for applications that require frequent insertions and deletions, particularly in
the middle of the list. However, accessing elements is slower compared to an ArrayList.

Program to Validate Email Address Using Regular Expression


import re
def validate_email(email):
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if re.match(pattern, email):
return True
else:
return False
# Test the function
email = input("Enter email address: ")
if validate_email(email):
print("Valid email address")
else:
print("Invalid email address")
3) What is Hashing in Collection Framework?
· hashing in the Collection Framework refers to the use of hash functions to store and retrieve
data efficiently, especially in hash-based data structures like set, dict, and some specialized
classes from the collections module (like defaultdict, OrderedDict, etc.)

· class Person:

· def __init__(self, name):

· self.name = name

def __hash__(self):

return hash(self.name)

def __eq__(self, ther):

return self.name == other.name

p1 = Person("Alice")

p2 = Person("Alice")

print(hash(p1), hash(p2)) # Same hash

print(p1 == p2) # True

4) Difference Between Run-time and Compile-time Polymorphism


Aspect Compile-time Polymorphism Run-time Polymorphism
Also Known As Method Overloading, Operator Overloading Method Overriding
Binding Time Occurs at compile time Occurs at runtime
Method Methods are resolved at compile time based Methods are resolved at runtime based
Resolution on the method signature on object type
Method overriding (e.g., super
Example Method overloading, operator overloading
keyword usage)
Less flexible (the method is chosen at More flexible (method is chosen at
Flexibility
compile time) runtime)
Slightly faster because method binding Slightly slower due to dynamic method
Performance
occurs at compile time resolution at runtime

1. Compile-time Polymorphism (Method Overloading) in


Python
Python does not support method overloading in the same way Java does. If you define multiple
methods with the same name, only the last one will be used.

However, we can simulate method overloading using default arguments, *args, or type checking.

✅ Python Version Using Type Checking:


python
CopyEdit
class Calculator:
def add(self, a, b):
if isinstance(a, int) and isinstance(b, int):
print("Adding integers:")
return a + b
elif isinstance(a, float) and isinstance(b, float):
print("Adding floats:")
return a + b
else:
raise TypeError("Unsupported types")

calc = Calculator()
print(calc.add(2, 3)) # Output: 5
print(calc.add(2.5, 3.5)) # Output: 6.0

🐶 2. Run-time Polymorphism (Method Overriding) in Python


Python fully supports method overriding using inheritance. This is similar to Java.

✅ Python Version:
python
CopyEdit
class Animal:
def sound(self):
print("Animal makes a sound")

class Dog(Animal):
def sound(self):
print("Dog barks")

# Runtime Polymorphism
animal = Dog()
animal.sound() # Output: Dog barks

Here, even though animal is declared as an Animal, Python correctly calls the Dog class's sound()
method at runtime. This is run-time polymorphism.
Shallow Cloning and Deep CloningIn Python, cloning or copying refers to creating a duplicate of
an existing object. There are two types of cloning: Shallow Cloning and Deep Cloning.

1. Shallow Cloning:

· Definition: In shallow cloning, a new object is created, but the elements inside the object (like
nested lists or objects) are not copied. Instead, references to the original objects are copied.

· Behavior: When a shallow clone is created, the top-level structure of the object is copied, but
nested objects (objects within the original object) are shared between the original and the
clone. Changes made to nested objects affect both the original and the clone.

· How to create: The copy() method or copy module can be used for shallow cloning.

· Example:

python
CopyEdit
import copy

original = [[1, 2], [3, 4]] # A list containing other lists


shallow_copy = copy.copy(original) # Creating shallow copy

# Modifying the nested object in shallow_copy


shallow_copy[0][0] = 99

print("Original:", original) # [[99, 2], [3, 4]]


print("Shallow Copy:", shallow_copy) # [[99, 2], [3, 4]]

Explanation: After modifying shallow_copy, the change is reflected in the original object as
well, since both objects point to the same nested list.

2. Deep Cloning:

· Definition: In deep cloning, both the object and all the objects contained within it (recursively)
are copied, meaning that all nested objects are also cloned. No references are shared between
the original and the clone.

· Behavior: Changes made to the nested objects in the deep clone do not affect the original
object and vice versa.

· How to create: The copy.deepcopy() method is used for deep cloning.

· Example:

python
CopyEdit
import copy

original = [[1, 2], [3, 4]] # A list containing other lists


deep_copy = copy.deepcopy(original) # Creating deep copy
# Modifying the nested object in deep_copy
deep_copy[0][0] = 99

print("Original:", original) # [[1, 2], [3, 4]]


print("Deep Copy:", deep_copy) # [[99, 2], [3, 4]]

Explanation: In the deep copy, changing the nested list in deep_copy does not affect
original, because the inner lists are copied entirely.

How Deadlock Can Be Avoided in ThreadsDeadlock occurs when two or more threads are
blocked forever because they are waiting for each other to release a resource. To prevent deadlocks, the
following techniques can be used:1. Lock Ordering:Explanation: Ensure that all threads acquire locks
in the same order. If every thread follows the same order for acquiring locks, it prevents circular wait
conditions, which is one of the necessary conditions for deadlock. xample:python

import threading
lock1 = threading.Lock()
lock2 = threading.Lock()
def thread1():
lock1.acquire()
lock2.acquire()
print("Thread 1 is working.")
lock2.release()
lock1.release()
def thread2():
lock1.acquire()
lock2.acquire()
print("Thread 2 is working.")
lock2.release()
lock1.release()In this case, both threads acquire the locks in the same order (lock1
followed by lock2), avoiding deadlock.

2. Lock Timeout:

· Explanation: Another approach is to set a timeout when acquiring a lock. If a thread cannot
acquire the lock within the specified time, it releases any locks it already holds and tries again
later.

· Example:

python
CopyEdit
import threading
import time

lock1 = threading.Lock()
lock2 = threading.Lock()

def thread1():
if lock1.acquire(timeout=1):
print("Thread 1 acquired lock1")
time.sleep(2)
if lock2.acquire(timeout=1):
print("Thread 1 acquired lock2")
lock2.release()
lock1.release()

def thread2():
if lock2.acquire(timeout=1):
print("Thread 2 acquired lock2")
time.sleep(2)
if lock1.acquire(timeout=1):
print("Thread 2 acquired lock1")
lock1.release()
lock2.release()

What is Object Serialization?Object Serialization is the process of converting an object into a


format (like a byte stream or JSON) that can be easily stored in a file, transmitted over a network, or
saved into a database. Once the object is serialized, it can be deserialized (or reconstructed) back into
the original object.

Uses:Persistence: Storing objects in files or databases.Communication: Sending objects over networks,


e.g., in distributed systems or web services.Caching: Storing serialized objects to retrieve them later,
reducing the need for recomputation.

Example in Python:

· Using pickle module (for Python object serialization):

· import pickle

# Object to be serialized
data = {'name': 'Alice', 'age': 30, 'city': 'New York'}
# Serialize object to a byte stream (serialize to a file)
with open('data.pkl', 'wb') as f:
pickle.dump(data, f)
# Deserialize the object
with open('data.pkl', 'rb') as f:
loaded_data = pickle.load(f)
print(loaded_data

Explanation:

· Serialization: The dictionary data is serialized using pickle.dump() into a file (data.pkl).

· Deserialization: The file data.pkl is read, and the serialized object is reconstructed using
pickle.load().

Conclusion:
· Shallow Cloning copies references to nested objects, while Deep Cloning copies the entire
object, including nested objects, ensuring no shared references.

· Deadlock in threads can be avoided using techniques like lock ordering, lock timeouts, and
avoiding nested locks.

· Object Serialization is the process of converting an object into a format (like byte streams or
JSON) for storage or transmission, and it is often used in saving objects or in distributed systems.

Python Program to Reverse a Number and Find the Sum of Digits in the Reverse
Number
Here’s a Python program that reverses a number and calculates the sum of digits of the reversed
number.

# Function to reverse a number and calculate the sum of digits of the reversed
number
def reverse_and_sum_digits():
# Take input from user
number = int(input("Enter a number: "))

# Initialize variables
reversed_number = 0
sum_of_digits = 0

# Reverse the number and calculate the sum of digits


while number != 0:
digit = number % 10 # Get the last digit
reversed_number = reversed_number * 10 + digit # Build the reversed
number
sum_of_digits += digit # Add digit to sum
number //= 10 # Remove the last digit from the number

# Print the results


print(f"Reversed number: {reversed_number}")
print(f"Sum of digits in the reversed number: {sum_of_digits}")

# Call the function


reverse_and_sum_digits()

Explanation:
1. Input: The program prompts the user to input a number.

2. Reversing the number: The number is reversed by using modulus (%) and integer division (//).

3. Sum of digits: While reversing the number, the program simultaneously adds the digits of the
reversed number to a variable sum_of_digits.

4. Output: Finally, the program prints the reversed number and the sum of digits of the reversed
number.
Example Run:
Enter a number: 1234
Reversed number: 4321
Sum of digits in the reversed number: 10

How to Determine When to Use a Module in Your Code (with Justification)


Modules are used in Python to organize and reuse code, and they help in separating concerns,
improving maintainability, and avoiding code repetition.

When to Use a Module:

1. Reuse of Code: If you find yourself writing the same or similar code multiple times across
different scripts, it’s a good practice to place that code into a module.

2. Better Organization: When a program becomes large, it’s important to separate logically
different pieces of functionality into separate modules for better organization and readability.

3. External Libraries: If a functionality you need is available in an external Python library (module),
you can simply import it and use it, saving time and effort.

Example of Using a Module:

Suppose you have a task that requires mathematical operations like finding the area of a circle, which
can be reused in multiple programs. You can create a module for this functionality.

1. Create a Module (circle_operations.py):

# circle_operations.py
import math

def area_of_circle(radius):
return math.pi * (radius ** 2)

2. Use the Module in Another Program:

# main_program.py
import circle_operations

radius = float(input("Enter the radius of the circle: "))


area = circle_operations.area_of_circle(radius)
print(f"The area of the circle is: {area}")

Justification:
· Using a module here makes your code reusable, and you only need to write the function
area_of_circle() once.

· You can use this module in multiple programs without duplicating the same logic.

· It keeps the code organized and easier to maintain as your project grows.

OR: Decorator Concept in Python


A decorator is a function that takes another function as an argument and extends or alters its behavior
without modifying its structure. Decorators are a very powerful and useful tool in Python because they
allow you to add functionality to an existing function in a clean and readable way.

Example of Decorator:
# Basic decorator function
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper

# Function to be decorated
@my_decorator
def say_hello():
print("Hello!")

# Calling the decorated function


say_hello()

Explanation:
1. Decorator Function: my_decorator is the decorator function. It takes a function func as an
argument and defines a nested function wrapper(). The wrapper() function adds some extra
behavior before and after calling func().

2. Using the Decorator: The @my_decorator syntax is used to apply the decorator to the
say_hello() function. This makes say_hello() call wrapper() instead, which adds extra
behavior while calling say_hello().

Output:
Something is happening before the function is called.
Hello!
Something is happening after the function is called.
Decorators are useful for tasks like logging, access control, memoization, and more.

Function to Calculate the Average of Numbers Entered by the User


Here's a Python program to calculate the average of numbers entered by the user:

# Function to calculate average of user input numbers


def average():
# Accept input from the user
numbers = input("Enter numbers separated by space: ").split()

# Convert string inputs to integers


numbers = [int(num) for num in numbers]

# Calculate the average


avg = sum(numbers) / len(numbers)

return avg

# Call the function and print the result


result = average()
print(f"The average of the entered numbers is: {result}")

Explanation:
1. Input: The user is prompted to enter numbers separated by spaces.

2. Processing: The input string is split into individual elements, and each element is converted to
an integer.

3. Calculation: The sum of the numbers is divided by the total count of numbers to find the
average.

4. Output: The average is returned and printed.

Example Run:
Enter numbers separated by space: 10 20 30 40
The average of the entered numbers is: 25.0
Conclusion:
· Shallow and Deep Cloning: These techniques are important for understanding object behavior
in Python. Shallow cloning only copies references to nested objects, whereas deep cloning
creates independent copies of all objects.

· Using Modules: Modules help in organizing and reusing code. You can use a module when you
want to reuse logic across different programs or to keep code organized.

· Python Program to Reverse a Number and Find the Sum of Digits in the
Reverse Number
Here's the Python program that reverses the number entered by the user and calculates the sum of
digits in the reversed number:

# Function to reverse a number and calculate the sum of digits of the reversed
number
def reverse_and_sum_digits():
# Take input from the user
number = int(input("Enter a number: "))

# Initialize variables
reversed_number = 0
sum_of_digits = 0

# Reverse the number and calculate the sum of digits


while number != 0:
digit = number % 10 # Get the last digit
reversed_number = reversed_number * 10 + digit # Build the reversed
number
sum_of_digits += digit # Add digit to sum
number //= 10 # Remove the last digit from the number

# Print the results


print(f"Reversed number: {reversed_number}")
print(f"Sum of digits in the reversed number: {sum_of_digits}")

# Call the function


reverse_and_sum_digits()

Explanation:
1. Input: The program prompts the user to input a number.

2. Reversing the number: The number is reversed by using modulus (%) and integer division (//).

3. Sum of digits: While reversing the number, the program simultaneously adds the digits of the
reversed number to a variable sum_of_digits.

4. Output: Finally, the program prints the reversed number and the sum of digits of the reversed
number.
Example Run:
Enter a number: 1234
Reversed number: 4321
Sum of digits in the reversed number: 10

How to Determine When to Use a Module in Your Code


Modules are used in Python to structure and organize code, and they allow you to reuse functionality
across multiple programs or scripts. Here’s how to determine when to use a module:

When to Use a Module:

1. Code Reusability: If you have a set of related functions or variables that you use frequently
across different programs, it is a good idea to place them into a module.

2. Organization: As your codebase grows, it’s important to break down your code into smaller,
more manageable files. Using modules helps you organize your code logically and keeps it more
readable.

3. Separation of Concerns: If your program does multiple things, like handling user input,
performing calculations, and writing output, it's beneficial to separate these tasks into different
modules.

Example:Suppose you have a program that deals with geometric calculations (like finding the area of
different shapes). You can create a module to handle these calculations, so you can reuse it in multiple
programs without rewriting the code.

Creating a Module (geometry.py):

# geometry.py

import math
def area_of_circle(radius):
return math.pi * (radius ** 2)
def area_of_square(side):
return side * side
Using the Module in Another Program (main.py):
# main.py
import geometry
radius = float(input("Enter the radius of the circle: "))
area_circle = geometry.area_of_circle(radius)
print(f"The area of the circle is: {area_circle}")
side = float(input("Enter the side of the square: "))
area_square = geometry.area_of_square(side)
print(f"The area of the square is: {area_square}")

a) Explain the Decorator Concept with a Suitable Example


A decorator in Python is a design pattern that allows you to extend or modify the behavior of a callable
(like functions or methods) without modifying the actual code of the callable. It takes a function as input
and returns a new function that extends or alters its behavior.How Decorators WorkA decorator is a
function that takes another function as an argument and returns a new function that adds additional
functionality before or after calling the original function.

Example of Decorator:# Basic decorator function


def my_decorator(func):

def wrapper():

print("Before function call.")


func()
print("After function call.")
return wrapper

# Function to be decorated
@my_decorator
def say_hello():
print("Hello, World!")

# Call the decorated function


say_hello()

Explanation:
1. my_decorator: This is the decorator function. It takes a function func as an argument.

2. wrapper: This is a new function that wraps around the original func to add extra behavior
before and after the function call.

3. @my_decorator: This is the decorator syntax, which is shorthand for say_hello =


my_decorator(say_hello).

4. Result: When say_hello() is called, the output includes the additional behavior from the
decorator.

Output:
Before function call.
Hello, World!
After function call.

b) Write a Function average() Which Will Return the Average of the Numbers
Inputted by the User
Here’s a Python program that defines a function average() to calculate the average of numbers
inputted by the user:

# Function to calculate the average of numbers


def average():
# Accept input from the user as space-separated values
numbers = input("Enter numbers separated by spaces: ").split()

# Convert the string inputs to integers


numbers = [int(num) for num in numbers]

# Calculate the average


avg = sum(numbers) / len(numbers)

return avg

# Call the function and display the result


result = average()
print(f"The average of the entered numbers is: {result}")

Explanation:
1. Input: The user is asked to enter a series of numbers separated by spaces.

2. Processing: The split() method splits the input into a list of strings, which are then converted
to integers using a list comprehension.

3. Calculation: The sum() function calculates the total sum of the numbers, and the len()
function gives the count of numbers. The average is computed by dividing the sum by the count.

4. Output: The function returns and prints the average of the numbers.

Example Run:
Enter numbers separated by spaces: 10 20 30 40 50
The average of the entered numbers is: 30.0

Conclusion:
· Reverse and Sum of Digits: The program reverses the number and calculates the sum of its
digits.

· Modules: Modules are useful for organizing and reusing code across different programs.

Python Program to Validate PAN Card Number Using Regular Expression


A PAN (Permanent Account Number) card number is typically in the format of "AAAAA1234A", where:

· The first five characters are uppercase English letters.

· The next four characters are digits.

· The last character is an uppercase English letter.

Here’s the Python program that uses regular expressions to validate the PAN card number:

import re
# Function to validate PAN card number
def validate_pan(pan):
# Regular expression for validating PAN card number
pattern = r'^[A-Z]{5}[0-9]{4}[A-Z]{1}$'
# Check if the PAN matches the pattern
if re.match(pattern, pan):
return True
else:
return False
# Accept input from user
pan_number = input("Enter PAN card number: ")
# Validate the PAN number
if validate_pan(pan_number):
print("PAN card number is valid.")
else:
print("Invalid PAN card number.")

b) Constructors and Destructors in Python Classes

Constructors in Python:A constructor in Python is a special method __init__() that is automatically


called when an object of a class is created. The primary purpose of a constructor is to initialize the
object’s attributes.Destructors in Python:A destructor is a special method __del__() that is called
when an object is about to be destroyed. It is generally used for cleanup purposes, such as closing files
or releasing resources that the object might have acquired during its lifetime

class Car:
# Constructor
def __init__(self, make, model):
self.make = make
self.model = model
print(f"Car {self.make} {self.model} is created.")

# Destructor
def __del__(self):
print(f"Car {self.make} {self.model} is destroyed.")

# Create an object of Car


car1 = Car("Toyota", "Corolla")
del car1 # Explicitly call the destructor
OR: Python Program to Illustrate the Use of super() to Call a Method from a Parent
Class
The super() function is used to call methods from a parent class in a subclass. It is commonly used
when overriding methods in a subclass but still needs to call the parent class’s method.

Example:
class Animal:
# Method in parent class
def speak(self):
print("Animal is making a sound.")

class Dog(Animal):
# Overriding the speak method in subclass
def speak(self):
super().speak() # Call the parent class method
print("Dog is barking.")

# Create an object of Dog


dog = Dog()
dog.speak()

Example Output:
Animal is making a sound.
Dog is barking.

Python Program to Validate a Strong PasswordA strong password should meet the following
conditions:At least one uppercase letter.At least one lowercase letter.At least one digit.At least one
special character.Minimum 8 characters in length.
import re
# Function to validate strong password
def validate_password(password):
# Regular expression pattern to validate the password
pattern = r'^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?
&]{8,}$'
# Check if the password matches the pattern
if re.match(pattern, password):
return True
else:
return False
# Accept input from user
password = input("Enter a password: ")
# Validate the password
if validate_password(password):
print("The password is strong.")
else:
print("The password is weak. It must include at least one capital letter,
one numeric character, and one special character.")

a) DataFrame Operations in Pandas


Let's start with the DataFrame created using the provided data.

Let's compute the total price and maximum profit from the above data using Python code.

import pandas as pd

# Create the DataFrame


data = {
'Product-ID': ['P1', 'P2', 'P3', 'P4'],
'Product-Name': ['Samsung M32', 'OnePlus Nord 12', 'OPPO', 'iPhone 14'],
'Price': [14000, 16000, 10000, 60000],
'Profit': [2000, 2500, 1500, 5000]
}
df = pd.DataFrame(data)
# i) Compute total price of all products
total_price = df['Price'].sum()
# ii) Compute the maximum profit from given products
max_profit = df['Profit'].max()
print("Total Price of All Products:", total_price)
print("Maximum Profit from Given Products:", max_profit)

Output:
Total Price of All Products: 100000
Maximum Profit from Given Products: 5000

b) Create a 6x6 2D NumPy Array and Retrieve Bottom-Right Corner 2x2 Array
You can create a 6x6 NumPy array and retrieve the bottom-right 2x2 subarray using the following code:

import numpy as np

# Create a 6x6 2D NumPy array


arr = np.array([[1, 2, 3, 4, 5, 6],
[7, 8, 9, 10, 11, 12],
[13, 14, 15, 16, 17, 18],
[19, 20, 21, 22, 23, 24],
[25, 26, 27, 28, 29, 30],
[31, 32, 33, 34, 35, 36]])

# Retrieve bottom-right 2x2 array


bottom_right_2x2 = arr[-2:, -2:]

print("Bottom-right 2x2 Array:\n", bottom_right_2x2)


Bottom-right 2x2 Array:
[[29 30]
[35 36]]

OR: Create a Series from NumPy Array and Find Max and Mean of Unique Items
To create a Pandas Series from a NumPy array and then compute the maximum and mean of the unique
items, you can use the following code:

import pandas as pd
import numpy as np

# Create a NumPy array


arr = np.array([1, 2, 2, 3, 4, 4, 5])

# Create a Pandas Series from the NumPy array


series = pd.Series(arr)

# Get unique items from the series


unique_items = series.unique()

# Compute the maximum and mean of unique items


max_value = unique_items.max()
mean_value = unique_items.mean()

print("Unique items:", unique_items)


print("Max value of unique items:", max_value)
print("Mean value of unique items:", mean_value)

Explanation:
· pd.Series(arr) converts the NumPy array arr into a Pandas Series.

· series.unique() retrieves the unique elements from the Series.

· max() and mean() are used to calculate the maximum and mean of the unique items.

Output:
Unique items: [1 2 3 4 5]
Max value of unique items: 5
Mean value of unique items: 3.0

b) How to Create a Series from List, NumPy Array, and Dictionary

Creating a Series from a List:


import pandas as pd

# Create a Series from a list


list_data = [10, 20, 30, 40]
series_from_list = pd.Series(list_data)

print("Series from List:\n", series_from_list)

Creating a Series from a NumPy Array:


import numpy as np
import pandas as pd

# Create a Series from a NumPy array


np_array = np.array([1, 2, 3, 4])
series_from_np_array = pd.Series(np_array)

print("Series from NumPy Array:\n", series_from_np_array)

Creating a Series from a Dictionary:


# Create a Series from a dictionary
dict_data = {'a': 1, 'b': 2, 'c': 3}
series_from_dict = pd.Series(dict_data)

print("Series from Dictionary:\n", series_from_dict)

Creating a Series with Labeled Index:


# Create a Series with a labeled index
labeled_index_series = pd.Series([10, 20, 30], index=['X', 'Y', 'Z'])

print("Series with Labeled Index:\n", labeled_index_series)

· List to Series: A list is converted directly into a Pandas Series.

· NumPy Array to Series: A NumPy array can also be directly converted into a Pandas Series.

· Dictionary to Series: The keys of the dictionary become the index of the Series, and the values
become the corresponding elements.
a) Shallow Copy with Suitable Example
A shallow copy in Python refers to creating a new collection (such as a list, dictionary, or set) that
contains references to the original objects. However, if the original objects contain references to other
objects (like nested lists or dictionaries), only the references to those nested objects are copied, not the
nested objects themselves. This means that if you modify a nested object, it will reflect in both the
original and the copied collection.

Python provides several ways to make a shallow copy, such as using the copy() method or the copy
module’s copy() function.

Example of Shallow Copy


import copy

# Original List (contains another list)


original_list = [1, 2, [3, 4]]

# Shallow copy using copy() method


shallow_copy = original_list.copy()

# Shallow copy using copy() function from copy module


shallow_copy_using_module = copy.copy(original_list)

# Modifying the nested list in shallow copy


shallow_copy[2][0] = 100

# Printing both lists to see the effect


print("Original List:", original_list) # The nested list changes in original
as well
print("Shallow Copy:", shallow_copy) # The nested list changes in shallow
copy too

Output:
Original List: [1, 2, [100, 4]]
Shallow Copy: [1, 2, [100, 4]]

In this case, the change in the nested list in shallow_copy is also reflected in original_list,
because the reference to the nested list is shared between both. The shallow copy only copied the outer
list, not the nested objects inside it.

b) Python Program Using MongoDB to Create a "learners" Collection


To interact with MongoDB from Python, you'll need the pymongo library. The following program creates
a "learners" collection, inserts documents into it, and then displays all the learners' names.

import pymongo

# Connect to MongoDB
client = pymongo.MongoClient("mongodb://localhost:27017/")

# Create/select database
db = client["learning_database"]

# Create/select collection
learners_collection = db["learners"]

# Insert sample data (learners' information)


learners_data = [
{"id": 1, "name": "John Doe", "course": "Python Programming", "mobile_no":
"1234567890"},
{"id": 2, "name": "Jane Smith", "course": "Data Science", "mobile_no":
"9876543210"},
{"id": 3, "name": "Mark Johnson", "course": "Web Development",
"mobile_no": "1122334455"},
{"id": 4, "name": "Emily Davis", "course": "Machine Learning",
"mobile_no": "5566778899"}
]

# Insert documents into "learners" collection


learners_collection.insert_many(learners_data)

# Display all learners' names


print("List of Learners' Names:")
for learner in learners_collection.find({}, {"name": 1, "_id": 0}):
print(learner["name"])

1. {"name": 1, "_id": 0} to retrieve only the name field from each document, excluding the
default _id field.

Output:
List of Learners' Names:
John Doe
Jane Smith
Mark Johnson
Emily Davis

· Shallow Copy: A shallow copy creates a new object but does not recursively copy nested
objects. Modifying nested objects affects both the original and the copied collection.

· MongoDB Program: We created a MongoDB collection to store learners' data, inserted


documents, and retrieved the names of all learners.

) Leap Year Checker with Input Validation


def is_leap(year):
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
try:
year = int(input("Enter a year: "))
if year > 0:

print(f"{year} is {'a leap year' if is_leap(year) else 'not a leap year'}")


else:
print("Year must be positive!")
except ValueError:
print("Invalid input! Enter a valid year.")

b) Factorial Using Recursion


def factorial(n):
return 1 if n <= 1 else n * factorial(n - 1)
num = int(input("Enter a number: "))
print(f"Factorial of {num} = {factorial(num)}")

c) Inheritance & Multiple Inheritance


Inheritance
Inheritance allows a child class to reuse methods and attributes from a parent class.
Multiple Inheritance
A class can inherit from multiple parent classes.
Example:
class Father:
def skills(self):
print("Gardening, Programming")
class Mother:
def skills(self):
print("Cooking, Painting")
class Child(Father, Mother): # Inherits from both Father and Mother
def skills(self):
Father.skills(self) # Calls Father's skills
Mother.skills(self) # Calls Mother's skills
print("Sports")
child = Child()
child.skills()
Output:
Gardening, Programming
Cooking, Painting
Sports
Key Points:
Leap Year Rule:

a) Tuple vs List in Python


Tuple
Immutable (cannot be changed after creation)
Defined using parentheses ()
Faster than lists for fixed data
List
Mutable (can be modified after creation)
Defined using square brackets []
More methods available (append(), remove(), etc.)
Example:
# Tuple (Immutable)
my_tuple = (1, 2, 3)
# my_tuple[0] = 10 # Error: Tuples are immutable
# List (Mutable)
my_list = [1, 2, 3]
my_list[0] = 10 # Works fine

b) Sum of Natural Numbers Using Recursion


def sum_natural(n):
return n if n == 1 else n + sum_natural(n - 1)
num = int(input("Enter a positive integer: "))
print(f"Sum of first {num} natural numbers = {sum_natural(num)}")
Output (if input is 5):
Sum of first 5 natural numbers = 15
c) Inheritance & Multilevel Inheritance
Inheritance
A mechanism where a child class inherits properties and methods from a parent class.
Multilevel Inheritance
When a child class inherits from another child class
(forming a "grandparent → parent → child" chain).
Example:
class Grandparent
def grand_method(self):
print("Grandparent's method")
class Parent(Grandparent):
def parent_method(self):
print("Parent's method")
class Child(Parent): # Inherits from Parent (which inherits from Grandparent)
def child_method(self):
print("Child's method")
obj = Child()
obj.grand_method() # From Grandparent
obj.parent_method() # From Parent
obj.child_method() # Own method

User Defined Exception for Voting Eligibility


class VotingEligibilityError(Exception):
pass
def check_voting_eligibility():
try:
age = int(input("Enter your age: "))
if age < 18:
raise VotingEligibilityError("Not eligible for voting")
else:
print("You are eligible for voting!")
except ValueError:
print("Please enter a valid age (numeric value)")
except VotingEligibilityError as e:
print(e)
# Test the function
check_voting_eligibility()

Program to Validate URL Using Regular Expression


import re def validate_url(url):
pattern = r'^(https?:\/\/)?(www\.)?([a-zA-Z0-9-]+\.){1,}[a-zA-Z]{2,}(\/[a-zA-Z0-9-
._~:\/?%#\[\]@!$&\'()*+,;=]*)?$'
if re.match(pattern, url):
return True
else:
return False
# Test the function
url = input("Enter URL: ")
if validate_url(url):
print("Valid URL")
else:
print("Invalid URL")

Create Series from NumPy Array and Find Frequency Count


import numpy as np
import pandas as pd
# Create NumPy array
data = np.array([1, 2, 2, 3, 3, 3, 4, 4, 4, 4])
# Convert to Pandas Series
series = pd.Series(data)
# Get frequency count of unique items
frequency = series.value_counts()
print("Frequency count:\n", frequency)

onymous Function (Lambda) with Example


Anonymous Function (Lambda):
A lambda function is a small, anonymous function defined with the lambda keyword
Syntax: lambda arguments: expression
Can take any number of arguments but only one expression
Doesn't require a return statement (expression is automatically returned)
Typically used for short, simple operations
Key Characteristics
No name (anonymous)
Limited to single expression
Can be used wherever function objects are required
Example 1: Basic Lambda Function
# Regular function
def square(x):
return x * x
# Equivalent lambda
square_lambda = lambda x: x * x
print(square(5)) # Output: 25
print(square_lambda(5)) # Output: 25
Example 2: Using with Built-in Functions
numbers = [1, 2, 3, 4, 5]
# Using lambda with map()
squares = list(map(lambda x: x**2, numbers))
print(squares) # Output: [1, 4, 9, 16, 25]
# Using lambda with filter()
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens) # Output: [2, 4]
Example 3: Sorting with Lambda
people = [('Alice', 32), ('Bob', 25), ('Charlie', 40)]
# Sort by age
people.sort(key=lambda x: x[1])
print(people) # Output: [('Bob', 25), ('Alice', 32), ('Charlie', 40)]

Multithread Program for Square and Cube Calculations


import threading
import time
def square_numbers(numbers):
print("Calculating squares:")
for n in numbers:
print(f"Square of {n} = {n*n}")
time.sleep(0.1)
def cube_numbers(numbers):
print("Calculating cubes:")
for n in numbers:
print(f"Cube of {n} = {n*n*n}")
time.sleep(0.1)
numbers = [1, 2, 3, 4, 5]
start_time = time.time() # Create threads
t1 = threading.Thread(target=square_numbers, args=(numbers,))
t2 = threading.Thread(target=cube_numbers, args=(numbers,))
# Start threads
t1.start()
t2.start()

# Wait for both threads to complete


t1.join()
t2.join()

end_time = time.time()

print(f"\nTotal execution time: {end_time - start_time:.2f} seconds")

## Q1)
### a) Write python program to reverse a number and find the sum of digits in reverse number
prompt the user for input.

```python
def reverse_and_sum():
num = input("Enter a number: ")
reversed_num = num[::-1]
sum_digits = sum(int(digit) for digit in reversed_num)

print(f"Reversed number: {reversed_num}")


print(f"Sum of digits in reversed number: {sum_digits}")

reverse_and_sum()
```

### b) How would you determine when to use a module in your code. Justify with suitable example.
You should use a module when:
1. You want to organize related code into reusable components
2. You need functionality that's already implemented in existing modules
3. You want to avoid code duplication
4. You need to separate concerns in your application
Example justification:
When working with dates in Python, instead of implementing all date calculations yourself, you should
use the `datetime` module:
import datetime
# Without module (bad practice)
# You'd have to implement all date logic yourself
# With module (good practice)
today = datetime.date.today()
next_week = today + datetime.timedelta(days=7)
print(f"Today: {today}, Next week: {next_week}")
```
## Q2)
### a) Explain Decorator concept with suitable example.

Decorators are a design pattern in Python that allows you to modify the functionality of a function
without changing its source code. They are functions that take another function as an argument and
extend its behavior.

Example:
```python
def uppercase_decorator(func):
def wrapper():
result = func()
return result.upper()
return wrapper

@uppercase_decorator
def say_hello():
return "hello world"

print(say_hello()) # Output: HELLO WORLD


```

### b) Write a function average() which will return the average of the numbers inputted by the user.

```python
def average():
numbers = []
while True:
user_input = input("Enter a number (or 'done' to finish): ")
if user_input.lower() == 'done':
break
try:
numbers.append(float(user_input))
except ValueError:
print("Please enter a valid number or 'done' to finish.")

if not numbers:
return 0
return sum(numbers) / len(numbers)

print(f"The average is: {average()}")


```
## Q3)
### a) What is multithreading? Implement multithreading with suitable example.
Multithreading is a technique where multiple threads are spawned within a single process to execute
tasks concurrently, improving performance for I/O-bound operations.
Example:
```python
import threading
import time
def print_numbers():
for i in range(1, 6):
time.sleep(1)
print(f"Number: {i}")
def print_letters():
for letter in 'abcde':
time.sleep(1.5)
print(f"Letter: {letter}")
t1 = threading.Thread(target=print_numbers)
t2 = threading.Thread(target=print_letters)
t1.start()
t2.start()
t1.join()
t2.join()
print("Done!")
```

### b) Write a Python program that defines a custom exception that takes user input. If the user
enters a negative number, raise the custom exception with an appropriate error message. Implement
exception handling to catch and handle this custom exception.
```python
class NegativeNumberError(Exception):
pass
def check_positive():
try:
num = float(input("Enter a positive number: "))
if num < 0:
raise NegativeNumberError("Negative numbers are not allowed!")
print(f"You entered: {num}")
except NegativeNumberError as e:
print(f"Error: {e}")
except ValueError:
print("Please enter a valid number.")
check_positive()
```

### OR
### a) Write a Python program that creates two threads. One thread should print even numbers from
2 to 10 & the other should print odd numbers from 1 to 9. Ensure that the threads run concurrently &
use Synchronization.
```python
import threading
lock = threading.Lock()
def print_even():
with lock:
for num in range(2, 11, 2):
print(f"Even: {num}")
def print_odd():
with lock:
for num in range(1, 10, 2):
print(f"Odd: {num}")
t1 = threading.Thread(target=print_even)
t2 = threading.Thread(target=print_odd)
t1.start()
t2.start()
t1.join()
t2.join()
```

### b) What is file in Python? Write a python program that reads a text file named "sample.txt" and
prints its contents to the console.
In Python, a file is an object that allows you to work with files on your filesystem. It can be used to read,
write, or append data.
```python
def read_file():
try:
with open("sample.txt", "r") as file:
contents = file.read()
print(contents)
except FileNotFoundError:
print("File 'sample.txt' not found.")
except IOError:
print("Error reading the file.")

read_file()
```
### a) Write a Python Program using MongoDB database to create a collection "Badminton
Tournaments" with fields (player-id, p-name, age, phone no) and display all registration.
```python
from pymongo import MongoClient
def manage_badminton_tournament():
client = MongoClient('mongodb://localhost:27017/')
db = client['tournament_db']
collection = db['Badminton_Tournaments']
# Insert sample data
collection.insert_many([
{"player-id": "P001", "p-name": "Player One", "age": 25, "phone no": "1234567890"},
{"player-id": "P002", "p-name": "Player Two", "age": 28, "phone no": "9876543210"}
])
# Display all registrations
print("All Registrations:")
for player in collection.find():
print(player)
manage_badminton_tournament()
```

### b) What do you understand by immutability. Discuss with suitable example.


Immutability means that an object's state cannot be modified after it is created. In Python, strings,
tuples, and numbers are immutable.
Example:
```python
# String immutability
s = "hello"
# s[0] = 'H' # This would raise an error
s = "H" + s[1:] # We create a new string instead
# Tuple immutability
t = (1, 2, 3)
# t[0] = 4 # This would raise an error
t = (4,) + t[1:] # Create a new tuple
```
### OR

### a) Explain shallow copy with suitable example.


A shallow copy creates a new object but doesn't create copies of nested objects within the original
object. It only copies the reference to nested objects.
import copy
original = [[1, 2, 3], [4, 5, 6]]
shallow_copied = copy.copy(original)
# Changing the outer list doesn't affect the original
shallow_copied.append([7, 8, 9])
print(original) # [[1, 2, 3], [4, 5, 6]]
# But changing a nested list affects both
shallow_copied[0][0] = 'X'
print(original) # [['X', 2, 3], [4, 5, 6]
### b) Write a Python program using MongoDB to create learners collection having field id, name,
course & mobile no and display all learners name.
from pymongo import MongoClient
def manage_learners():
client = MongoClient('mongodb://localhost:27017/')
db = client['education_db']
collection = db['learners']
# Insert sample data
collection.insert_many([
{"id": "L001", "name": "Alice", "course": "Python", "mobile no": "1111111111"},
{"id": "L002", "name": "Bob", "course": "Data Science", "mobile no": "2222222222"}
])# Display all learner names
print("Learner Names:")
for learner in collection.find({}, {"name": 1, "_id": 0}):
print(learner["name"])
manage_learners()
```

### a) Given data frame as below, compute:


i) Total price of all products
ii) Maximum profit from given products
```python
import pandas as pd
data = {
'Product-id': ['P1', 'P2', 'P3', 'P4'],
'Proc-name': ['Samsung M32', '1 + Nord 12', 'QPPO', 'iPhone 14'],
'Price': [14000, 16000, 10000, 60000],
'Profit': [2000, 2500, 1500, 5000]
}
df = pd.DataFrame(data)
# i) Total price
total_price = df['Price'].sum()
print(f"Total price of all products: {total_price}")
# ii) Maximum profit
max_profit = df['Profit'].max()
print(f"Maximum profit: {max_profit}")
```
### b) Create a 6×6 2D numpy array and retrieve bottom right corner 2×2 array.
```python
import numpy as np
# Create 6x6 array
arr = np.arange(1, 37).reshape(6, 6)
print("Original array:")
print(arr)
# Get bottom right 2x2
bottom_right = arr[-2:, -2:]
print("\nBottom right 2x2 array:")
print(bottom_right)
```
### a) Create a series from numpy array and find max and mean of unique items of series.
import numpy as np
import pandas as pd
# Create numpy array
np_array = np.array([3, 5, 2, 5, 7, 3, 8, 2])
# Create series
series = pd.Series(np_array)
# Find max and mean of unique items
unique_items = series.unique()
max_unique = unique_items.max()
mean_unique = unique_items.mean()
print(f"Unique items: {unique_items}")
print(f"Max of unique items: {max_unique}")
print(f"Mean of unique items: {mean_unique}")
```

### b) How to create a series from a list, numpy array and dictionary? Also demonstrate how to create
a series with labeled index.
```python
import pandas as pd
import numpy as np
# From list
list_data = [10, 20, 30, 40]
series_from_list = pd.Series(list_data)
print("From list:")
print(series_from_list)
# From numpy array
np_array = np.array([1.1, 2.2, 3.3, 4.4])
series_from_array = pd.Series(np_array)
print("\nFrom numpy array:")
print(series_from_array)
# From dictionary
dict_data = {'a': 100, 'b': 200, 'c': 300}
series_from_dict = pd.Series(dict_data)
print("\nFrom dictionary:")
print(series_from_dict)
# With labeled index
labeled_series = pd.Series([5, 10, 15, 20], index=['A', 'B', 'C', 'D'])
print("\nWith labeled index:")
print(labeled_series)
```

You might also like