0% found this document useful (0 votes)
7 views74 pages

Python PPR Soln

The document discusses various aspects of Python programming, including the role and merging of DataFrames, features of Python, differences between lists, tuples, and dictionaries, methods for reading data from files, and exception handling in Python. It highlights DataFrames as a key structure in Pandas for data organization and manipulation, and outlines Python's readability, dynamic typing, and extensive libraries. Additionally, it explains how to handle exceptions gracefully using try, except, else, and finally blocks.

Uploaded by

rishavyadav006
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)
7 views74 pages

Python PPR Soln

The document discusses various aspects of Python programming, including the role and merging of DataFrames, features of Python, differences between lists, tuples, and dictionaries, methods for reading data from files, and exception handling in Python. It highlights DataFrames as a key structure in Pandas for data organization and manipulation, and outlines Python's readability, dynamic typing, and extensive libraries. Additionally, it explains how to handle exceptions gracefully using try, except, else, and finally blocks.

Uploaded by

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

‭Q1: Answer following in brief.

[5x5=25]‬
‭(a) What is the role of data frame? How can data frames be merged?‬

‭ ‬‭DataFrame‬‭is a 2-dimensional, size-mutable, and potentially heterogeneous tabular‬


A
‭data structure with labeled axes (rows and columns).‬‭1‬ ‭It's a fundamental data‬
‭structure in‬‭2‬ ‭the Pandas library, widely used for data analysis in Python.‬

‭Roles of a DataFrame:‬
‭●‬ ‭Data Storage and Organization:‬‭Efficiently stores and organizes data in a‬
t‭ able-like format.‬
‭●‬ ‭Data Cleaning:‬‭Provides methods to handle missing data (e.g., fill or drop NaNs),‬
‭duplicates, and incorrect data types.‬
‭●‬ ‭Data Exploration and Analysis:‬‭Allows for quick insights into data through‬
‭operations like filtering, grouping, sorting, and aggregation.‬
‭●‬ ‭Data Manipulation:‬‭Facilitates reshaping data, adding or deleting columns, and‬
‭transforming data.‬
‭●‬ ‭Input/Output:‬‭Can read data from various file formats (CSV, Excel, SQL‬
‭databases, etc.) and write data back to them.‬

‭ erging DataFrames:‬
M
‭DataFrames can be merged using functions like pandas.merge() or the DataFrame's merge()‬
‭method. This is similar to SQL joins. Common ways to merge include:‬
‭●‬ ‭pd.merge(df1, df2, on='common_column')‬‭: Merges df1 and df2 based on the‬
v‭ alues in common_column.‬
‭ ‬ ‭pd.merge(df1, df2, left_on='col_df1', right_on='col_df2')‬‭: Merges based on‬

‭specified columns from each DataFrame.‬
‭●‬ ‭pd.merge(df1, df2, on='key_column', how='inner')‬‭: Includes only rows with‬
‭matching keys in both DataFrames (intersection).‬
‭●‬ ‭pd.merge(df1, df2, on='key_column', how='outer')‬‭: Includes all rows from both‬
‭ ataFrames, filling with NaN where data is missing (union).‬
D
‭ ‬ ‭pd.merge(df1, df2, on='key_column', how='left')‬‭: Includes all rows from df1 and‬

‭matching rows from df2.‬
‭●‬ ‭pd.merge(df1, df2, on='key_column', how='right')‬‭: Includes all rows from df2‬
‭and matching rows from df1.‬

‭ ther methods like concat() (for stacking DataFrames vertically or horizontally) and‬
O
‭join() (for index-based merging) are also available.‬

‭(b) What are the features of Python Programming language?‬

‭ ython is a popular, versatile, and powerful programming language. Its key features‬
P
‭include:‬
‭●‬ ‭Easy to Learn and Read:‬‭Python's syntax is designed to be clear and readable,‬
r‭ esembling plain English. This makes it relatively easy for beginners to pick up.‬
‭●‬ ‭Interpreted Language:‬‭Python code is executed line by line by an interpreter,‬
‭which simplifies debugging and makes development faster as there's no separate‬
‭compilation step.‬
‭●‬ ‭High-Level Language:‬‭It abstracts many complex details of the computer's‬
‭hardware, allowing developers to focus on problem-solving rather than low-level‬
‭implementation.‬
‭●‬ ‭Dynamically Typed:‬‭Variable types are checked at runtime, meaning you don't‬
‭need to declare the variable type explicitly.‬
‭●‬ ‭Object-Oriented Programming (OOP):‬‭Python supports OOP principles like‬
‭encapsulation, inheritance, and polymorphism, allowing for the creation of‬
‭reusable and modular code.‬
‭●‬ ‭Extensive Standard Library:‬‭Python comes with a large standard library that‬
‭provides modules and functions for a wide range of tasks,‬‭3‬ ‭from string‬
‭manipulation to networking.‬
‭●‬ ‭Cross-Platform Compatibility (Portable):‬‭Python code can run değişiklik‬
‭yapmadan on various operating systems like Windows, macOS, and Linux.‬
‭●‬ ‭Extensible:‬‭Python can be extended with modules written in other languages like‬
‭C or C++.‬
‭●‬ ‭Embeddable:‬‭Python code can be embedded into applications written in other‬
‭languages.‬
‭●‬ ‭Large Community and Ecosystem:‬‭Python has a massive and active community,‬
‭which means abundant resources, libraries (like NumPy, Pandas, Django, Flask,‬
‭ ensorFlow), and frameworks are available.‬
T
‭ ‬ ‭Free and Open Source:‬‭Python is freely available, and its source code can be‬

‭modified and distributed.‬
‭●‬ ‭Support for Multiple Programming Paradigms:‬‭Besides OOP, Python supports‬
‭procedural, functional, and imperative programming styles.‬

‭(c) Differentiate between list, tuple and dictionary with example.‬

‭Feature‬ ‭List‬ ‭Tuple‬ ‭Dictionary‬

‭Mutability‬ ‭ utable (can be‬


M I‭mmutable (cannot‬ ‭ utable (can be‬
M
‭changed after‬ ‭be changed after‬ ‭changed after‬
‭creation)‬ ‭creation)‬ ‭creation)‬

‭Syntax‬ ‭[item1, item2, ...]‬ ‭(item1, item2, ...)‬ {‭ key1: value1, key2:‬
‭value2, ...}‬

‭Ordering‬ ‭ rdered (maintains‬


O ‭ rdered (maintains‬
O ‭ nordered (prior to‬
U
‭the order of‬ ‭the order of‬ ‭Python 3.7); Ordered‬
‭elements)‬ ‭elements)‬ ‭(Python 3.7+)‬

‭Indexing‬ I‭nteger-based (0, 1,‬ I‭nteger-based (0, 1,‬ ‭ ey-based (uses‬


K
‭2, ...)‬ ‭2, ...)‬ ‭unique keys)‬

‭Use Case‬ ‭ ollections of items‬


C ‭ ollections of items‬
C ‭ toring data as‬
S
‭that may need to‬ ‭that should not‬ ‭key-value pairs for‬
‭change; sequences.‬ ‭change (e.g.,‬ ‭quick lookups.‬
‭coordinates, fixed‬
‭sequences).‬

‭Duplicates‬ ‭ llows duplicate‬


A ‭ llows duplicate‬
A ‭ eys must be unique;‬
K
‭elements‬ ‭elements‬ ‭values can be‬
‭duplicates.‬

‭Performance‬ ‭ enerally slower than‬


G ‭ aster than lists for‬
F ‭ ast for lookups,‬
F
‭tuples for iteration‬ ‭iteration because‬ ‭insertions, and‬
‭due to mutability.‬ ‭they are immutable.‬ ‭deletions if you know‬
‭the key.‬

‭Examples:‬
‭●‬ ‭List:‬
‭Python‬
‭ y_list = [‬‭1‬‭,‬‭"hello"‬‭,‬‭3.14‬‭,‬‭"hello"‬‭]‬
m
‭print(my_list)‬ ‭# Output: [1, 'hello', 3.14, 'hello']‬
‭my_list.append(‬‭True‬‭)‬
‭my_list[‬‭1‬‭] =‬‭"world"‬
‭print(my_list)‬ ‭# Output: [1, 'world', 3.14, 'hello', True]‬

‭●‬ ‭Tuple:‬
‭Python‬
‭ y_tuple = (‬‭1‬‭,‬‭"hello"‬‭,‬‭3.14‬‭,‬‭"hello"‬‭)‬
m
‭print(my_tuple)‬ ‭# Output: (1, 'hello', 3.14, 'hello')‬
‭ my_tuple.append(True) # This would cause an AttributeError‬
#
‭# my_tuple[1] = "world" # This would cause a TypeError‬
‭print(my_tuple[‬‭0‬‭])‬‭# Output: 1‬

‭●‬ ‭Dictionary:‬
‭Python‬
‭ y_dict = {‬‭"name"‬‭:‬‭"Alice"‬‭,‬‭"age"‬‭:‬‭30‬‭,‬‭"city"‬‭:‬‭"New York"‬‭}‬
m
‭print(my_dict)‬ ‭# Output (order may vary before Python 3.7): {'name': 'Alice', 'age': 30, 'city':‬
‭'New York'}‬
‭ y_dict[‬‭"age"‬‭] =‬‭31‬
m
‭my_dict[‬‭"occupation"‬‭] =‬‭"Engineer"‬
‭print(my_dict)‬ ‭# Output: {'name': 'Alice', 'age': 31, 'city': 'New York', 'occupation': 'Engineer'}‬
‭print(my_dict[‬‭"name"‬‭])‬‭# Output: Alice‬

‭(d) Write a short note on different methods to read data from a file.‬

‭ eading data from a file in Python typically involves three steps: opening the file,‬
R
‭reading its contents, and closing the file. The open() function is used to open a file‬
‭and returns a file object.‬

‭Common methods to read data from a file object (f):‬


‭1.‬ ‭f.read(size):‬
‭○‬ ‭Reads up to size bytes from the file.‬
‭○‬ ‭If size is omitted or negative, it reads the entire file content as a single string.‬
‭○‬ ‭Useful for reading the whole file at once or in chunks.‬
‭ ython‬
P
‭with‬‭open‬‭(‬‭"example.txt"‬‭,‬‭"r"‬‭)‬‭as‬ ‭f:‬
‭content = f.read()‬‭# Reads the entire file‬
‭# print(content)‬
‭f.seek(‬‭0‬‭)‬‭# Go back to the beginning of the file‬
‭chunk = f.read(‬‭10‬‭)‬‭# Reads the first 10 bytes/characters‬
‭# print(chunk)‬

‭2.‬ ‭f.readline(size):‬
‭○‬ ‭Reads a single line from the file, including the newline character (\n) at the‬
‭ nd.‬
e
‭ ‬ ‭If size is specified, it reads at most size bytes from that line.‬

‭○‬ ‭Useful for reading a file line by line, especially for large files where reading‬
‭everything into memory is not feasible.‬
‭ ython‬
P
‭with‬‭open‬‭(‬‭"example.txt"‬‭,‬‭"r"‬‭)‬‭as‬ ‭f:‬
‭line1 = f.readline()‬
‭# print(line1, end='') # end='' prevents double newline‬
‭line2 = f.readline()‬
‭# print(line2, end='')‬

‭3.‬ ‭f.readlines(hint):‬
‭○‬ ‭Reads all lines from the file and returns them as a list of strings. Each string in‬
t‭ he list represents a line and includes the newline character.‬
‭ ‬ ‭The optional hint argument, if provided, suggests the number of bytes to‬

‭read. The method will read approximately that many bytes and then complete‬
‭reading the current line.‬
‭○‬ ‭Can consume a lot of memory for very large files.‬
‭ ython‬
P
‭with‬‭open‬‭(‬‭"example.txt"‬‭,‬‭"r"‬‭)‬‭as‬ ‭f:‬
‭all_lines_list = f.readlines()‬
‭ for line in all_lines_list:‬
#
‭# print(line, end='')‬

‭4.‬ ‭Iterating over the file object:‬


‭○‬ ‭This is the most idiomatic and memory-efficient way to read a file line by line.‬
‭ ython‬
P
‭with‬‭open‬‭(‬‭"example.txt"‬‭,‬‭"r"‬‭)‬‭as‬ ‭f:‬
‭for‬ ‭line‬‭in‬ ‭f:‬‭# Processes one line at a time‬
‭# print(line, end='')‬
‭pass‬

‭The with open(...) as f: statement (context manager) is highly recommended because‬


‭it ensures the file is automatically closed even if errors occur.‬

‭(e) How is exception handling implemented in python?‬

‭ xception handling in Python is implemented using try, except, else, and finally blocks.‬
E
‭This mechanism allows you to gracefully manage errors and unexpected situations‬
‭that might occur during program execution, preventing the program from crashing.‬
‭●‬ ‭try block:‬‭The code that might potentially raise an exception is placed inside the‬
t‭ ry block.‬
‭ ‬ ‭except block:‬‭If an exception occurs within the try block, the Python interpreter‬

‭looks for a matching except block to handle it.‬
‭○‬ ‭You can specify the type of exception to catch (e.g., except ValueError:).‬
‭○‬ ‭A generic except: block will catch any exception (though this is generally‬
‭discouraged as it can hide bugs).‬
‭○‬ ‭You can have multiple except blocks to handle different types of exceptions.‬
‭○‬ ‭You can use except (ExceptionType1, ExceptionType2) as e: to handle multiple‬
‭specific exceptions and get access to the exception object e.‬
‭●‬ ‭else block (optional):‬‭The code inside the else block is executed only if the try‬
‭block completes without raising any exceptions.‬
‭●‬ ‭finally block (optional):‬‭The code inside the finally block is always executed,‬
‭regardless of whether an exception occurred or not. This is typically used for‬
‭cleanup operations, like closing files or releasing resources.‬

‭Example:‬

‭Python‬

‭def‬‭divide_numbers‬‭(a, b):‬
‭try‬‭:‬
‭result = a / b‬
‭except‬‭ZeroDivisionError:‬
‭print(‬‭"Error: Cannot divide by zero!"‬‭)‬
‭return‬‭None‬
‭except‬‭TypeError‬‭as‬ ‭e:‬
‭print(‬‭f"Error: Invalid input types.‬‭{e}‬‭"‬‭)‬
‭return‬‭None‬
‭except‬ ‭Exception‬‭as‬ ‭e:‬‭# Catch any other unexpected errors‬
‭print(‬‭f"An unexpected error occurred:‬‭{e}‬‭"‬‭)‬
‭return‬‭None‬
‭else‬‭:‬
‭print(‬‭"Division successful."‬‭)‬
‭return‬ ‭result‬
‭finally‬‭:‬
‭print(‬‭"Executing finally clause."‬‭)‬

‭ rint(divide_numbers(‬‭10‬‭,‬‭2‭)‬ )‬
p
‭print(‬‭"-"‬ ‭*‬‭20‬‭)‬
‭print(divide_numbers(‬‭10‬‭,‬‭0‭)‬ )‬
‭print(‬‭"-"‬ ‭*‬‭20‬‭)‬
‭print(divide_numbers(‬‭10‬‭,‬‭"a"‬‭))‬
‭print(‬‭"-"‬ ‭*‬‭20‬‭)‬
‭print(divide_numbers(‬‭10‬‭, []))‬‭# Example to trigger‬‭the generic Exception‬

‭Output:‬

‭ ivision successful.‬
D
‭Executing finally clause.‬
‭5.0‬
‭--------------------‬
‭Error: Cannot divide by zero!‬
‭Executing finally clause.‬
‭None‬
‭--------------------‬
‭Error: Invalid input types. unsupported operand type(s) for /: 'int' and 'str'‬
‭Executing finally clause.‬
‭None‬
‭--------------------‬
‭An unexpected error occurred: unsupported operand type(s) for /: 'int' and 'list'‬
‭Executing finally clause.‬
‭None‬

‭Python also allows raising exceptions deliberately using the raise keyword (e.g., raise‬
‭ alueError("Invalid value")). You can also define custom exception classes by‬
V
‭inheriting from the base Exception class.‬

‭UNIT-I‬
‭ 2 (a) What are loop interruption statements? What is the difference between‬
Q
‭break, continue and pass? Write a program that prints all integers lying between‬
‭1 and 50 that aren't divisible by either 2 or 3.‬‭(6)‬

‭ oop Interruption Statements:‬


L
‭Loop interruption statements are keywords in Python that alter the normal flow of execution‬
‭within a loop (like for or while loops). They allow you to skip iterations, exit the loop‬
‭prematurely, or do nothing.‬
‭Differences between break, continue, and pass:‬
‭●‬ ‭break Statement:‬
‭○‬ ‭Purpose:‬‭Terminates the innermost enclosing loop (e.g.,‬‭for or while loop)‬
i‭mmediately.‬
‭ ‬ ‭Behavior:‬‭When break is encountered, the program execution‬‭jumps to the‬

‭statement immediately following the loop.‬
‭○‬ ‭Use Case:‬‭Used when a certain condition is met and‬‭you need to exit the loop‬
‭entirely, even if the loop's condition is still true or there are more items to‬
‭iterate over.‬
‭○‬ ‭Example:‬
‭Python‬
‭for‬ ‭i‬‭in‬‭range‬‭(‬‭1‭,‬ ‬‭10‬‭):‬
‭if‬ ‭i ==‬‭5‬‭:‬
‭break‬ ‭# Exits the loop when i is 5‬
‭print(i)‬
‭# Output: 1 2 3 4‬

‭●‬ ‭continue Statement:‬


‭○‬ ‭Purpose:‬‭Skips the rest of the current iteration of‬‭the loop and proceeds to‬
t‭ he next iteration.‬
‭ ‬ ‭Behavior:‬‭When continue is encountered, any remaining‬‭code within the‬

‭current iteration of the loop block is skipped, and the loop's control‬
‭expression (for while) or next item (for for) is evaluated.‬
‭○‬ ‭Use Case:‬‭Used when you want to skip processing for‬‭a particular‬
‭item/condition but want the loop to continue with subsequent iterations.‬
‭○‬ ‭Example:‬
‭Python‬
‭for‬ ‭i‬‭in‬‭range‬‭(‬‭1‭,‬ ‬‭6‭)‬ :‬
‭if‬ ‭i ==‬‭3‭:‬ ‬
‭continue‬ ‭# Skips printing 3‬
‭print(i)‬
‭# Output: 1 2 4 5‬

‭●‬ ‭pass Statement:‬


‭○‬ ‭Purpose:‬‭Acts as a null operation; nothing happens‬‭when it is executed.‬
‭○‬ ‭Behavior:‬‭It's a placeholder.‬
‭○‬ ‭Use Case:‬‭Used when a statement is syntactically required‬‭but you don't want‬
‭ ny command or code to execute. This is common in empty function‬
a
‭definitions, class definitions, or empty if or except blocks during development.‬
‭ ‬ ‭Example:‬

‭Python‬
‭for‬ ‭i‬‭in‬‭range‬‭(‬‭1‭,‬ ‬‭4‬‭):‬
‭if‬ ‭i ==‬‭2‬‭:‬
‭pass‬ ‭# Does nothing, just a placeholder‬
‭else‬‭:‬
‭print(i)‬
‭# Output: 1 3‬

‭def‬‭my_function‬‭():‬
‭pass‬‭# A function that does nothing yet‬

‭class‬‭MyEmptyClass:‬
‭pass‬‭# An empty class definition‬

‭ rogram to print integers between 1 and 50 not divisible by 2 or 3:‬


P
‭This means the numbers should not be divisible by 2 AND should not be divisible by 3.‬

‭Python‬

‭print(‬‭"Integers between 1 and 50 not divisible by‬‭2 or 3:"‬‭)‬


‭for‬ ‭num‬‭in‬‭range‬‭(‬‭1‬‭,‬‭51‬‭):‬‭# Iterate from 1 to 50 (inclusive)‬
‭# Check if the number is divisible by 2 OR by‬‭3‬
‭if‬ ‭num %‬‭2‬ ‭==‬‭0‬‭or‬ ‭num %‬‭3‬ ‭==‬‭0‭:‬ ‬
‭continue‬‭# Skip this number if it's divisible by 2 or 3‬
‭ rint(num, end=‬‭" "‬‭)‬
p
‭print()‬‭# For a newline at the end‬

‭ xplanation:‬
E
‭The problem asks for numbers that "aren't divisible by either 2 or 3".‬
‭This can be interpreted in two ways:‬
‭1.‬ ‭not (divisible by 2 OR divisible by 3): This means the number is neither divisible by‬
‭ NOR divisible by 3. (e.g., 1, 5, 7, 11, ...)‬
2
‭ .‬ ‭(not divisible by 2) OR (not divisible by 3): This would include numbers like 3 (not‬
2
‭divisible by 2) and 4 (not divisible by 3). This interpretation usually isn't what's‬
‭intended by such phrasing.‬

‭ he code above implements the first interpretation, which is the standard understanding:‬
T
‭numbers that are prime to 6 (except for multiples of other primes like 5, 7, etc.).‬
‭A number num is not divisible by 2 if num % 2 != 0.‬
‭A number num is not divisible by 3 if num % 3 != 0.‬
‭So we want numbers where (num % 2 != 0) AND (num % 3 != 0).‬
‭Alternatively, using the continue statement as in the provided solution:‬
‭If a number IS divisible by 2 (num % 2 == 0) OR IS divisible by 3 (num % 3 == 0), then we‬
‭continue to the next iteration, effectively skipping it. Only numbers that do not satisfy this‬
‭condition (i.e., are not divisible by 2 AND not divisible by 3) will be printed.‬
‭Output of the program:‬

I‭ntegers between 1 and 50 not divisible by 2 or 3:‬


‭1 5 7 11 13 17 19 23 25 29 31 35 37 41 43 47 49‬

(‭ b) Explain the different numeric, assignment, augmented operators used in‬


‭python.‬‭(3)‬

‭ umeric (Arithmetic) Operators:‬


N
‭These operators perform mathematical calculations on numeric operands.‬
‭●‬ ‭+ (Addition):‬‭Adds two operands. x + y‬
‭●‬ ‭- (Subtraction):‬‭Subtracts the right operand from‬‭the left. x - y‬
‭●‬ ‭* (Multiplication):‬‭Multiplies two operands. x * y‬
‭●‬ ‭/ (Division):‬‭Divides the left operand by the right,‬‭always results in a float. x / y‬
‭●‬ ‭// (Floor Division):‬‭Divides and returns the integer part of the quotient (rounds‬
‭ own to the nearest integer). x // y‬
d
‭ ‬ ‭% (Modulus):‬‭Returns the remainder of the division.‬‭x % y‬

‭●‬ ‭** (Exponentiation):‬‭Raises the left operand to the‬‭power of the right operand. x‬
‭** y‬

‭ ssignment Operators:‬
A
‭These operators are used to assign values to variables.‬
‭●‬ ‭=‬ ‭(Simple Assignment):‬‭Assigns the value of the right‬‭operand to the left‬
‭ perand.‬‭4‬ ‭x = 5‬
o
‭ ‬ ‭Can be used for multiple assignments: x, y = 5, 10‬

‭ ugmented Assignment Operators (Compound Assignment Operators):‬


A
‭These operators combine an arithmetic (or bitwise) operation with an assignment. They‬
‭perform an operation and then assign the result back to the left operand.‬
‭●‬ ‭+= (Add and Assign):‬‭x += y is equivalent to x = x‬‭+ y‬
‭●‬ ‭-= (Subtract and Assign):‬‭x -= y is equivalent to‬‭x = x - y‬
‭●‬ ‭*= (Multiply and Assign):‬‭x *= y is equivalent to‬‭x = x * y‬
‭●‬ ‭/= (Divide and Assign):‬‭x /= y is equivalent to x‬‭= x / y‬
‭●‬ ‭//= (Floor Divide and Assign):‬‭x //= y is equivalent‬‭to x = x // y‬
‭●‬ ‭%= (Modulus and Assign):‬‭x %= y is equivalent to x‬‭= x % y‬
‭●‬ ‭**= (Exponent and Assign):‬‭x **= y is equivalent to‬‭x = x ** y‬

‭And for bitwise operations (though the question specifically asks for numeric):‬
‭●‬ ‭&= (Bitwise AND and Assign):‬‭x &= y‬
‭●‬ ‭|= (Bitwise OR and Assign):‬‭x |= y‬
‭●‬ ‭^= (Bitwise XOR and Assign):‬‭x ^= y‬
‭●‬ ‭>>= (Bitwise Right Shift and Assign):‬‭x >>= y‬
‭●‬ ‭<<= (Bitwise Left Shift and Assign):‬‭x <<= y‬

‭Example:‬

‭Python‬

‭# Numeric Operators‬
‭ =‬‭10‬
a
‭b =‬‭3‬
‭print(‬‭f"a + b =‬‭{a + b}‬‭"‬‭)‬ ‭# 13‬
‭ rint(‬‭f"a - b =‬‭{a - b}‬‭"‬‭)‬ ‭# 7‬
p
‭print(‬‭f"a * b =‬‭{a * b}‬‭"‬‭)‬ ‭# 30‬
‭print(‬‭f"a / b =‬‭{a / b}‬‭"‬‭)‬ ‭# 3.333...‬
‭print(‬‭f"a // b =‬‭{a // b}‬‭"‬‭)‬ ‭# 3‬
‭print(‬‭f"a % b =‬‭{a % b}‬‭"‬‭)‬ ‭# 1‬
‭print(‬‭f"a ** b =‬‭{a ** b}‬‭"‬‭)‬ ‭# 1000‬

‭# Assignment Operator‬
x‭ =‬‭5‬
‭print(‬‭f"x =‬‭{x}‬‭"‬‭)‬‭# 5‬

‭# Augmented Assignment Operators‬


x‭ +=‬‭2‬ ‭# x = x + 2‬
‭print(‬‭f"x += 2 -> x =‬‭{x}‬‭"‬‭)‬‭# 7‬
‭x *=‬‭3‬ ‭# x = x * 3‬
‭print(‬‭f"x *= 3 -> x =‬‭{x}‬‭"‬‭)‬‭# 21‬

‭(c) Explain how operator precedence is used in evaluating expressions.‬‭(3.5)‬

‭ perator Precedence‬‭in Python (and most programming‬‭languages) defines the‬


O
‭order in which different operators are evaluated in an expression that contains‬
‭multiple operators. It's similar to the BODMAS/PEMDAS rule in mathematics.‬
‭Operators with higher precedence are evaluated before operators with lower‬
‭precedence. If operators have the same precedence, their‬‭associativity‬‭5‬ ‭(usually‬
‭left-to-right, but sometimes right-to-left for operators like exponentiation) determines‬
‭the order.‬

‭ hy is it important?‬
W
‭Operator precedence ensures that expressions are evaluated consistently and‬
‭unambiguously, leading to predictable results. Without these rules, an expression like 3 + 4 * 2‬
‭could be interpreted as (3 + 4) * 2 = 14 or 3 + (4 * 2) = 11. Python's precedence rules dictate‬
‭the latter is correct because multiplication (*) has higher precedence than addition (+).‬
‭General Order of Precedence (Highest to Lowest - simplified list):‬
‭1.‬ ‭() (Parentheses):‬‭Expressions within parentheses are‬‭always evaluated first.‬
‭ hey can be used to override the default precedence.‬
T
‭ .‬ ‭**** (Exponentiation): Evaluated right-to-left. 2 ** 3 ** 2 is 2 ** (3 ** 2) = 2 ** 9 =‬
2
‭512.‬
‭3.‬ ‭+x, -x, ~x (Unary plus, Unary minus, Bitwise NOT)‬
‭4.‬ ‭*, /, //, % (Multiplication, Division, Floor Division, Modulus):‬‭Evaluated‬
l‭eft-to-right.‬
‭5.‬ ‭+, - (Addition, Subtraction):‬‭Evaluated left-to-right.‬
‭6.‬ ‭<<, >> (Bitwise Shifts):‬‭Evaluated left-to-right.‬
‭7.‬ ‭& (Bitwise AND):‬‭Evaluated left-to-right.‬
‭8.‬ ‭^ (Bitwise XOR):‬‭Evaluated left-to-right.‬
‭9.‬ ‭| (Bitwise OR):‬‭Evaluated left-to-right.‬
‭10.‬‭Comparison, Identity, Membership operators (==, !=, >, >=, <, <=, is, is not, in,‬
‭not in):‬‭These have lower precedence than arithmetic‬‭operators and are usually‬
‭evaluated left-to-right (though comparisons can be chained like a < b < c).‬
‭11.‬ ‭not (Logical NOT)‬
‭12.‬‭and (Logical AND)‬
‭13.‬‭or (Logical OR)‬
‭14.‬‭Conditional expression (x if C else y)‬
‭15.‬‭Assignment operators (=, +=, -=, etc.)‬‭: Have very‬‭low precedence.‬
‭16.‬‭lambda (Lambda expression)‬

‭How it's used in evaluating expressions:‬

‭Consider the expression: result = 10 + 5 * 2 - 4 / 2 ** 2‬


‭1.‬ ‭**** (Exponentiation): 2 ** 2 is evaluated first to 4. Expression becomes: result =‬
1‭ 0 + 5 * 2 - 4 / 4‬
‭ .‬ ‭* and / (Multiplication and Division):‬‭These have‬‭the same precedence and are‬
2
‭evaluated left-to-right.‬
‭○‬ ‭5 * 2 is 10.‬
‭○‬ ‭4 / 4 is 1.0. Expression becomes: result = 10 + 10 - 1.0‬
‭3.‬ ‭+ and - (Addition and Subtraction):‬‭These have the‬‭same precedence and are‬
‭evaluated left-to-right.‬
‭○‬ ‭10 + 10 is 20. Expression becomes: result = 20 - 1.0‬
‭○‬ ‭20 - 1.0 is 19.0.‬
‭4.‬ ‭Finally, result is assigned 19.0.‬

‭ sing Parentheses to Override Precedence:‬


U
‭If we wanted addition to happen before multiplication, we could use parentheses:‬
‭result = (10 + 5) * 2‬
‭1.‬ ‭(10 + 5) is evaluated first to 15.‬
‭2.‬ ‭15 * 2 is 30. result becomes 30.‬

‭ nderstanding operator precedence is crucial for writing correct and predictable‬


U
‭code. When in doubt, using parentheses to explicitly define the order of operations is‬
‭a good practice for clarity and correctness.‬

‭ 3 (a) What are the commonly used operators in python? Explain operator‬
Q
‭overloading with the help of an example.‬‭(6)‬

‭Commonly Used Operators in Python:‬

‭ ython offers a rich set of operators. Some of the most commonly used categories‬
P
‭and operators include:‬
‭1.‬ ‭Arithmetic Operators:‬
‭○‬ ‭+ (Addition)‬
‭○‬ ‭- (Subtraction)‬
‭○‬ ‭* (Multiplication)‬
‭○‬ ‭/ (Division - results in float)‬
‭○‬ ‭// (Floor Division - results in integer, discards remainder)‬
‭○‬ ‭% (Modulus - remainder of division)‬
‭○‬ ‭** (Exponentiation)‬
‭2.‬ ‭Assignment Operators:‬
‭○‬ ‭= (Assign)‬
‭○‬ ‭+=, -=, *=, /=, //=, %=, **= (Augmented assignment operators)‬
‭3.‬ ‭Comparison (Relational) Operators:‬
‭○‬ ‭==‬ ‭(Equal to)‬
‭○‬ ‭!=‬ ‭(Not equal to)‬
‭○‬ ‭>‬ ‭(Greater than)‬
‭○‬ ‭<‬ ‭(Less than)‬
‭○‬ ‭>=‬ ‭(Greater than or equal to)‬
‭○‬ ‭<=‬ ‭(Less than or equal‬‭6‬ ‭to)‬
‭4.‬ ‭Logical Operators:‬
‭○‬ ‭and‬ ‭(Logical AND: returns True if both operands are‬‭true)‬
‭○‬ ‭or‬ ‭(Logical OR: returns True if at least one operand‬‭is true)‬‭7‬
‭○‬ ‭not‬ ‭(Logical NOT: returns True if the operand is false,‬‭and vice-versa)‬‭8‬
‭5.‬ ‭Identity Operators:‬
‭○‬ ‭is (Returns True if both variables point to the same object in memory)‬
‭○‬ ‭is not (Returns True if both variables do not point to the same object)‬
‭6.‬ ‭Membership Operators:‬
‭○‬ ‭in (Returns True if a value is found in a sequence like list, tuple, string, or keys‬
‭of a dictionary)‬
‭○‬ ‭not in (Returns True if a value is not found in a sequence)‬
‭7.‬ ‭Bitwise Operators‬‭(less common in general scripting,‬‭but important in specific‬
‭domains):‬
‭○‬ ‭& (Bitwise AND)‬
‭○‬ ‭| (Bitwise OR)‬
‭○‬ ‭^ (Bitwise XOR)‬
‭○‬ ‭~ (Bitwise NOT/Complement)‬
‭○‬ ‭<< (Bitwise Left Shift)‬
‭○‬ ‭>> (Bitwise Right Shift)‬

‭Operator Overloading:‬

‭ perator overloading means giving extended meaning to an existing operator beyond‬


O
‭its predefined operational meaning. For example, the + operator is used for arithmetic‬
‭addition of numbers, but it can also be used for string concatenation or merging lists.‬
‭This is achieved by defining special methods (also known as "dunder" or "magic"‬
‭methods because they start and end with double underscores) in a class.‬

‭ hen an operator is used with objects of a custom class, Python looks for these‬
W
‭special methods in the class definition to determine how the operation should be‬
‭performed.‬

‭Example of Operator Overloading:‬

‭ et's create a Vector class representing a 2D vector and overload the + operator to‬
L
‭perform vector addition and the * operator for scalar multiplication.‬

‭Python‬

‭class‬‭Vector:‬
‭def‬‭__init__‬‭(self, x, y):‬
s‭ elf.x = x‬
‭self.y = y‬

‭def‬‭__str__‬‭(self):‬
‭# For printing the vector object‬
‭return‬‭f"Vector(‬‭{self.x}‬‭,‬‭{self.y}‬‭)"‬
‭def‬‭__add__‬‭(self, other_vector):‬
‭# Overloads the + operator for Vector objects‬
‭if‬‭isinstance‬‭(other_vector, Vector):‬
‭# Add two vectors‬
‭return‬ ‭Vector(self.x + other_vector.x,‬‭self.y + other_vector.y)‬
‭else‬‭:‬
‭# If the other operand is not a Vector, this operation is not supported‬
‭return‬‭NotImplemented‬‭# A special value‬‭indicating the operation is not implemented‬

‭def‬‭__mul__‬‭(self, scalar):‬
‭# Overloads the * operator for scalar multiplication‬‭(Vector * scalar)‬
‭if‬‭isinstance‬‭(scalar, (‬i‭nt‬‭,‬‭float‬‭)):‬
‭return‬ ‭Vector(self.x * scalar, self.y‬‭* scalar)‬
‭else‬‭:‬
‭return‬‭NotImplemented‬

‭def‬‭__rmul__‬‭(self, scalar):‬
‭# Overloads the * operator for scalar multiplication‬‭(scalar * Vector)‬
‭# This is called if the left operand does‬‭not support __mul__ with the Vector‬
‭if‬‭isinstance‬‭(scalar, (‬i‭nt‬‭,‬‭float‬‭)):‬
‭return‬ ‭Vector(self.x * scalar, self.y‬‭* scalar)‬‭# Or call self.__mul__(scalar)‬
‭else‬‭:‬
‭return‬‭NotImplemented‬

‭# Create Vector objects‬


v‭ 1 = Vector(‬‭2‭,‬ ‬‭3‭)‬ ‬
‭v2 = Vector(‬‭5‬‭,‬‭1‬‭)‬

‭# Using the overloaded + operator‬


v‭ 3 = v1 + v2‬ ‭# This calls v1.__add__(v2)‬
‭print(‬‭f"‬‭{v1}‬‭+‬‭{v2}‬‭=‬‭{v3}‬‭"‬‭)‬‭# Output: Vector(2, 3)‬‭+ Vector(5, 1) = Vector(7, 4)‬

‭# Using the overloaded * operator‬


s‭ calar_value =‬‭3‬
‭v4 = v1 * scalar_value‬‭# This calls v1.__mul__(scalar_value)‬
‭print(‬‭f"‬‭{v1}‬‭*‬‭{scalar_value}‬‭=‬‭{v4}‬‭"‬‭)‬‭# Output: Vector(2,‬‭3) * 3 = Vector(6, 9)‬

‭v5 = scalar_value * v1‬‭# This calls v1.__rmul__(scalar_value)‬‭because int doesn't know how to‬
‭multiply by Vector‬
‭print(‬‭f"‬‭{scalar_value}‬‭*‬‭{v1}‬‭=‬‭{v5}‬‭"‬‭)‬‭# Output: 3‬‭* Vector(2, 3) = Vector(6, 9)‬
‭ Example of unsupported operation‬
#
‭# v_error = v1 + 10 # This would try v1.__add__(10), which returns NotImplemented if not handled‬
‭# print(v_error) # Leads to TypeError: unsupported operand type(s) for +: 'Vector' and 'int'‬

‭Common special methods for operator overloading:‬


‭●‬ ‭__‬‭add__(self, other)‬ ‭for‬‭+‬
‭●‬ ‭__sub__(self, other)‬ ‭for‬‭-‬
‭●‬ ‭__mul__(self, other)‬ ‭for‬‭*‬
‭●‬ ‭__truediv__(self, other)‬ ‭for‬‭/‬
‭●‬ ‭__floordiv__(self, other)‬ ‭for‬‭//‬
‭●‬ ‭__mod__(self, other)‬ ‭for‬‭%‬
‭●‬ ‭__pow__(self, other)‬‭9‬ ‭for **‬
‭●‬ ‭__lt__(self, other) for <‬
‭●‬ ‭__le__(self, other) for <=‬
‭●‬ ‭__eq__(self, other) for ==‬
‭●‬ ‭__ne__(self, other) for !=‬
‭●‬ ‭__gt__(self, other) for >‬
‭●‬ ‭__ge__(self, other) for >=‬
‭●‬ ‭__len__(self) for len()‬
‭●‬ ‭__getitem__(self, key) for indexing obj[key]‬
‭●‬ ‭__setitem__(self, key, value) for obj[key] = value‬
‭●‬ ‭__str__(self) for str() and print() (user-friendly representation)‬
‭●‬ ‭__repr__(self) for repr() (developer-friendly, unambiguous representation)‬

(‭ b) What is type conversion? Write a python program to demonstrate number‬


‭type conversions.‬‭(6.5)‬

‭Type Conversion (Type Casting):‬

‭ ype conversion, also known as type casting, is the process of converting a value from‬
T
‭one data type to another. For example, converting an integer to a string, or a string to‬
‭a float.‬‭10‬ ‭Python is a dynamically typed language,‬‭but sometimes you need to explicitly‬
‭convert types to perform certain operations or to meet the requirements of‬
‭functions/methods.‬

‭There are two main types of type conversion:‬


‭1.‬ ‭Implicit Type Conversion (Coercion):‬
‭○‬ ‭Python automatically converts one data type to another without explicit‬
i‭nstruction from the programmer.‬
‭○‬ ‭This usually happens when‬‭11‬ ‭mixing numeric types in‬‭an operation, where the‬
‭"lower" type is converted to the "higher" type to prevent data loss (e.g., int to‬
‭float).‬
‭○‬ ‭Example: 5 + 2.0 (integer 5 is implicitly converted to float 5.0, result is 7.0).‬
‭ .‬ ‭Explicit Type Conversion (Casting):‬
2
‭○‬ ‭The programmer manually converts a data type using built-in functions.‬
‭○‬ ‭This is necessary when automatic conversion is not possible or when you‬
‭need a specific type.‬
‭○‬ ‭Common built-in functions for explicit type conversion:‬
‭■‬ ‭int(x, base=10): Converts x to an integer. base is optional for string‬
‭conversions.‬
‭■‬ ‭float(x): Converts x to a floating-point number.‬
‭■‬ ‭str(x): Converts x to a string.‬
‭■‬ ‭complex(real, imag): Creates a complex number.‬
‭■‬ ‭bool(x): Converts x to a boolean value (True or False).‬
‭■‬ ‭list(s): Converts sequence s to a list.‬
‭■‬ ‭tuple(s): Converts sequence s to a tuple.‬
‭■‬ ‭set(s): Converts sequence s to a set.‬
‭■‬ ‭dict(d): Creates a dictionary (e.g., from a sequence of key-value pairs).‬

‭Python Program to Demonstrate Number Type Conversions:‬

‭Python‬

‭# --- Implicit Type Conversion ---‬


‭ rint(‬‭"--- Implicit Type Conversion ---"‬‭)‬
p
‭num_int =‬‭10‬
‭num_float =‬‭5.5‬
‭result_implicit = num_int + num_float‬‭# int is promoted‬‭to float‬
‭print(‬‭f"Integer:‬‭{num_int}‬‭(type:‬‭{‬‭type‬‭(num_int)}‬‭)"‬‭)‬
‭print(‬‭f"Float:‬‭{num_float}‬‭(type:‬‭{‭t‬ ype‬‭(num_float)}‬‭)"‬‭)‬
‭print(‬‭f"Result of‬‭{num_int}‬‭+‬‭{num_float}‬‭:‬‭{result_implicit}‬‭(type:‬‭{‬‭type‬‭(result_implicit)}‬‭)"‬‭)‬
‭print(‬‭"-"‬ ‭*‬‭30‬‭)‬

‭# --- Explicit Type Conversion ---‬


‭print(‬‭"--- Explicit Type Conversion ---"‬‭)‬
‭# 1. Integer to Float‬
i‭nt_val =‬‭100‬
‭float_val_from_int =‬‭float‬‭(int_val)‬
‭print(‬‭f"Original Integer:‬‭{int_val}‬‭(type:‬‭{‭t‬ ype‬‭(int_val)}‬‭)"‬‭)‬
‭print(‬‭f"Converted to Float:‬‭{float_val_from_int}‬‭(type:‬‭{‭t‬ ype‬‭(float_val_from_int)}‬‭)"‬‭)‬
‭print()‬

‭# 2. Float to Integer‬
‭ oat_val =‬‭99.99‬
fl
‭int_val_from_float =‬‭int‬‭(float_val)‬‭# Truncates the‬‭decimal part‬
‭print(‬‭f"Original Float:‬‭{float_val}‬‭(type:‬‭{‬‭type‬‭(float_val)}‬‭)"‬‭)‬
‭print(‬‭f"Converted to Integer:‬‭{int_val_from_float}‬‭(type:‬‭{‭t‬ ype‬‭(int_val_from_float)}‬‭)"‬‭)‬
‭print()‬

‭# 3. String to Integer‬
s‭ tr_num_int =‬‭"12345"‬
‭int_from_str =‬‭int‬‭(str_num_int)‬
‭print(‬‭f"Original String: '‬‭{str_num_int}‬‭' (type:‬‭{‭t‬ ype‬‭(str_num_int)}‬‭)"‬‭)‬
‭print(‬‭f"Converted to Integer:‬‭{int_from_str}‬‭(type:‬‭{‭t‬ ype‬‭(int_from_str)}‬‭)"‬‭)‬
‭ Example of invalid conversion (will raise ValueError)‬
#
‭# str_invalid_int = "123.45"‬
‭# try:‬
‭# invalid_int = int(str_invalid_int)‬
‭# except ValueError as e:‬
‭# print(f"Error converting '{str_invalid_int}' to int: {e}")‬
‭print()‬

‭# 4. String to Float‬
s‭ tr_num_float =‬‭"67.89"‬
‭float_from_str =‬‭float‬‭(str_num_float)‬
‭print(‬‭f"Original String: '‬‭{str_num_float}‬‭' (type:‬‭{‬‭type‬‭(str_num_float)}‬‭)"‬‭)‬
‭print(‬‭f"Converted to Float:‬‭{float_from_str}‬‭(type:‬‭{‬‭type‬‭(float_from_str)}‬‭)"‬‭)‬
‭ Example of invalid conversion (will raise ValueError)‬
#
‭# str_invalid_float = "hello"‬
‭# try:‬
‭# invalid_float = float(str_invalid_float)‬
‭# except ValueError as e:‬
‭# print(f"Error converting '{str_invalid_float}' to float: {e}")‬
‭print()‬

‭# 5. Integer/Float to String‬
‭ um_to_str_int =‬‭777‬
n
‭str_from_int =‬‭str‬‭(num_to_str_int)‬
‭print(‬‭f"Original Integer:‬‭{num_to_str_int}‬‭(type:‬‭{‭t‬ ype‬‭(num_to_str_int)}‬‭)"‬‭)‬
‭print(‬‭f"Converted to String: '‬‭{str_from_int}‬‭' (type:‬‭{‭t‬ ype‬‭(str_from_int)}‬‭)"‬‭)‬

‭ um_to_str_float =‬‭23.45‬
n
‭str_from_float =‬‭str‬‭(num_to_str_float)‬
‭print(‬‭f"Original Float:‬‭{num_to_str_float}‬‭(type:‬‭{‬‭type‬‭(num_to_str_float)}‬‭)"‬‭)‬
‭print(‬‭f"Converted to String: '‬‭{str_from_float}‬‭' (type:‬‭{‬‭type‬‭(str_from_float)}‬‭)"‬‭)‬
‭print()‬

‭# 6. Number to Boolean‬
‭ um_zero =‬‭0‬
n
‭num_one =‬‭1‬
‭num_negative = -‬‭10‬
‭float_zero =‬‭0.0‬
‭bool_from_zero =‬‭bool‬‭(num_zero)‬
‭bool_from_one =‬‭bool‬‭(num_one)‬
‭bool_from_negative =‬‭bool‬‭(num_negative)‬
‭bool_from_float_zero =‬‭bool‬‭(float_zero)‬
‭print(‬‭f"Boolean of‬‭{num_zero}‬‭:‬‭{bool_from_zero}‬‭"‬‭)‬ ‭# False‬
‭print(‬‭f"Boolean of‬‭{num_one}‬‭:‬‭{bool_from_one}‬‭"‭)‬ ‬ ‭# True‬
‭print(‬‭f"Boolean of‬‭{num_negative}‬‭:‬‭{bool_from_negative}‬‭"‬‭)‬‭# True‬
‭print(‬‭f"Boolean of‬‭{float_zero}‬‭:‬‭{bool_from_float_zero}‬‭"‬‭)‬‭# False‬
‭print(‬‭"-"‬ ‭*‬‭30‬‭)‬

‭# 7. Integer to Complex‬
r‭ eal_part =‬‭5‬
‭imag_part =‬‭3‬
‭complex_num1 =‬‭complex‬‭(real_part, imag_part)‬
‭print(‬‭f"Integer‬‭{real_part}‬‭and‬‭{imag_part}‬‭converted‬‭to Complex:‬‭{complex_num1}‬‭(type:‬
‭{‭t‬ ype‬‭(complex_num1)}‬‭)"‬‭)‬

‭ omplex_num2 =‬‭complex‬‭(‬‭7‭)‬ ‬‭# Imaginary part defaults‬‭to 0‬


c
‭print(‬‭f"Integer 7 converted to Complex:‬‭{complex_num2}‬‭(type:‬‭{‬‭type‬‭(complex_num2)}‬‭)"‬‭)‬
‭print(‬‭"-"‬ ‭*‬‭30‬‭)‬

‭Output of the program:‬


-‭ -- Implicit Type Conversion ---‬
‭Integer: 10 (type: <class 'int'>)‬
‭Float: 5.5 (type: <class 'float'>)‬
‭Result of 10 + 5.5: 15.5 (type: <class 'float'>)‬
‭------------------------------‬
‭--- Explicit Type Conversion ---‬
‭Original Integer: 100 (type: <class 'int'>)‬
‭Converted to Float: 100.0 (type: <class 'float'>)‬

‭ riginal Float: 99.99 (type: <class 'float'>)‬


O
‭Converted to Integer: 99 (type: <class 'int'>)‬

‭ riginal String: '12345' (type: <class 'str'>)‬


O
‭Converted to Integer: 12345 (type: <class 'int'>)‬

‭ riginal String: '67.89' (type: <class 'str'>)‬


O
‭Converted to Float: 67.89 (type: <class 'float'>)‬

‭ riginal Integer: 777 (type: <class 'int'>)‬


O
‭Converted to String: '777' (type: <class 'str'>)‬
‭Original Float: 23.45 (type: <class 'float'>)‬
‭Converted to String: '23.45' (type: <class 'str'>)‬

‭ oolean of 0: False‬
B
‭Boolean of 1: True‬
‭Boolean of -10: True‬
‭Boolean of 0.0: False‬
‭------------------------------‬
‭Integer 5 and 3 converted to Complex: (5+3j) (type: <class 'complex'>)‬
‭Integer 7 converted to Complex: (7+0j) (type: <class 'complex'>)‬
‭------------------------------‬

I‭t's important to note that not all conversions are possible. For example, int("hello")‬
‭will raise a ValueError because "hello" cannot be directly interpreted as an integer.‬
‭Proper error handling (e.g., using try-except blocks) should be used when dealing‬
‭with conversions from types like strings, which might contain invalid values.‬

‭UNIT-II‬
‭ 4 (a) What are the different ways of string manipulation in python? WAP in‬
Q
‭python check whether the string is Symmetrical or Palindrome‬‭(6)‬

‭Different Ways of String Manipulation in Python:‬

‭ trings in Python are immutable, meaning their content cannot be changed after‬
S
‭creation. However, Python provides a rich set of built-in methods and operators that‬
‭return new strings based on manipulations of existing ones.‬
‭1.‬ ‭Concatenation:‬‭Combining strings using the + operator.‬
‭Python‬
s‭ 1 =‬‭"Hello"‬
‭s2 =‬‭"World"‬
‭s3 = s1 +‬‭" "‬ ‭+ s2‬ ‭# "Hello World"‬

‭2.‬ ‭Repetition:‬‭Repeating a string using the * operator.‬


‭Python‬
‭s =‬‭"abc"‬ ‭*‬‭3‬ ‭# "abcabcabc"‬

‭3.‬ ‭Indexing and Slicing:‬‭Accessing individual characters‬‭or subsequences.‬


‭○‬ ‭Indexing:‬‭my_string[i] (accesses character at index‬‭i).‬
‭○‬ ‭Slicing:‬‭my_string[start:end:step] (extracts a portion‬‭of the string).‬
‭Python‬
‭text =‬‭"Python"‬
‭char_p = text[‬‭0‭]‬ ‬ ‭# 'P'‬
‭sub = text[‬‭1‬‭:‭4‬ ‬‭]‬ ‭# "yth"‬
‭reversed_text = text[::-‬‭1‭]‬ ‬‭# "nohtyP"‬

‭4.‬ ‭String Methods:‬‭Python strings have numerous built-in‬‭methods for common‬


‭operations:‬
‭○‬ ‭Case Conversion:‬‭lower(), upper(), capitalize(), title(),‬‭swapcase().‬
‭○‬ ‭Searching and Replacing:‬‭find(sub), rfind(sub), index(sub),‬‭rindex(sub),‬
‭count(sub), startswith(prefix), endswith(suffix), replace(old, new).‬
‭○‬ ‭Stripping Whitespace:‬‭strip(), lstrip(), rstrip().‬
‭○‬ ‭Splitting and Joining:‬‭split(sep=None), rsplit(sep=None),‬‭partition(sep),‬
‭rpartition(sep), splitlines(), sep.join(iterable).‬
‭○‬ ‭Checking Character Types:‬‭isalpha(), isdigit(), isalnum(), isspace(), islower(),‬
i‭supper(), istitle().‬
‭○‬ ‭Formatting:‬
‭■‬ ‭f-strings (formatted string literals): f"Value is {variable}" (Python 3.6+)‬
‭■‬ ‭format() method: "Value is {}".format(variable)‬
‭■‬ ‭%-formatting (older style): "Value is %s" % variable‬
‭○‬ ‭Length:‬‭len(my_string) function (not a method, but‬‭commonly used).‬
‭○‬ ‭Padding:‬‭zfill(width), center(width, fillchar), ljust(width, fillchar), rjust(width,‬
‭fillchar).‬
‭ .‬ ‭Membership Testing:‬‭Using in and not in operators.‬
5
‭Python‬
‭if‬‭"key"‬‭in‬‭"keyword"‬‭:‬
‭print(‬‭"Found"‬‭)‬

‭Program to check whether a string is Symmetrical or Palindrome:‬


‭●‬ ‭Palindrome:‬‭A string is a palindrome if it reads the‬‭same forwards and backward‬
(‭ e.g., "madam", "racecar").‬
‭ ‬ ‭Symmetrical:‬‭A string is symmetrical if its first‬‭half is identical to its second half‬

‭(e.g., "khokho", "ababa" is not symmetrical but "abab" is, "amaama" is). This‬
‭definition can be tricky depending on odd/even length. If length is odd, the middle‬
‭character is often ignored for symmetry check, or symmetry is defined differently.‬
‭For this problem, we'll assume for an odd length string, it cannot be symmetrical‬
‭unless the two halves (excluding the middle character if necessary for‬
‭comparison) are identical. A common interpretation for symmetry is that the‬
‭string can be divided into two equal halves that are identical.‬

‭Python‬

‭def‬‭check_symmetrical_palindrome‬‭(s):‬
‭"""‬
‭ hecks if a string is symmetrical and/or a palindrome.‬
C
‭Prints the results.‬
‭"""‬
‭ riginal_string = s‬
o
‭length =‬‭len‬‭(s)‬
‭is_palindrome =‬‭False‬
‭is_symmetrical =‬‭False‬

‭# 1. Check for Palindrome‬


‭# A string is a palindrome if it is equal to its‬‭reverse.‬
‭# s.lower() can be used for case-insensitive palindrome‬‭check.‬
‭# For this example, we'll do a case-sensitive‬‭check.‬
‭reversed_s = s[::-‬‭1‬‭]‬
‭if‬ ‭s == reversed_s:‬
‭is_palindrome =‬‭True‬

‭# 2. Check for Symmetrical‬


‭# A string is symmetrical if its first half is‬‭the same as its second half.‬
‭# For odd length strings, the middle character‬‭is ignored for this type of symmetry.‬
‭# Or, if strict two equal halves are needed, odd‬‭length strings are not symmetrical.‬
‭# Let's consider the case where we divide it into‬‭two halves.‬
‭# If length is odd, it cannot be perfectly symmetrical‬‭into two identical halves.‬

‭# Alternative definition: two halves are mirror‬‭images (like palindrome but with a center point)‬
‭# For this problem, let's assume symmetrical means‬‭the first half == second half.‬
‭# This implies the string length must be even‬‭for perfect symmetry of this kind.‬
‭# If an odd length string like "ababa" were considered,‬‭"ab" != "ba".‬
‭# If a string like "amaama" is given, half1="ama",‬‭half2="ama", then it's symmetrical.‬

‭ id_point = length //‬‭2‬


m
‭first_half =‬‭""‬
‭second_half =‬‭""‬

‭if‬ ‭length %‬‭2‬ ‭==‬‭0‭:‬ ‬‭# Even length‬


‭first_half = s[:mid_point]‬
‭second_half = s[mid_point:]‬
‭else‬‭:‬‭# Odd length‬
‭# For symmetry, we could compare s[:mid_point]‬‭and s[mid_point+1:]‬
‭ Or, by one definition, an odd length string‬‭isn't symmetrical if we need two *equal* identical‬
#
‭ alves.‬
h
‭# Let's use the interpretation: first_half‬‭must equal second_half.‬
‭# Example: "phphph" -> first="php", second="hph"‬‭(not symmetrical)‬
‭# Example: "amaama" -> first="ama", second="ama"‬‭(symmetrical)‬
‭# Example: "khokho" -> first="kho", second="kho"‬‭(symmetrical)‬
‭first_half = s[:mid_point]‬
‭second_half = s[mid_point:]‬‭if‬ ‭length %‬‭2‬ ‭==‬‭0‬‭else‬ ‭s[mid_point + (length %‬‭2‬‭):]‬‭#‬
‭for "amaama", mid=3, second_half = s[3:]‬
‭# Corrected logic for symmetrical:‬
‭# For "level", mid=2. first_half = s[0:2]="le",‬‭second_half = s[3:5]="el" (Not symm)‬
‭# For "amaama", len=6, mid=3. first_half =‬‭s[0:3]="ama", second_half = s[3:6]="ama" (Symm)‬

‭# A simpler way for symmetry:‬


‭# Divide the string into two halves.‬
‭# For an even length string: s1 = s[0:length//2],‬‭s2 = s[length//2:]‬
‭# For an odd length string: s1 = s[0:length//2], s2 = s[length//2+1:] (ignoring middle char for‬
‭comparison)‬
‭# The question probably implies if the string‬‭s can be written as s1+s1.‬

‭# Let's use the definition: first half == second‬‭half.‬


‭# This means "ababa" is not symmetrical (ab !=‬‭ba)‬
‭# "khokho" is symmetrical (kho == kho)‬
‭# "amaama" is symmetrical (ama == ama)‬

‭# Re-evaluating symmetry condition based on common‬‭interpretations:‬


‭# A string s is symmetrical if s[:n/2] == s[n/2:]‬‭for even length.‬
‭# For odd length, it's typically not considered‬‭symmetrical by this strict definition.‬
‭# However, if the question implies string s can‬‭be formed by two identical halves.‬
‭# Example: "ABBA" is symmetrical (AB == BA is‬‭false). "AB" + "BA"‬
‭# Example: "ABABA", first half "AB", second half‬‭"BA".‬
‭# Let's assume symmetrical means the first n characters‬‭are identical to the last n characters.‬

‭# A more common definition for symmetrical string‬‭might be if s[:n//2] == s[n//2:] (for even length)‬
‭# or s[:n//2] == s[n//2 + 1:] (for odd length,‬‭checking parts around middle character)‬

‭# Let's go with a clear definition: a string is‬‭symmetrical if it can be divided into two identical halves.‬
‭# This implies that if the length is odd, it cannot‬‭be symmetrical in this sense unless the problem‬
‭specifies ignoring the middle char.‬
‭ Given "Symmetrical or Palindrome", "madam" is‬‭a palindrome, not symmetrical by "first_half ==‬
#
‭second_half" strict def.‬
‭# "khokho" is symmetrical and not a palindrome.‬
‭# "amaama" is symmetrical and a palindrome.‬

‭# Symmetrical: First half of the string is the‬‭same as the second half.‬


‭ =‬‭len‬‭(s)‬
n
‭flag_symmetrical =‬‭0‬‭# 0 for not symmetrical,‬‭1 for symmetrical‬
‭if‬ ‭n%‬‭2‬ ‭==‬‭0‭:‬ ‬‭# Even length‬
‭first_str = s[‬‭0‬ ‭: n//‬‭2‬‭]‬
‭second_str = s[n//‬‭2‬ ‭: n]‬
‭else‬‭:‬‭# Odd length‬
‭ For symmetry based on "first half == second‬‭half", odd length strings are generally not‬
#
‭considered symmetrical‬
‭# unless the middle character is ignored AND the remaining two halves are identical.‬
‭# "level": first="le", second="el" (around‬‭v) -> not symmetrical‬
‭# "abcdcba": first="abc", second="cba" (around d) -> not symmetrical‬
‭# "amaama" is length 6 (even). "ama" == "ama".‬‭Symmetrical.‬
‭# "khokho" is length 6 (even). "kho" == "kho".‬‭Symmetrical.‬
‭# If the intent for odd length is, e.g. "abXcba"‬‭-> no.‬
‭# If "abXab" -> yes.‬
‭# Let's stick to the definition where two‬‭halves must be equal.‬
‭# So, for odd length, it's symmetrical only‬‭if all chars are same, which is a palindrome too.‬
‭# Or, we consider symmetry around the midpoint.‬
‭# Let's use the common definition: s is symmetrical‬‭if s = A + A.‬
‭ This means only even length strings can‬‭be symmetrical unless A is a single char and the string‬
#
‭is A+X+A.‬
‭# The question is a bit ambiguous for odd‬‭length symmetry.‬
‭# Common interpretation: First half = Second‬‭half.‬
‭# For "ababa": len=5, mid=2. s[0:2]="ab",‬‭s[2:4]="ab" (if taking mid chars) s[3:5]="ba"‬
‭# If symmetry means s[:len//2] == s[len -‬‭len//2:], this handles even/odd.‬
‭# "ababa": s[0:2]="ab", s[3:5]="ba". Not symmetrical.‬
‭# "amaama": s[0:3]="ama", s[3:6]="ama". Symmetrical.‬

‭# A clear condition for symmetry (dividing‬‭into two parts and comparing):‬


‭ rst_str = s[‬‭0‬ ‭: n//‬‭2‬‭]‬
fi
‭second_str = s[n - n//‬‭2‬ ‭: n]‬‭# Takes the last‬‭n//2 characters‬

‭if‬ ‭first_str == second_str:‬‭# This handles "amaama"‬‭(ama == ama) and "khokho" (kho == kho)‬
‭is_symmetrical =‬‭True‬
‭else‬‭:‬‭# Check if middle char makes difference‬‭for odd length symmetry‬
‭if‬ ‭n %‬‭2‬ ‭!=‬‭0‭:‬ ‬‭# Odd length‬
‭# If first half = second half (ignoring‬‭middle)‬
‭# e.g. "racecar": "rac" vs "car" - no‬
‭# e.g. "abcdcba": "abc" vs "cba" - no‬
‭# e.g. "abXab": "ab" vs "ab" (ignoring‬‭X)‬
‭ rst_part_odd = s[‬‭0‬ ‭: n//‬‭2‭]‬ ‬
fi
‭second_part_odd = s[n//‬‭2‬ ‭+‬‭1‬ ‭: n]‬
‭if‬ ‭first_part_odd == second_part_odd:‬
‭# This means symmetrical around middle‬‭char. e.g. "abXab"‬
‭# However, "symmetrical" often means‬‭s = A + A.‬
‭# The question does not provide a‬‭strict definition for "symmetrical".‬
‭# Let's use the definition: first‬‭half is identical to the second half.‬
‭# For "ababa", first_half="ab", second_half="ba". Not symmetrical.‬
‭# "amaama", first_half="ama", second_half="ama".‬‭Symmetrical.‬
‭# "khokho", first_half="kho", second_half="kho". Symmetrical.‬
‭# So, we only need to compare s[0‬‭: n//2] and s[n//2 : n] (for even)‬
‭# or s[0 : n//2] and s[n//2+1 : n]‬‭for odd and they must be equal‬
‭# Let's refine:‬
‭pass‬‭# Sticking to s[:n//2] == s[n‬‭- n//2:] comparison above.‬

‭print(‬‭f"\nThe string is: '‬‭{original_string}‬‭'"‬‭)‬


‭if‬ ‭is_palindrome:‬
‭print(‬‭"This string is a Palindrome."‬‭)‬
‭else‬‭:‬
‭print(‬‭"This string is NOT a Palindrome."‬‭)‬

‭if‬ ‭is_symmetrical:‬
‭print(‬‭"This string is Symmetrical."‬‭)‬
‭else‬‭:‬
‭print(‬‭"This string is NOT Symmetrical."‬‭)‬

‭# Test cases‬
‭ heck_symmetrical_palindrome(‬‭"khokho"‬‭)‬ ‭# Symmetrical,‬‭Not Palindrome‬
c
‭check_symmetrical_palindrome(‬‭"amaama"‬‭)‬ ‭# Symmetrical,‬‭Palindrome‬
‭check_symmetrical_palindrome(‬‭"madam"‬‭)‬ ‭# Not Symmetrical‬‭(by first_half=second_half def),‬
‭Palindrome‬
‭ heck_symmetrical_palindrome(‬‭"level"‬‭)‬ ‭# Not Symmetrical,‬‭Palindrome‬
c
‭check_symmetrical_palindrome(‬‭"python"‬‭)‬ ‭# Not Symmetrical,‬‭Not Palindrome‬
‭check_symmetrical_palindrome(‬‭"ababa"‬‭)‬ ‭# Not Symmetrical‬‭(ab != ba), Palindrome‬
‭check_symmetrical_palindrome(‬‭"racecar"‬‭)‬‭# Not Symmetrical‬‭(rac != car), Palindrome‬
‭check_symmetrical_palindrome(‬‭"noon"‬‭)‬ ‭# Symmetrical,‬‭Palindrome‬
‭check_symmetrical_palindrome(‬‭"php"‬‭)‬ ‭# Not Symmetrical‬‭(p != p), Palindrome‬
‭# If symmetrical‬‭for "php" means p == p ignoring h, then yes.‬
‭# My current symm. logic: "php", n=3, n//2=1. first_str="p", second_str="p".‬
‭is_symmetrical=True.‬
‭# Palindrome:‬‭"php" == "php". is_palindrome=True.‬

‭ Refined Symmetrical Check:‬


#
‭# A string is symmetrical if the first half is equal to the second half.‬
‭# If the length is odd, the middle character is ignored, and the remaining halves are compared.‬

‭def‬‭refined_check_symmetrical_palindrome‬‭(s):‬
‭ riginal_string = s‬
o
‭length =‬‭len‬‭(s)‬
‭is_palindrome =‬‭False‬
‭is_symmetrical =‬‭False‬

‭# 1. Check for Palindrome‬


‭if‬ ‭s== s[::-‬‭1‭]‬ :‬
‭is_palindrome =‬‭True‬

‭# 2. Check for Symmetrical‬


‭# Symmetrical if first half == second half.‬
‭# For odd length, ignore middle character.‬
‭mid_point_strict_half = length //‬‭2‬

‭first_half = s[:mid_point_strict_half]‬
‭if‬ ‭length %‬‭2‬ ‭==‬‭0‭:‬ ‬‭# Even length‬
‭second_half = s[mid_point_strict_half:]‬
‭else‬‭:‬‭# Odd length, ignore middle char for symmetry‬‭check‬
‭second_half = s[mid_point_strict_half +‬‭1‭:‬ ]‬

‭if‬ ‭first_half
== second_half:‬
‭is_symmetrical =‬‭True‬

‭ rint(‬‭f"\nString: '‬‭{original_string}‬‭'"‬‭)‬
p
‭print(‬‭f" - Palindrome:‬‭{‭'‬Yes'‬‭if‬‭is_palindrome‬‭else‬‭'No'‬‭}‭"‬ ‬‭)‬
‭print(‬‭f" - Symmetrical (first half == second half,‬‭ignoring mid for odd len):‬‭{‭'‬Yes'‬‭if‬‭is_symmetrical‬
‭else‬‭'No'‬‭}‭"‬ ‬‭)‬

‭ rint(‬‭"\n--- Refined Symmetrical/Palindrome Check‬‭---"‬‭)‬


p
‭refined_check_symmetrical_palindrome(‬‭"khokho"‬‭)‬ ‭#‬‭S: Yes (kho==kho), P: No‬
r‭ efined_check_symmetrical_palindrome(‬‭"amaama"‬‭)‬ ‭# S: Yes (ama==ama), P: Yes‬
‭refined_check_symmetrical_palindrome(‬‭"madam"‬‭)‬ ‭#‬‭S: Yes (ma==ma, d ignored), P: Yes‬
‭refined_check_symmetrical_palindrome(‬‭"level"‬‭)‬ ‭#‬‭S: Yes (le==le, v ignored), P: Yes‬
‭refined_check_symmetrical_palindrome(‬‭"python"‬‭)‬ ‭#‬‭S: No (pyt!=hon), P: No‬
‭refined_check_symmetrical_palindrome(‬‭"ababa"‬‭)‬ ‭#‬‭S: Yes (ab==ab, a ignored), P: Yes‬
‭refined_check_symmetrical_palindrome(‬‭"racecar"‬‭)‬‭#‬‭S: Yes (rac==car, e ignored) - ERROR: (rac‬
‭!= car)‬
‭# My logic for "racecar": mid=3. first="rac", second="car". Not‬
‭symmetrical. Correct.‬
r‭ efined_check_symmetrical_palindrome(‬‭"noon"‬‭)‬ #
‭ S: Yes (no==on), P: Yes‬
‭refined_check_symmetrical_palindrome(‬‭"php"‬‭)‬ ‭#‬‭S: Yes (p==p, h ignored), P: Yes‬

‭ he refined symmetrical definition (first half == second half, ignoring middle for odd‬
T
‭length) makes sense for many common interpretations.‬

‭Final version for the program part:‬

‭Python‬

‭def‬‭check_string_properties‬‭(s):‬
"‭ ""‬
‭Checks if a string is Symmetrical and/or Palindrome.‬
‭A string is a Palindrome if it reads the same forwards and backward.‬
‭A string is Symmetrical if its first half is identical to its second half.‬
‭For odd length strings, the middle character is ignored for symmetry check.‬
‭"""‬
‭ =‬‭len‬‭(s)‬
n
‭is_palindrome =‬‭False‬
‭is_symmetrical =‬‭False‬

‭# Check for Palindrome‬


‭if‬ ‭s== s[::-‬‭1‭]‬ :‬
‭is_palindrome =‬‭True‬

‭# Check for Symmetrical‬


‭# Divide string into two halves. If odd, middle‬‭char is not part of halves to compare.‬
‭ alf_len = n //‬‭2‬
h
‭first_half = s[:half_len]‬
‭if‬ ‭n%‬‭2‬ ‭==‬‭0‭:‬ ‬‭# Even length‬
‭second_half = s[half_len:]‬
‭else‬‭:‬‭# Odd length‬
‭second_half = s[half_len +‬‭1‬‭:]‬‭# Skip the‬‭middle character‬

‭if‬ ‭first_half
== second_half:‬
‭is_symmetrical =‬‭True‬

‭print(‬‭f"\nInput string: '‬‭{s}‬‭'"‬‭)‬


‭if‬ ‭is_palindrome:‬
‭print(‬‭"The string is a Palindrome."‬‭)‬
‭else‬‭:‬
‭print(‬‭"The string is NOT a Palindrome."‬‭)‬

‭if‬ ‭is_symmetrical:‬
‭print(‬‭"The string is Symmetrical."‬‭)‬
‭else‬‭:‬
‭print(‬‭"The string is NOT Symmetrical."‬‭)‬

‭# Demonstrations:‬
‭check_string_properties(‬‭"khokho"‬‭)‬
‭ Expected: Symmetrical: Yes (kho==kho), Palindrome: No‬
#
‭# Output:‬
‭# Input string: 'khokho'‬
‭# The string is NOT a Palindrome.‬
‭# The string is Symmetrical.‬

‭check_string_properties(‬‭"amaama"‬‭)‬
‭ Expected: Symmetrical: Yes (ama==ama), Palindrome: Yes‬
#
‭# Output:‬
‭# Input string: 'amaama'‬
‭# The string is a Palindrome.‬
‭# The string is Symmetrical.‬

‭check_string_properties(‬‭"madam"‬‭)‬
‭ Expected: Symmetrical: Yes (ma==ma, d ignored), Palindrome: Yes‬
#
‭# Output:‬
‭# Input string: 'madam'‬
‭# The string is a Palindrome.‬
‭# The string is Symmetrical.‬
‭check_string_properties(‬‭"level"‬‭)‬
‭ Expected: Symmetrical: Yes (le==le, v ignored), Palindrome: Yes‬
#
‭# Output:‬
‭# Input string: 'level'‬
‭# The string is a Palindrome.‬
‭# The string is Symmetrical.‬

‭check_string_properties(‬‭"racecar"‬‭)‬
‭ Expected: Symmetrical: No (rac != car, e ignored), Palindrome: Yes‬
#
‭# Output:‬
‭# Input string: 'racecar'‬
‭# The string is a Palindrome.‬
‭# The string is NOT Symmetrical. (Correct: rac != car)‬

‭check_string_properties(‬‭"noon"‬‭)‬
‭ Expected: Symmetrical: Yes (no==on), Palindrome: Yes‬
#
‭# Output:‬
‭# Input string: 'noon'‬
‭# The string is a Palindrome.‬
‭# The string is Symmetrical.‬

‭check_string_properties(‬‭"ababa"‬‭)‬
‭ Expected: Symmetrical: Yes (ab==ab, mid 'a' ignored), Palindrome: Yes‬
#
‭# Output:‬
‭# Input string: 'ababa'‬
‭# The string is a Palindrome.‬
‭# The string is Symmetrical.‬

‭check_string_properties(‬‭"python"‬‭)‬
‭ Expected: Symmetrical: No, Palindrome: No‬
#
‭# Output:‬
‭# Input string: 'python'‬
‭# The string is NOT a Palindrome.‬
‭# The string is NOT Symmetrical.‬

‭ his version of symmetry seems consistent with typical interpretations where the‬
T
‭middle element is excluded for odd-length strings.‬

(‭ b) What are functions in python? WAP in python to use Pythagoras theorem to‬
‭calculate the length of the hypotenuse of a right-angled triangle where the‬
‭other sides have lengths 3 and 4.‬‭(6.5)‬
‭Functions in Python:‬

‭ ‬‭function‬‭in Python is a named block of reusable‬‭code that is designed to perform a‬


A
‭specific task. Functions help in organizing code, making it more modular, readable,‬
‭and easier to debug. They promote code reuse, as you can define a function once and‬
‭call it multiple times from different parts of your program with different inputs.‬

‭Key characteristics and components of Python functions:‬


‭1.‬ ‭Definition:‬
‭○‬ ‭Defined using the def keyword, followed by the function name, parentheses‬
(‭ ), and a colon :.‬
‭ ‬ ‭The parentheses can optionally contain‬‭parameters‬‭(also called arguments),‬

‭which are variables that receive input values when the function is called.‬
‭○‬ ‭The code block within the function is indented.‬
‭ ython‬
P
‭def‬‭function_name‬‭(parameter1, parameter2):‬
‭# Function body (code to be executed)‬
s‭ tatement1‬
‭statement2‬
‭return‬ ‭value‬‭# Optional: returns a value to the‬‭caller‬

‭2.‬ ‭Docstrings (Documentation Strings):‬


‭○‬ ‭An optional string literal placed as the first statement in a function's body,‬
‭ nclosed in triple quotes ("""Docstring goes here""").‬
e
‭○‬ ‭Used to document what the function does, its parameters, and what it‬
‭returns. Accessible via function_name.__doc__.‬
‭ .‬ ‭Calling a Function:‬
3
‭○‬ ‭Once defined, a function is executed by calling its name followed by‬
‭parentheses, providing actual values (‬‭arguments‬‭) for‬‭the parameters if any.‬
‭○‬ ‭result = function_name(arg1, arg2)‬
‭4.‬ ‭Parameters and Arguments:‬
‭○‬ ‭Parameters:‬‭Variables listed inside the parentheses‬‭in the function definition.‬
‭○‬ ‭Arguments:‬‭Actual values passed to the function when‬‭it is called.‬
‭○‬ ‭Python supports various types of arguments:‬
‭■‬ ‭Positional arguments:‬‭Matched by their position.‬
‭■‬ ‭Keyword arguments:‬‭Passed with name=value syntax,‬‭order doesn't‬
‭matter.‬
‭■‬ ‭Default arguments:‬‭Parameters can have default values,‬‭making the‬
‭argument optional during the call.‬
‭■‬ ‭Variable-length arguments:‬‭*args (for non-keyworded‬‭variable number‬
‭ f arguments) and **kwargs (for keyworded variable number of‬
o
‭arguments).‬
‭ .‬ ‭return Statement:‬
5
‭○‬ ‭Used to exit a function and optionally pass back a value (or multiple values as‬
‭a tuple) to the caller.‬
‭○‬ ‭If no return statement is present, or return is used without an expression, the‬
‭function returns None by default.‬
‭6.‬ ‭Scope of Variables:‬
‭○‬ ‭Variables defined inside a function are‬‭local‬‭to that function (local scope) and‬
‭cannot be accessed from outside unless explicitly returned or declared global‬
‭(which is generally discouraged).‬
‭○‬ ‭Variables defined outside all functions have‬‭global‬‭scope.‬

‭Benefits of using functions:‬


‭●‬ ‭Modularity:‬‭Breaks down complex problems into smaller,‬‭manageable pieces.‬
‭●‬ ‭Reusability:‬‭Avoids code duplication. Define once,‬‭use many times.‬
‭●‬ ‭Readability:‬‭Makes code easier to understand and follow.‬
‭●‬ ‭Maintainability:‬‭Easier to update and debug smaller,‬‭self-contained functions.‬
‭●‬ ‭Abstraction:‬‭Hides the internal details of how a task‬‭is performed, only exposing‬
‭the interface (function name and parameters).‬

‭Program to use Pythagoras theorem:‬

‭ he Pythagoras theorem states that in a right-angled triangle, the square of the length of the‬
T
‭hypotenuse (the side opposite the right angle) is equal to the sum of the squares of the‬
‭lengths of the other two sides (legs).‬
‭Formula: c2=a2+b2, where c is the hypotenuse, and a and b are the other two sides.‬
‭So, c=a2+b2​.‬

‭Python‬

‭import‬ ‭math‬‭# To use the sqrt function‬

‭def‬‭calculate_hypotenuse‬‭(side_a, side_b):‬
‭"""‬
‭ alculates the length of the hypotenuse of a right-angled triangle‬
C
‭given the lengths of the other two sides.‬

‭Args:‬
s‭ ide_a (float or int): Length of one side of the right-angled triangle.‬
‭side_b (float or int): Length of the other side of the right-angled triangle.‬

‭Returns:‬
‭float: The length of the hypotenuse, or None if inputs are invalid.‬
‭"""‬
‭if‬‭not‬ ‭(‬‭isinstance‬‭(side_a, (‬i‭nt‬‭,‬‭float‬‭))‬‭and‬‭isinstance‬‭(side_b,‬‭(‭i‬nt‬‭,‬‭float‬‭))):‬
‭print(‬‭"Error: Both side lengths must be numbers."‬‭)‬
‭return‬‭None‬
‭if‬ ‭side_a <=‬‭0‬‭or‬ ‭side_b <=‬‭0‬‭:‬
‭print(‬‭"Error: Side lengths must be positive."‬‭)‬
‭return‬‭None‬

‭ ypotenuse_squared = side_a**‬‭2‬ ‭+ side_b**‬‭2‬


h
‭hypotenuse = math.sqrt(hypotenuse_squared)‬
‭return‬ ‭hypotenuse‬

‭# Given sides have lengths 3 and 4‬


s‭ ide1 =‬‭3‬
‭side2 =‬‭4‬

‭# Calculate the hypotenuse using the function‬


‭hypotenuse_length = calculate_hypotenuse(side1, side2)‬

‭if‬ ‭hypotenuse_length‬‭is‬‭not‬‭None‬‭:‬
‭ rint(‬‭f"For a right-angled triangle with sides‬‭of length‬‭{side1}‬‭and‬‭{side2}‬‭:"‬‭)‬
p
‭print(‬‭f"The length of the hypotenuse is:‬‭{hypotenuse_length}‬‭"‬‭)‬

‭# Example with invalid input‬


‭ rint(‬‭"\n--- Example with invalid input ---"‬‭)‬
p
‭invalid_hyp = calculate_hypotenuse(-‬‭3‭,‬ ‬‭4‬‭)‬
‭if‬ ‭invalid_hyp‬‭is‬‭None‬‭:‬
‭print(‬‭"Calculation failed for invalid input as‬‭expected."‬‭)‬

‭invalid_hyp_type = calculate_hypotenuse(‬‭"three"‬‭,‬‭4‬‭)‬
‭if‬ ‭invalid_hyp_type‬‭is‬‭None‬‭:‬
‭print(‬‭"Calculation failed for invalid type as‬‭expected."‬‭)‬

‭Output:‬
‭ or a right-angled triangle with sides of length 3 and 4:‬
F
‭The length of the hypotenuse is: 5.0‬

-‭ -- Example with invalid input ---‬


‭Error: Side lengths must be positive.‬
‭Calculation failed for invalid input as expected.‬
‭Error: Both side lengths must be numbers.‬
‭Calculation failed for invalid type as expected.‬

‭ 5 (a) Define the following: find(), isalnum(), zfill(), strip(), split() and rindex().‬
Q
‭(6)‬

‭These are all string methods in Python, used for various string manipulation tasks.‬
‭1.‬ ‭find(substring, start=0, end=len(string))‬
‭○‬ ‭Definition:‬‭Searches for the first occurrence of substring‬‭within the string (or‬
‭ specified slice string[start:end]).‬
a
‭ ‬ ‭Returns:‬‭The lowest index in the string where substring‬‭is found. If substring‬

‭is not found, it returns -1.‬
‭○‬ ‭Example:‬
‭Python‬
s‭ =‬‭"hello world, hello"‬
‭print(s.find(‬‭"hello"‬‭))‬ ‭# Output: 0‬
‭print(s.find(‬‭"world"‬‭))‬ ‭# Output: 6‬
‭print(s.find(‬‭"Python"‬‭))‬ ‭# Output: -1‬
‭print(s.find(‬‭"hello"‬‭,‬‭5‬‭))‬‭# Output: 13 (starts searching‬‭from index 5)‬

‭2.‬ ‭isalnum()‬
‭○‬ ‭Definition:‬‭Checks if all characters in the string‬‭are alphanumeric (either‬
‭ lphabets from a-z, A-Z or digits from 0-9) and the string is not empty.‬
a
‭ ‬ ‭Returns:‬‭True if all characters are alphanumeric and‬‭the string has at least‬

‭one character. False otherwise (e.g., if it contains spaces, punctuation, or is‬
‭empty).‬
‭○‬ ‭Example:‬
‭Python‬
s‭ 1 =‬‭"Python123"‬
‭s2 =‬‭"Python 123"‬‭# Contains space‬
‭s3 =‬‭"Python@"‬ ‭# Contains symbol‬
‭s4 =‬‭""‬ ‭# Empty string‬
‭print(‬‭f"'‬‭{s1}‬‭'.isalnum():‬‭{s1.isalnum()}‬‭"‬‭)‬‭# Output:‬‭True‬
‭print(‬‭f"'‬‭{s2}‬‭'.isalnum():‬‭{s2.isalnum()}‬‭"‬‭)‬‭# Output:‬‭False‬
‭print(‬‭f"'‬‭{s3}‬‭'.isalnum():‬‭{s3.isalnum()}‬‭"‬‭)‬‭# Output:‬‭False‬
‭print(‬‭f"'‬‭{s4}‬‭'.isalnum():‬‭{s4.isalnum()}‬‭"‬‭)‬‭# Output: False‬

‭3.‬ ‭zfill(width)‬
‭○‬ ‭Definition:‬‭Pads the string on the left with zeros ('0') to make it reach a total‬
‭ idth.‬
w
‭○‬ ‭If the string starts with a sign character (+ or -), the zeros are inserted‬‭after‬
‭the sign character.‬
‭○‬ ‭If width is less than or equal to the length of the string, the original string is‬
‭returned.‬
‭○‬ ‭Returns:‬‭A new string, padded with leading zeros.‬
‭○‬ ‭Example:‬
‭Python‬
s‭ 1 =‬‭"42"‬
‭s2 =‬‭"-42"‬
‭s3 =‬‭"abc"‬
‭print(s1.zfill(‬‭5‬‭))‬ ‭# Output: "00042"‬
‭print(s2.zfill(‬‭5‬‭))‬ ‭# Output: "-0042"‬
‭print(s3.zfill(‬‭2‬‭))‬ ‭# Output: "abc" (width <= len(s3))‬
‭print(s1.zfill(‬‭2‬‭))‬ ‭# Output: "42"‬

‭4.‬ ‭strip(chars=None)‬
‭○‬ ‭Definition:‬‭Removes leading and trailing characters‬‭from a string.‬
‭○‬ ‭If chars is not specified or is None, it removes leading and trailing whitespace‬
‭ haracters (space, tab \t, newline \n, carriage return \r, form feed \f, vertical‬
c
‭tab \v).‬
‭ ‬ ‭If chars is a string, it removes any character present in chars from the‬

‭beginning and end of the original string. It does not remove these characters‬
‭from the middle of the string.‬
‭○‬ ‭Returns:‬‭A new string with leading/trailing characters‬‭removed.‬
‭○‬ ‭Related methods:‬‭lstrip() (removes leading only),‬‭rstrip() (removes trailing‬
‭only).‬
‭○‬ ‭Example:‬
‭Python‬
s‭ 1 =‬‭" hello world "‬
‭s2 =‬‭"---hello world+++"‬
‭s3 =‬‭"abracadabra"‬
‭print(‬‭f"'‬‭{s1.strip()}‬‭'"‬‭)‬ ‭ Output: "'hello‬‭world'"‬
#
‭print(‬‭f"'‬‭{s2.strip(‬‭'-+'‬‭)}‬‭'"‬‭)‬ ‭# Output: "'hello‬‭world'" (removes leading - and trailing +)‬
‭print(‬‭f"'‬‭{s3.strip(‬‭'abr'‬‭)}‬‭'"‬‭)‬ ‭# Output: "'cad'" (removes 'a', 'b', 'r' from ends)‬

‭5.‬ ‭split(sep=None, maxsplit=-1)‬


‭○‬ ‭Definition:‬‭Splits the string into a list of substrings based on a delimiter sep.‬
‭○‬ ‭If sep is not specified or is None, the string is split by consecutive whitespace‬
‭ haracters, and empty strings are not included in the result.‬
c
‭○‬ ‭If sep is specified, it's used as the delimiter. Multiple consecutive delimiters‬
‭will result in empty strings in the list if sep is given.‬
‭○‬ ‭maxsplit is an optional argument. If specified, the string is split at most‬
‭maxsplit times. The last element in the list will contain the remainder of the‬
‭string. Default is -1 (no limit).‬
‭○‬ ‭Returns:‬‭A list of strings.‬
‭○‬ ‭Example:‬
‭Python‬
s‭ 1 =‬‭"hello world example"‬
‭s2 =‬‭"apple,banana,cherry"‬
‭s3 =‬‭"one::two:::three"‬
‭print(s1.split())‬ ‭# Output: ['hello',‬‭'world', 'example'] (splits by space)‬
‭print(s2.split(‬‭','‬‭))‬ ‭# Output: ['apple',‬‭'banana', 'cherry']‬
‭print(s2.split(‬‭','‬‭, maxsplit=‬‭1‬‭))‬‭# Output: ['apple',‬‭'banana,cherry']‬
‭print(s3.split(‬‭'::'‬‭))‬ ‭# Output: ['one',‬‭'two', ':three']‬
‭print(s3.split(‬‭':'‬‭,maxsplit=‬‭2‭)‬ )‬ ‭# Output: ['one',‬‭'', 'two:::three']‬

‭6.‬ ‭rindex(substring, start=0, end=len(string))‬


‭○‬ ‭Definition:‬‭Similar to find(), but searches for the‬‭last‬‭occurrence (highest‬
i‭ndex) of substring within the string (or a specified slice string[start:end]).‬
‭ ‬ ‭Returns:‬‭The highest index in the string where substring‬‭is found.‬

‭○‬ ‭Raises:‬‭ValueError if the substring is not found (unlike‬‭find() which returns -1).‬
‭○‬ ‭Example:‬
‭Python‬
s‭ =‬‭"hello world, hello"‬
‭print(s.rindex(‬‭"hello"‬‭))‬ ‭# Output: 13‬
‭ rint(s.rindex(‬‭"world"‬‭))‬ ‭# Output: 6‬
p
‭print(s.rindex(‬‭"o"‬‭))‬ ‭# Output: 14‬
‭print(s.rindex(‬‭"hello"‬‭,‬‭0‬‭,‬‭10‬‭))‬‭# Output: 0 (searches‬‭"hello worl")‬
‭try‬‭:‬
‭print(s.rindex(‬‭"Python"‬‭))‬
‭except‬ ‭ValueError‬‭as‬ ‭e:‬
‭print(e)‬ ‭# Output: substring not‬‭found‬

*‭ ‭(‬ b) Write the definition of a function Reverse(X). WAP to display the elements in‬
‭reverse order such that each displayed element is twice of the original element‬
‭(element 2) of the List X.‬‭(3.5)‬

‭Python‬

‭def‬‭Reverse‬‭(X):‬
‭"""‬
‭ isplays the elements of a list X in reverse order,‬
D
‭with each displayed element being twice its original value.‬
‭Assumes X contains elements that can be multiplied by 2.‬

‭Args:‬
‭X (list): The input list.‬
‭"""‬
‭if‬‭not‬‭isinstance‬‭(X,‬‭list‬‭):‬
‭print(‬‭"Error: Input must be a list."‬‭)‬
‭return‬

‭ rint(‬‭"Original list:"‬‭, X)‬


p
‭print(‬‭"Elements in reverse order (each doubled):"‬‭)‬

‭# Iterate through the list in reverse order‬


‭# Slicing X[::-1] creates a reversed copy of the‬‭list‬
‭for‬ ‭i‬‭in‬‭range‬‭(‬‭len‬‭(X) -‬‭1‬‭, -‬‭1‬‭, -‬‭1‬‭):‬‭# Or use for‬‭element in reversed(X):‬
‭try‬‭:‬
‭ oubled_element = X[i] *‬‭2‬
d
‭print(doubled_element, end=‬‭" "‬‭)‬
‭except‬ ‭TypeError:‬
‭# Handle cases where an element might‬‭not support multiplication‬
‭# For example, if the list contains strings‬‭that can't be * 2 in the numeric sense‬
‭# (though string * 2 is repetition, the‬‭question implies numeric doubling)‬
‭# For simplicity, we'll assume numeric‬‭or compatible types.‬
‭# If strict numeric doubling is required,‬‭add type checks.‬
‭ rint(‬‭f"(Cannot double element:‬‭{X[i]}‬‭)"‬‭,‬‭end=‬‭" "‬‭)‬
p
‭print()‬‭# For a new line at the end‬

‭ Write a program (WAP) to use this function:‬


#
‭# This part is the "Write A Program" (WAP) that uses the function.‬
‭# Usually, WAP implies defining and then demonstrating its usage.‬

‭if‬ ‭__name__ ==‬‭"__main__"‬‭:‬


‭ rint(‬‭"--- Test Case 1: List of numbers ---"‬‭)‬
p
‭list1 = [‬‭1‭,‬ ‬‭2‬‭,‬‭3‬‭,‬‭4‬‭,‬‭5‭]‬ ‬
‭Reverse(list1)‬ ‭# Expected: 10 8 6 4 2‬

‭print(‬‭"\n--- Test Case 2: List with mixed types‬‭(if applicable, depends on interpretation of‬
‭'element*2') ---"‬‭)‬
‭# If the problem implies numeric multiplication,‬‭strings would cause issues or be repeated.‬
‭# Let's assume numeric context.‬
l‭ist2 = [‬‭10‬‭,‬‭20‬‭,‬‭0‬‭, -‬‭5‬‭]‬
‭Reverse(list2)‬ ‭# Expected: -10 0 40 20‬

‭ rint(‬‭"\n--- Test Case 3: Empty list ---"‬‭)‬


p
‭list3 = []‬
‭Reverse(list3)‬‭# Expected: (prints "Elements in‬‭reverse order..." then nothing)‬

‭ rint(‬‭"\n--- Test Case 4: List with one element‬‭---"‬‭)‬


p
‭list4 = [‬‭100‬‭]‬
‭Reverse(list4)‬‭# Expected: 200‬

‭# Example with non-numeric type to show potential‬‭issue if not handled/intended‬


‭# list5 = [1, 'a', 3]‬
‭# Reverse(list5) # This would cause TypeError‬‭for 'a'*2 if not handled inside Reverse‬

‭Output of the program:‬


-‭ -- Test Case 1: List of numbers ---‬
‭Original list: [1, 2, 3, 4, 5]‬
‭Elements in reverse order (each doubled):‬
‭10 8 6 4 2‬

-‭ -- Test Case 2: List with mixed types (if applicable, depends on interpretation of‬
‭'element*2') ---‬
‭Original list: [10, 20, 0, -5]‬
‭Elements in reverse order (each doubled):‬
‭-10 0 40 20‬

-‭ -- Test Case 3: Empty list ---‬


‭Original list: []‬
‭Elements in reverse order (each doubled):‬

-‭ -- Test Case 4: List with one element ---‬


‭Original list: [100]‬
‭Elements in reverse order (each doubled):‬
‭200‬

‭Alternative for Reverse(X) iteration:‬

‭Python‬

‭def‬‭Reverse_alternative‬‭(X):‬
‭if‬‭not‬‭isinstance‬‭(X,‬‭list‬‭):‬
‭print(‬‭"Error: Input must be a list."‬‭)‬
‭return‬
‭ rint(‬‭"Original list (alternative):"‬‭, X)‬
p
‭print(‬‭"Elements in reverse order (each doubled,‬‭alternative):"‬‭)‬
‭# Using slicing to reverse the list first, then‬‭iterate‬
‭for‬ ‭element‬‭in‬ ‭X[::-‬‭1‬‭]:‬‭# X[::-1] creates a reversed‬‭copy‬
‭try‬‭:‬
‭ oubled_element = element *‬‭2‬
d
‭print(doubled_element, end=‬‭" "‬‭)‬
‭except‬ ‭TypeError:‬
‭print(‬‭f"(Cannot double element:‬‭{element}‬‭)"‬‭,‬‭end=‬‭" "‬‭)‬
‭ rint()‬
p

‭ his alternative is often more Pythonic for iterating in reverse. The original range‬
T
‭based approach is also correct.‬

(‭ c) Write a function mul () which accepts list L, odd_L, even_L as its parameters.‬
‭Transfer all even values of list L to even_L and all odd values of L to odd_L.‬‭(3)‬

I‭t's important to note that lists in Python are passed by object reference. So, if odd_L‬
‭and even_L are lists passed to the function, modifications made to them (like‬
‭appending elements) inside the function will persist outside the function, assuming‬
‭they were initialized as empty lists before the call.‬

‭Python‬

‭def‬‭mul‬‭(L, odd_L, even_L):‬


"‭ ""‬
‭Accepts a list L, and two other lists odd_L and even_L.‬
‭Transfers all even integer values from L to even_L and all odd‬
‭integer values from L to odd_L.‬
‭Assumes L contains integer values.‬
‭Modifies odd_L and even_L in place.‬

‭Args:‬
‭L (list): The source list of numbers.‬
‭odd_L (list): The list to store odd numbers from L.‬
‭even_L (list): The list to store even numbers from L.‬
‭"""‬
‭if‬‭not‬‭all‬‭(‬‭isinstance‬‭(param,‬‭list‬‭)‬‭for‬ ‭param‬‭in‬ ‭[L,
odd_L, even_L]):‬
‭ rint(‬‭"Error: All parameters L, odd_L, and‬‭even_L must be lists."‬‭)‬
p
‭return‬

‭for‬ ‭number‬‭in‬ ‭L:‬


‭if‬‭isinstance‬‭(number,‬‭int‬‭):‬‭# Process only‬‭integers‬
‭if‬ ‭number %‬‭2‬ ‭==‬‭0‬‭:‬
‭even_L.append(number)‬
‭else‬‭:‬
‭odd_L.append(number)‬
‭else‬‭:‬
‭print(‬‭f"Warning: Element '‬‭{number}‬‭' in‬‭L is not an integer and will be skipped."‬‭)‬

‭# Demonstration of the mul() function:‬


‭if‬ ‭__name__
==‬‭"__main__"‬‭:‬
‭source_list = [‬‭1‬‭,‬‭2‭,‬ ‬‭3‭,‬ ‬‭4‬‭,‬‭5‬‭,‬‭6‬‭,‬‭7‬‭,‬‭8‬‭,‬‭9‬‭,‬‭10‬‭,‬‭11.5‬‭,‬‭"text"‬‭]‬

‭# Initialize empty lists for odd and even numbers‬


‭ dd_numbers = []‬
o
‭even_numbers = []‬

‭ rint(‬‭f"Original source list L:‬‭{source_list}‬‭"‭)‬ ‬


p
‭print(‬‭f"Initial odd_L:‬‭{odd_numbers}‬‭"‬‭)‬
‭print(‬‭f"Initial even_L:‬‭{even_numbers}‬‭"‭)‬ ‬

‭# Call the function‬


‭mul(source_list, odd_numbers, even_numbers)‬

‭ rint(‬‭f"\nAfter calling mul(L, odd_L, even_L):"‬‭)‬


p
‭print(‬‭f"Source list L (remains unchanged):‬‭{source_list}‬‭"‬‭)‬
‭print(‬‭f"odd_L (contains odd numbers from L):‬‭{odd_numbers}‬‭"‬‭)‬
‭print(‬‭f"even_L (contains even numbers from L):‬‭{even_numbers}‬‭"‭)‬ ‬

‭ rint(‬‭"\n--- Another Test Case ---"‬‭)‬


p
‭L2 = [‬‭22‬‭,‬‭33‬‭,‬‭44‬‭,‬‭55‬‭,‬‭66‬‭,‬‭0‬‭, -‬‭1‭,‬ -‬‭2‭]‬ ‬
‭odd_L2 = []‬
‭even_L2 = []‬
‭mul(L2, odd_L2, even_L2)‬
‭print(‬‭f"L2:‬‭{L2}‬‭"‬‭)‬
‭print(‬‭f"odd_L2:‬‭{odd_L2}‬‭"‬‭)‬
‭print(‬‭f"even_L2:‬‭{even_L2}‬‭"‬‭)‬

‭ rint(‬‭"\n--- Test with empty source list ---"‬‭)‬


p
‭L_empty = []‬
‭odd_L_empty = []‬
‭ ven_L_empty = []‬
e
‭mul(L_empty, odd_L_empty, even_L_empty)‬
‭print(‬‭f"L_empty:‬‭{L_empty}‬‭"‬‭)‬
‭print(‬‭f"odd_L_empty:‬‭{odd_L_empty}‬‭"‬‭)‬
‭print(‬‭f"even_L_empty:‬‭{even_L_empty}‬‭"‬‭)‬

‭Output of the demonstration:‬

‭ riginal source list L: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11.5, 'text']‬


O
‭Initial odd_L: []‬
‭Initial even_L: []‬
‭Warning: Element '11.5' in L is not an integer and will be skipped.‬
‭Warning: Element 'text' in L is not an integer and will be skipped.‬

‭ fter calling mul(L, odd_L, even_L):‬


A
‭Source list L (remains unchanged): [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11.5, 'text']‬
‭odd_L (contains odd numbers from L): [1, 3, 5, 7, 9]‬
‭even_L (contains even numbers from L): [2, 4, 6, 8, 10]‬

-‭ -- Another Test Case ---‬


‭L2: [22, 33, 44, 55, 66, 0, -1, -2]‬
‭odd_L2: [33, 55, -1]‬
‭even_L2: [22, 44, 66, 0, -2]‬

-‭ -- Test with empty source list ---‬


‭L_empty: []‬
‭odd_L_empty: []‬
‭even_L_empty: []‬

‭ he question implies that odd_L and even_L are initially empty lists that get populated‬
T
‭by the function. The source list L itself is not modified (i.e., elements are not removed‬
‭from L).‬

‭UNIT-III‬
‭ 6 (a) Write a program that accepts filename as an input from the user. Open‬
Q
‭the file and count the number of times a character appears in the file.‬‭(6.5)‬

‭This program should:‬


‭1.‬ ‭Prompt the user to enter a filename.‬
‭2.‬ ‭Prompt the user to enter the character they want to count.‬
‭3.‬ ‭Open the specified file.‬
‭4.‬ ‭Read the file content.‬
‭5.‬ ‭Count occurrences of the specified character (case-sensitively or‬
‭ ase-insensitively, typically case-sensitive unless specified). Let's assume‬
c
‭case-sensitive.‬
‭ .‬ ‭Display the count.‬
6
‭7.‬ ‭Handle potential errors like FileNotFoundError if the file doesn't exist.‬

‭Python‬

‭def‬‭count_character_in_file‬‭():‬
"‭ ""‬
‭Accepts a filename and a character from the user,‬
‭then counts and displays the occurrences of that character in the file.‬
‭"""‬
‭try‬‭:‬
‭ lename =‬‭input‬‭(‬‭"Enter the filename (e.g.,‬‭story.txt): "‬‭)‬
fi
‭char_to_count =‬‭input‬‭(‬‭"Enter the character‬‭to count: "‬‭)‬

‭if‬‭len‬‭(char_to_count) !=‬‭1‭:‬ ‬
‭print(‬‭"Error: Please enter a single character‬‭to count."‬‭)‬
‭return‬

‭count =‬‭0‬
‭with‬‭open‬‭(filename,‬‭'r'‬‭)‬‭as‬ ‭file:‬‭# Open file‬‭in read mode‬
‭for‬ ‭line‬‭in‬ ‭file:‬‭# Read file line by‬‭line (memory efficient for large files)‬
‭for‬ ‭char_in_line‬‭in‬ ‭line:‬
‭if‬ ‭char_in_line == char_to_count:‬
‭count +=‬‭1‬
‭print(‬‭f"The character '‬‭{char_to_count}‬‭' appears‬‭{count}‬‭times in the file '‬‭{filename}‬‭'."‬‭)‬

‭except‬‭FileNotFoundError:‬
‭print(‬‭f"Error: The file '‬‭{filename}‬‭' was not‬‭found."‬‭)‬
‭except‬‭Exception‬‭as‬‭e:‬
‭print(‬‭f"An unexpected error occurred:‬‭{e}‬‭"‬‭)‬

‭ To run this program, you would first create a sample file.‬


#
‭# For example, create a file named "story.txt" with the following content:‬
‭#‬
‭# This is a sample file.‬
‭# It contains sample text.‬
‭# Let's count how many times the letter 's' appears.‬
‭# Or the letter 'a'.‬

‭if‬ ‭__name__ ==‬‭"__main__"‬‭:‬


‭# Create a dummy file for testing if it doesn't‬‭exist‬
‭try‬‭:‬
‭with‬‭open‬‭(‬‭"story.txt"‬‭,‬‭"w"‬‭)‬‭as‬ ‭f:‬
f‭ .write(‬‭"This is a sample file.\n"‬‭)‬
‭f.write(‬‭"It contains sample text.\n"‬‭)‬
‭f.write(‬‭"Let's count how many times the‬‭letter 's' appears.\n"‬‭)‬
‭f.write(‬‭"Or the letter 'a'.\n"‬‭)‬
‭print(‬‭"Created a sample file 'story.txt' for‬‭demonstration."‬‭)‬
‭except‬ ‭IOError:‬
‭print(‬‭"Could not create 'story.txt'. Please‬‭create it manually to test fully."‬‭)‬

‭count_character_in_file()‬

‭# Example Interaction:‬
‭# Enter the filename (e.g., story.txt): story.txt‬
‭# Enter the character to count: s‬
‭# The character 's' appears 10 times in the file‬‭'story.txt'. (Counted manually: This(2) is(1) sample(1)‬
‭It(0) contains(1) sample(1) Let's(2) count(0) times(1) appears(1) = 10)‬

‭# Enter the filename (e.g., story.txt): story.txt‬


‭# Enter the character to count: a‬
‭# The character 'a' appears 8 times in the file‬‭'story.txt'. (Counted manually: a(1) sample(1)‬
‭contains(1) sample(1) many(1) a(1) appears(1) a(1) = 8)‬

‭# Enter the filename (e.g., story.txt): non_existent_file.txt‬


‭# Enter the character to count: e‬
‭# Error: The file 'non_existent_file.txt' was‬‭not found.‬

‭ lternative for reading content (simpler for smaller files):‬


A
‭Instead of line-by-line iteration, one could read the whole file content and use the string's‬
‭count() method.‬

‭Python‬

‭def‬‭count_character_in_file_alternative‬‭():‬
‭try‬‭:‬
‭ lename =‬‭input‬‭(‬‭"Enter the filename (e.g.,‬‭story.txt): "‬‭)‬
fi
‭char_to_count =‬‭input‬‭(‬‭"Enter the character‬‭to count: "‬‭)‬

‭if‬‭len‬‭(char_to_count) !=‬‭1‭:‬ ‬
‭print(‬‭"Error: Please enter a single character‬‭to count."‬‭)‬
‭return‬

‭with‬‭open‬‭(filename,‬‭'r'‬‭)‬‭as‬ ‭file:‬
‭ ontent = file.read()‬‭# Read entire file‬‭content into a string‬
c
‭count = content.count(char_to_count)‬‭#‬‭Use string's count() method‬

‭print(‬‭f"The character '‬‭{char_to_count}‬‭' appears‬‭{count}‬‭times in the file '‬‭{filename}‬‭' (using‬


‭alternative method)."‬‭)‬

‭except‬‭FileNotFoundError:‬
‭print(‬‭f"Error: The file '‬‭{filename}‬‭' was not‬‭found."‬‭)‬
‭except‬‭Exception‬‭as‬‭e:‬
‭print(‬‭f"An unexpected error occurred:‬‭{e}‬‭"‬‭)‬

‭ if __name__ == "__main__":‬
#
‭# # (Assuming story.txt is already created from the previous example)‬
‭# print("\nRunning alternative method:")‬
‭# count_character_in_file_alternative()‬

‭ he first method (iterating line by line, then character by character) is more‬


T
‭memory-efficient for very large files, as it doesn't load the entire file into memory at‬
‭once. The second method (using file.read().count()) is more concise for smaller files.‬
‭Given the phrasing, either should be acceptable, but the line-by-line approach is‬
‭generally safer.‬

(‭ b) What are files? Why do we need them? What are different access modes in‬
‭which you can open file?‬‭(6)‬

‭ hat are Files?‬


W
‭In computing, a file is a collection of related data stored on a persistent storage medium, such‬
‭as a hard disk, SSD, USB drive, or optical disc. Files are identified by a filename and typically‬
‭have an extension that indicates the type of data they contain (e.g., .txt for text files, .jpg for‬
‭images, .py for Python scripts).‬
‭ rom a user's perspective, a file can be a document, a picture, a song, a video, a‬
F
‭program, or any other organized block of information. From the operating system's‬
‭perspective, a file is a sequence of bytes.‬

‭ hy do we need Files?‬
W
‭Files are essential for several reasons:‬
‭1.‬ ‭Persistent Storage:‬‭Programs and data in a computer's‬‭main memory (RAM) are‬
v‭ olatile, meaning they are lost when the power is turned off. Files provide a way to‬
‭store data‬‭persistently‬‭so that it can be accessed‬‭later, even after the computer‬
‭is rebooted or the program that created it has finished executing.‬
‭2.‬ ‭Data Sharing:‬‭Files allow data to be easily shared‬‭between different programs,‬
‭different users, and even different computer systems.‬
‭3.‬ ‭Large Data Handling:‬‭Files can store vast amounts‬‭of data, much larger than‬
‭what can typically fit into the main memory at one time. Programs can read from‬
‭and write to files in chunks, processing large datasets efficiently.‬
‭4.‬ ‭Input/Output Operations:‬‭Files serve as a standard‬‭mechanism for programs to‬
‭receive input (e.g., configuration settings, user data) and produce output (e.g.,‬
‭reports, logs, processed data).‬
‭5.‬ ‭Organization:‬‭Files help organize information into‬‭manageable units, often‬
‭structured within a hierarchical directory (folder) system.‬
‭6.‬ ‭Backup and Recovery:‬‭Storing data in files allows‬‭for backup and recovery‬
‭procedures to protect against data loss.‬

‭ ifferent Access Modes in Which You Can Open a File:‬


D
‭When opening a file in Python using the open() function, you specify an access mode that‬
‭determines how the file can be interacted with (read, write, append, etc.) and whether it's‬
‭treated as a text file or a binary file.‬
‭The mode is a string argument passed to open(). Key modes include:‬
‭●‬ ‭'r' (Read Only):‬
‭○‬ ‭Opens the file for reading.‬
‭○‬ ‭This is the‬‭default mode‬‭if none is specified.‬
‭○‬ ‭The file pointer is placed at the beginning of the file.‬
‭○‬ ‭Raises a FileNotFoundError if the file does not exist.‬
‭●‬ ‭'w' (Write Only):‬
‭○‬ ‭Opens the file for writing.‬
‭○‬ ‭If the file exists, its‬‭content is truncated‬‭(emptied)‬‭before writing.‬
‭○‬ ‭If the file does not exist, it‬‭creates a new file‬‭.‬
‭○‬ ‭The file pointer is at the beginning of the file.‬
‭●‬ ‭'a' (Append Only):‬
‭○‬ ‭Opens the file for appending.‬
‭○‬ ‭If the file exists, the file pointer is placed at the‬‭end of the file‬‭. New data is‬
‭ ritten after the existing content.‬
w
‭○‬ ‭If the file does not exist, it‬‭creates a new file‬‭.‬
‭ ‬ ‭'r+' (Read and Write):‬

‭○‬ ‭Opens the file for both reading and writing.‬
‭○‬ ‭The file pointer is placed at the beginning of the file.‬
‭○‬ ‭Raises a‬‭FileNotFoundError‬ ‭if the file does not exist.‬‭12‬
‭○‬ ‭Can overwrite existing content or write new content depending on the file‬
‭pointer's position.‬
‭●‬ ‭'w+' (Write and Read):‬
‭○‬ ‭Opens the file for both writing and reading.‬
‭○‬ ‭If the file exists, its‬‭content is truncated‬‭.‬
‭○‬ ‭If the file does not exist, it‬‭creates a new file‬‭.‬
‭○‬ ‭After writing, you might need to reposition the file pointer (e.g., using seek(0))‬
‭to read the content.‬
‭●‬ ‭'a+' (Append and Read):‬
‭○‬ ‭Opens the file for both appending and reading.‬
‭○‬ ‭If the file exists, the file pointer is initially at the‬‭end of the file‬‭for writing.‬
‭○‬ ‭If the file does not exist, it‬‭creates a new file‬‭.‬
‭○‬ ‭To read the file's content (including what was appended), you typically need‬
‭to reposition the file pointer (e.g., seek(0)).‬

‭Additionally, these modes can be combined with:‬


‭●‬ ‭'b' (Binary Mode):‬
‭○‬ ‭Appended to any of the above modes (e.g., 'rb', 'wb', 'ab', 'r+b', 'w+b', 'a+b').‬
‭○‬ ‭Opens the file in binary mode, used for handling non-text files like images,‬
‭ udio, executable files, etc. Data is read and written as bytes objects.‬
a
‭ ‬ ‭'t' (Text Mode):‬

‭○‬ ‭Appended to any of the above modes (e.g., 'rt', 'wt').‬
‭○‬ ‭This is the‬‭default‬‭if 'b' is not specified.‬
‭○‬ ‭Opens the file in text mode. Data is read and written as strings. Handles‬
‭ latform-specific line endings automatically and may perform‬
p
‭encoding/decoding.‬

‭Example Usage:‬

‭Python‬

‭ Reading a text file (default mode is 'rt' or 'r')‬


#
‭# with open("myfile.txt", "r") as f:‬
‭# content = f.read()‬

‭ Writing to a text file (truncates if exists, creates if not)‬


#
‭# with open("output.txt", "w") as f:‬
‭# f.write("Hello, Python!")‬

‭ Appending to a text file‬


#
‭# with open("log.txt", "a") as f:‬
‭# f.write("New log entry.\n")‬

‭ Reading a binary file (e.g., an image)‬


#
‭# with open("image.jpg", "rb") as f:‬
‭# binary_data = f.read()‬

‭ Writing to a binary file‬


#
‭# with open("data.bin", "wb") as f:‬
‭# f.write(b'\x00\x01\x02\x03')‬

‭ sing the with statement (context manager) is highly recommended as it ensures the‬
U
‭file is properly closed automatically, even if errors occur.‬

‭ 7 (a) What is directory access? How do you walk through a directory tree?‬
Q
‭Write a python program to merge two files and then change the content of this‬
‭file to upper case letters.‬‭(6)‬

‭ hat is Directory Access?‬


W
‭Directory access refers to the ability of a program to interact with the file system's directory‬
‭structure (also known as folders). This includes operations such as:‬
‭●‬ ‭Listing the contents of a directory (files and subdirectories).‬
‭●‬ ‭Creating new directories.‬
‭●‬ ‭Deleting directories (if empty or recursively).‬
‭●‬ ‭Renaming directories.‬
‭●‬ ‭Checking if a path exists and whether it's a file or a directory.‬
‭●‬ ‭Getting or changing the current working directory.‬
‭●‬ ‭Navigating through the directory hierarchy.‬

I‭n Python, the os module provides most of the functions for directory and file system‬
‭operations.‬

‭ ow do you walk through a directory tree?‬


H
‭Walking through a directory tree means systematically visiting each directory and‬
‭subdirectory within a given starting directory, and often processing the files within them.‬
‭ he primary way to do this in Python is using the‬‭os.walk(top, topdown=True,‬
T
‭onerror=None, followlinks=False)‬‭function from the‬‭os module.‬
‭●‬ ‭os.walk() generates the file names in a directory tree by walking the tree either‬
t‭ op-down or bottom-up.‬
‭ ‬ ‭For each directory in the tree rooted at directory top (including top itself), it yields‬

‭a 3-tuple:‬
‭(dirpath, dirnames, filenames)‬
‭○‬ ‭dirpath: A string, the path to the current directory.‬
‭○‬ ‭dirnames: A list of strings, the names of the subdirectories in dirpath‬
‭(excluding '.' and '..').‬
‭○‬ ‭filenames: A list of strings, the names of the non-directory files in dirpath.‬
‭●‬ ‭topdown=True (default):‬‭The generator yields the tuple‬‭for a directory before its‬
‭subdirectories. You can modify dirnames in place (e.g., to prune the search) if‬
‭topdown is True.‬
‭●‬ ‭topdown=False:‬‭The generator yields the tuple for‬‭a directory after its‬
‭subdirectories (bottom-up).‬

‭Example of os.walk():‬

‭Python‬

‭import‬ ‭os‬
‭def‬‭list_directory_tree‬‭(start_path):‬
‭print(‬‭f"Walking directory tree starting from:‬‭{start_path}‬‭\n"‬‭)‬
‭for‬ ‭dirpath, dirnames, filenames‬‭in‬ ‭os.walk(start_path):‬
‭print(‬‭f"Current Directory Path:‬‭{dirpath}‬‭"‬‭)‬
‭print(‬‭f"Subdirectories:‬‭{dirnames}‬‭"‬‭)‬
‭print(‬‭f"Files:‬‭{filenames}‬‭"‬‭)‬
‭print(‬‭"-"‬ ‭*‬‭30‬‭)‬
‭# You can process files here, for example:‬
‭# for filename in filenames:‬
‭# print(os.path.join(dirpath, filename))‬

‭ Example usage (be careful with the path, choose a small directory for testing)‬
#
‭# list_directory_tree(".") # Walks the current directory‬

‭ ython program to merge two files and then change the content of this file to‬
P
‭upper case letters:‬

‭This program will:‬


‭1.‬ ‭Take names of two input files and one output/merged file from the user (or define‬
t‭ hem).‬
‭2.‬ ‭Read the content of the first input file.‬
‭3.‬ ‭Read the content of the second input file.‬
‭4.‬ ‭Write the content of the first file to the output file.‬
‭5.‬ ‭Append the content of the second file to the output file.‬
‭6.‬ ‭Re-open the output file (or read its content if kept in memory), convert its entire‬
‭content to uppercase.‬
‭7.‬ ‭Write the uppercase content back to the output file, overwriting its previous‬
‭merged content.‬

‭Python‬

‭import‬ ‭os‬

‭def‬‭merge_and_uppercase_files‬‭(file1_path, file2_path,‬‭merged_file_path):‬
‭"""‬
‭ erges the content of two files into a third file,‬
M
‭and then converts the entire content of the merged file to uppercase.‬
‭"""‬
‭try‬‭:‬
‭# Step 1 & 2: Read content from file1 and‬‭file2‬
‭content1 =‬‭""‬
‭with‬‭open‬‭(file1_path,‬‭'r'‬‭)‬‭as‬ ‭f1:‬
‭content1 = f1.read()‬
‭print(‬‭f"Content of '‬‭{file1_path}‬‭':\n‬‭{content1}‬‭"‭)‬ ‬

‭content2 =‬‭""‬
‭with‬‭open‬‭(file2_path,‬‭'r'‬‭)‬‭as‬ ‭f2:‬
‭content2 = f2.read()‬
‭print(‬‭f"Content of '‬‭{file2_path}‬‭':\n‬‭{content2}‬‭"‬‭)‬

‭# Step 3, 4 & 5: Merge content into the new‬‭file‬


‭# Open in 'w' mode to create/overwrite merged_file‬
‭with‬‭open‬‭(merged_file_path,‬‭'w'‬‭)‬‭as‬ ‭merged_file:‬
‭merged_file.write(content1)‬
‭# Add a newline between contents if desired,‬‭and if content1 doesn't end with one‬
‭if‬ ‭content1‬‭and‬‭not‬ ‭content1.endswith(‬‭'\n'‬‭):‬
‭ erged_file.write(‬‭'\n'‬‭)‬
m
‭merged_file.write(content2)‬
‭print(‬‭f"\nFiles '‬‭{file1_path}‬‭' and '‬‭{file2_path}‬‭'‬‭merged into '‬‭{merged_file_path}‬‭'."‬‭)‬

‭# Step 6: Read the merged content‬


‭merged_content =‬‭""‬
‭with‬‭open‬‭(merged_file_path,‬‭'r'‬‭)‬‭as‬ ‭mf_read:‬
‭merged_content = mf_read.read()‬
‭print(‬‭f"\nContent of '‬‭{merged_file_path}‬‭'‬‭before uppercase conversion:\n‬‭{merged_content}‬‭"‭)‬ ‬

‭# Step 7: Convert to uppercase and write back‬


‭uppercase_content = merged_content.upper()‬
‭with‬‭open‬‭(merged_file_path,‬‭'w'‬‭)‬‭as‬ ‭mf_write_upper:‬‭# Re-open in 'w' to overwrite‬
‭mf_write_upper.write(uppercase_content)‬

‭ rint(‬‭f"\nContent of '‬‭{merged_file_path}‬‭'‬‭successfully converted to uppercase."‬‭)‬


p
‭print(‬‭f"Final content of '‬‭{merged_file_path}‬‭':\n‬‭{uppercase_content}‬‭"‭)‬ ‬
‭except‬ ‭FileNotFoundError:‬
‭print(‬‭"Error: One or both input files not‬‭found."‬‭)‬
‭except‬‭IOError‬‭as‬‭e:‬
‭print(‬‭f"An I/O error occurred:‬‭{e}‬‭"‬‭)‬
‭except‬‭Exception‬‭as‬‭e:‬
‭print(‬‭f"An unexpected error occurred:‬‭{e}‬‭"‬‭)‬

‭if‬ ‭__name__ ==‬‭"__main__"‬‭:‬


‭# Create dummy input files for demonstration‬
‭ le_a_path =‬‭"fileA.txt"‬
fi
‭file_b_path =‬‭"fileB.txt"‬
‭merged_output_path =‬‭"merged_uppercase.txt"‬

‭with‬‭open‬‭(file_a_path,‬‭"w"‬‭)‬‭as‬ ‭f:‬
f‭ .write(‬‭"This is the first file.\n"‬‭)‬
‭f.write(‬‭"It has some lowercase text.\n"‬‭)‬

‭with‬‭open‬‭(file_b_path,‬‭"w"‬‭)‬‭as‬ ‭f:‬
f‭ .write(‬‭"This is the second file.\n"‬‭)‬
‭f.write(‬‭"More lowercase content here.\n"‬‭)‬

‭print(‬‭f"Created dummy files: '‬‭{file_a_path}‬‭' and‬‭'‬‭{file_b_path}‬‭'"‬‭)‬

‭merge_and_uppercase_files(file_a_path, file_b_path, merged_output_path)‬

‭# Clean up dummy files (optional)‬


‭# os.remove(file_a_path)‬
‭# os.remove(file_b_path)‬
‭# os.remove(merged_output_path)‬
‭# print("\nCleaned up dummy files.")‬

‭Output of the program:‬

‭ reated dummy files: 'fileA.txt' and 'fileB.txt'‬


C
‭Content of 'fileA.txt':‬
‭This is the first file.‬
‭It has some lowercase text.‬

‭ ontent of 'fileB.txt':‬
C
‭This is the second file.‬
‭More lowercase content here.‬

‭Files 'fileA.txt' and 'fileB.txt' merged into 'merged_uppercase.txt'.‬

‭ ontent of 'merged_uppercase.txt' before uppercase conversion:‬


C
‭This is the first file.‬
‭It has some lowercase text.‬
‭This is the second file.‬
‭More lowercase content here.‬

‭ ontent of 'merged_uppercase.txt' successfully converted to uppercase.‬


C
‭Final content of 'merged_uppercase.txt':‬
‭THIS IS THE FIRST FILE.‬
‭IT HAS SOME LOWERCASE TEXT.‬
‭THIS IS THE SECOND FILE.‬
‭MORE LOWERCASE CONTENT HERE.‬

(‭ b) Connect with ASC: Click Here‬


‭This line appears to be a footer or unrelated text in the PDF and not part of the question itself.‬

‭UNIT-IV‬
‭ he questions in Unit IV delve into web scraping. For these, I'll describe the concepts‬
T
‭and general approaches, and mention relevant Python libraries. Full, runnable code‬
‭for web scraping can be complex and depend on the target website's structure and‬
‭terms of service.‬

‭ 8 (a) What is the best framework for web scraping with python? How do you‬
Q
‭extract data from website?‬‭(6)‬
‭Best Framework for Web Scraping with Python:‬

‭ here isn't a single "best" framework as the choice depends on the complexity of the‬
T
‭task, the scale of scraping, and personal/team preference. However, some of the most‬
‭popular and powerful options include:‬
‭1.‬ ‭Scrapy:‬
‭○‬ ‭Description:‬‭An open-source and collaborative framework‬‭for extracting the‬
‭ ata you need from websites. It's fast, powerful, and extensible.‬
d
‭○‬ ‭Pros:‬
‭■‬ ‭Asynchronous:‬‭Built on Twisted, allowing for concurrent‬‭requests, making‬
‭it very fast for large-scale scraping.‬
‭■‬ ‭Full-fledged Framework:‬‭Provides a complete architecture‬‭including‬
‭spiders (bots that crawl sites), item pipelines (for processing scraped‬
‭data), middlewares (for custom request/response handling), and more.‬
‭■‬ ‭Extensible:‬‭Easily extendable with custom components‬‭and plugins.‬
‭■‬ ‭Robust:‬‭Handles many common scraping challenges like‬‭managing‬
‭cookies, sessions, user-agents, and following redirects.‬
‭■‬ ‭Data Export:‬‭Built-in support for exporting data in‬‭various formats (JSON,‬
‭CSV, XML).‬
‭■‬ ‭Good for complex, ongoing scraping projects.‬
‭○‬ ‭Cons:‬
‭■‬ ‭Steeper learning curve compared to simpler libraries.‬
‭■‬ ‭Might be overkill for very simple, one-off scraping tasks.‬
‭ .‬ ‭Beautiful Soup (with requests):‬
2
‭○‬ ‭Description:‬‭Beautiful Soup is a library for parsing‬‭HTML and XML‬
‭documents. It's not a full framework itself but is often used in conjunction with‬
‭the requests library (for making HTTP requests).‬
‭○‬ ‭Pros:‬
‭■‬ ‭Easy to Learn and Use:‬‭Very Pythonic and has a gentle‬‭learning curve,‬
‭great for beginners.‬
‭■‬ ‭Excellent for Parsing:‬‭Handles malformed HTML gracefully‬‭and provides‬
‭convenient methods for navigating, searching, and modifying the parse‬
‭tree.‬
‭■‬ ‭Flexible:‬‭Works with different parsers like html.parser‬‭(built-in), lxml, and‬
‭html5lib.‬
‭■‬ ‭Good for smaller projects, quick scripts, and when you need to parse‬
‭HTML/XML content retrieved by other means.‬
‭○‬ ‭Cons:‬
‭■‬ ‭Not a complete framework:‬‭It only handles parsing.‬‭You need requests‬
f‭ or fetching web pages and manage other aspects like concurrency,‬
‭politeness (rate limiting, robots.txt) manually or with other tools.‬
‭■‬ ‭Slower for large-scale scraping compared to asynchronous frameworks‬
‭like Scrapy if not parallelized carefully.‬
‭ .‬ ‭Selenium:‬
3
‭○‬ ‭Description:‬‭Primarily a browser automation tool,‬‭but widely used for web‬
‭scraping, especially for dynamic websites that heavily rely on JavaScript to‬
‭load content.‬
‭○‬ ‭Pros:‬
‭■‬ ‭Handles JavaScript:‬‭Can interact with web pages just‬‭like a human user‬
‭(clicking buttons, filling forms, scrolling), allowing it to scrape content‬
‭loaded by JavaScript.‬
‭■‬ ‭Real Browser Interaction:‬‭Automates actual web browsers‬‭(Chrome,‬
‭Firefox, etc.) via WebDriver.‬
‭■‬ ‭Good for sites where content is not present in the initial HTML source.‬
‭○‬ ‭Cons:‬
‭■‬ ‭Slower and Resource-Intensive:‬‭Since it runs a full‬‭browser, it's slower‬
‭and uses more memory/CPU than direct HTTP request-based scrapers.‬
‭■‬ ‭Can be more complex to set up (requires WebDriver).‬
‭■‬ ‭May be blocked more easily if not configured to mimic human behavior.‬
‭4.‬ ‭Requests-HTML:‬
‭○‬ ‭Description:‬‭Created by the author of the requests‬‭library, it aims to make‬
‭parsing HTML (and scraping) as simple as possible. It provides requests-like‬
‭functionality with integrated parsing capabilities (using PyQuery and lxml) and‬
‭basic JavaScript support (using Chromium via Pyppeteer).‬
‭○‬ ‭Pros:‬
‭■‬ ‭Simpler Syntax:‬‭Aims for a very user-friendly API.‬
‭■‬ ‭JavaScript Support (Basic):‬‭Can render JavaScript-driven‬‭sites to a‬
‭certain extent.‬
‭■‬ ‭Combines HTTP requests and parsing in one library.‬
‭○‬ ‭Cons:‬
‭■‬ ‭Less mature and feature-rich than Scrapy or Beautiful Soup + Selenium‬
‭for very complex tasks.‬
‭■‬ ‭JavaScript rendering might not be as robust as Selenium for all cases.‬

‭Which is "best"?‬
‭●‬ ‭For‬‭large-scale, complex, ongoing scraping projects‬‭needing high‬
‭ erformance:‬‭Scrapy‬‭is often the preferred choice.‬
p
‭ ‬ ‭For‬‭smaller tasks, parsing static HTML, quick scripts,‬‭or learning‬‭:‬‭Beautiful‬

‭ oup with requests‬‭is excellent.‬
S
‭ ‬ ‭For‬‭dynamic websites heavily reliant on JavaScript‬‭where content is not in the‬

‭initial HTML:‬‭Selenium‬‭(or sometimes Requests-HTML)‬‭is necessary.‬

‭How do you extract data from a website?‬

‭ he general process of extracting data from a website (web scraping) involves these‬
T
‭steps:‬
‭1.‬ ‭Inspect the Website:‬
‭○‬ ‭Understand the Structure:‬‭Use browser developer tools‬‭(e.g., "Inspect‬
‭ lement" in Chrome/Firefox) to examine the HTML structure of the page(s)‬
E
‭you want to scrape. Identify the HTML tags, classes, IDs, and attributes that‬
‭contain the data you need.‬
‭○‬ ‭Check robots.txt:‬‭Visit www.example.com/robots.txt to see if the website has‬
‭rules for web crawlers. Respect these rules to avoid being blocked and for‬
‭ethical scraping.‬
‭○‬ ‭Identify Data Location:‬‭Determine if the data is present‬‭in the initial HTML‬
‭source or loaded dynamically via JavaScript (AJAX calls).‬
‭ .‬ ‭Fetch the Web Page Content:‬
2
‭○‬ ‭Use an HTTP client library like requests (most common for static content) or a‬
‭browser automation tool like Selenium (for dynamic content) to send an HTTP‬
‭GET (or POST) request to the website's URL.‬
‭○‬ ‭The server responds with the page content, usually HTML.‬
‭ ython‬
P
‭# Using requests‬
‭# import requests‬
‭# try:‬
‭# response = requests.get("http://example.com")‬
‭# response.raise_for_status() # Raise an exception for HTTP errors (4XX or 5XX)‬
‭# html_content = response.text‬
‭# except requests.exceptions.RequestException as e:‬
‭# print(f"Error fetching page: {e}")‬
‭# html_content = None‬

‭3.‬ ‭Parse the HTML Content:‬


‭○‬ ‭Once you have the HTML, use a parsing library like‬‭Beautiful Soup‬‭or‬‭lxml‬
(‭ Scrapy has its own selectors built on lxml) to convert the raw HTML string‬
‭into a structured object (a parse tree) that can be easily navigated and‬
‭searched.‬
‭ ython‬
P
‭# Using Beautiful Soup‬
‭# from bs4 import BeautifulSoup‬
‭ if html_content:‬
#
‭# soup = BeautifulSoup(html_content, 'html.parser') # or 'lxml'‬

‭4.‬ ‭Locate and Extract Data Elements:‬


‭○‬ ‭Use the methods provided by your parsing library to find the HTML elements‬
‭containing the desired data. This is typically done using:‬
‭■‬ ‭CSS Selectors:‬‭(e.g., soup.select('div.classname p'))‬‭- very powerful and‬
‭common.‬
‭■‬ ‭XPath Expressions:‬‭(e.g., Scrapy's‬
‭response.xpath('//div[@class="classname"]/p/text()')) - another powerful‬
‭way to select nodes.‬
‭■‬ ‭Tag names, attributes, IDs: (e.g., soup.find_all('h2'),‬
‭soup.find(id='main-title')).‬
‭ ‬ ‭Once elements are located, extract their text content, attributes (like href‬

‭from an <a> tag), or other relevant information.‬
‭ ython‬
P
‭# Using Beautiful Soup to find all headings‬
‭# if 'soup' in locals():‬
‭# headings = soup.find_all('h1')‬
‭# for heading in headings:‬
‭# print(heading.text.strip()) # .text gets the text content, .strip() removes whitespace‬

‭ Example: extracting links‬


#
‭# links = soup.select('a.some-class')‬
‭# for link in links:‬
‭# print(f"Text: {link.text.strip()}, URL:‬‭{link.get('href')}")‬

‭5.‬ ‭Store the Data:‬


‭○‬ ‭Save the extracted data in a structured format, such as:‬
‭■‬ ‭CSV files‬
‭■‬ ‭JSON files‬
‭■‬ ‭Databases (SQL, NoSQL)‬
‭■‬ ‭Spreadsheets‬
‭6.‬ ‭Handle Challenges (Advanced):‬
‭○‬ ‭Pagination:‬‭If data spans multiple pages, implement‬‭logic to navigate to the‬
‭ ext page and repeat the scraping process.‬
n
‭ ‬ ‭JavaScript-loaded Content:‬‭Use Selenium or tools like‬‭Playwright or‬

‭Puppeteer (via Pyppeteer for Requests-HTML).‬
‭○‬ ‭Rate Limiting/Blocks:‬‭Implement delays between requests,‬‭use proxies,‬
‭rotate user agents, and respect robots.txt.‬
‭○‬ ‭Error Handling:‬‭Add robust error handling for network‬‭issues, changes in‬
‭website structure, etc.‬
‭○‬ ‭Login/Authentication:‬‭If data is behind a login, your scraper might need to‬
‭handle login forms (e.g., using requests.Session or Selenium).‬

‭Ethical Considerations:‬
‭●‬ ‭Always check a website's robots.txt and Terms of Service.‬
‭●‬ ‭Don't overload the website's server with too many rapid requests.‬
‭●‬ ‭Identify your scraper with a user-agent if possible.‬
‭●‬ ‭Scrape only publicly available data.‬
‭●‬ ‭Be mindful of privacy and copyright.‬

‭(b) How to scrape multiple pages of website using Python.‬‭(6.5)‬

‭ craping multiple pages of a website, often referred to as‬‭crawling‬‭or handling‬


S
‭pagination‬‭, is a common requirement in web scraping.‬‭The general approach‬
‭involves:‬
‭1.‬ ‭Identify the Pagination Mechanism:‬
‭○‬ ‭"Next" Button Links:‬‭Many websites have "Next," "More,"‬‭or page number‬
l‭inks. You need to find the URL pattern or the specific link that leads to the‬
‭subsequent page.‬
‭○‬ ‭URL Parameters:‬‭Some sites use URL parameters for‬‭pagination (e.g.,‬
‭example.com/items?page=1, example.com/items?page=2). You can increment‬
‭the page parameter.‬
‭○‬ ‭Infinite Scroll:‬‭Content loads dynamically as you‬‭scroll down. This usually‬
‭involves monitoring network requests (XHR/Fetch) in browser developer tools‬
‭to find the API endpoint being called or using tools like Selenium to simulate‬
‭scrolling and wait for new content to load.‬
‭○‬ ‭JavaScript Calls:‬‭Pagination might be handled by JavaScript‬‭functions‬
‭without changing the main URL directly. Selenium or analyzing XHR requests‬
‭would be needed.‬
‭ .‬ ‭Looping and Fetching:‬
2
‭○‬ ‭Start with the URL of the first page.‬
‭○‬ ‭Inside a loop:‬
‭■‬ ‭Fetch the current page's content.‬
‭■‬ ‭Parse the content and extract the desired data.‬
‭■‬ ‭Find the link/URL for the next page.‬
‭■‬ ‭If a "next page" link exists and is valid (e.g., not a duplicate of the current‬
‭page, or doesn't lead to an error page), update the current URL to this‬
‭new URL and continue the loop.‬
‭■‬ ‭If no "next page" link is found, or a predefined limit is reached, exit the‬
l‭oop.‬
‭ .‬ ‭Managing State:‬
3
‭○‬ ‭Keep track of visited URLs to avoid infinite loops if pagination links are not‬
‭strictly linear or can lead back to already processed pages. A set is efficient‬
‭for this.‬
‭4.‬ ‭Delay Between Requests:‬
‭○‬ ‭It's crucial to add delays (e.g., time.sleep(seconds)) between requests to‬
‭avoid overwhelming the server and getting your IP address blocked.‬

‭ xample Strategy using requests and BeautifulSoup (for "Next" button type‬
E
‭pagination):‬

‭Python‬

‭import‬ ‭requests‬
‭from‬ ‭bs4‬‭import‬ ‭BeautifulSoup‬
‭import‬ ‭time‬
‭import‬ ‭urllib.parse‬‭# For joining relative URLs‬

‭def‬‭scrape_multiple_pages_example‬‭(base_url, start_page_relative_path):‬
"‭ ""‬
‭Example of scraping multiple pages by following a 'Next' link.‬
‭This is a conceptual example and needs adaptation for specific sites.‬
‭"""‬
‭ urrent_url = urllib.parse.urljoin(base_url, start_page_relative_path)‬
c
‭scraped_data_all_pages = []‬
‭visited_urls =‬‭set‬‭()‬
‭page_count =‬‭0‬
‭max_pages =‬‭5‬‭# Limit the number of pages to scrape‬‭for this example‬

‭while‬ ‭current_url‬‭and‬ ‭current_url‬‭not‬‭in‬ ‭visited_urls‬‭and‬ ‭page_count < max_pages:‬


‭ rint(‬‭f"Scraping page:‬‭{current_url}‬‭"‬‭)‬
p
‭visited_urls.add(current_url)‬
‭page_count +=‬‭1‬

‭try‬‭:‬
r‭ esponse = requests.get(current_url, headers={‬‭'User-Agent'‬‭:‬‭'MySimpleScraper/1.0'‬‭})‬
‭response.raise_for_status()‬‭# Check for‬‭HTTP errors‬
‭soup = BeautifulSoup(response.text,‬‭'html.parser'‬‭)‬

‭# --- Extract data from the current page‬‭---‬


‭# This part is specific to the website's‬‭structure‬
‭# Example: Find all items with a specific‬‭class‬
‭items_on_page = soup.select(‬‭'.item-class-name'‬‭)‬‭# Replace with actual selector‬
‭for‬ ‭item‬‭in‬ ‭items_on_page:‬
‭title_element = item.find(‬‭'h2'‬‭)‬‭#‬‭Replace with actual selector for title‬
‭title = title_element.text.strip()‬‭if‬ ‭title_element‬‭else‬‭"No title"‬
‭scraped_data_all_pages.append({‬‭'title'‬‭:‬‭title,‬‭'source_page'‬‭: current_url})‬
‭print(‬‭f" Extracted:‬‭{title}‬‭"‬‭)‬

‭# --- Find the link to the next page ---‬


‭# This logic is highly dependent on the‬‭website's HTML structure‬
‭next_page_link_element = soup.find(‬‭'a'‬‭,‬‭class_=‬‭'next-page-button'‬‭)‬‭# Example‬
‭selector for a 'Next' button‬
‭# Or: next_page_link_element = soup.select_one('a.pagination-next')‬
‭# Or: next_page_link_element = soup.find('a',‬‭string='Next Page')‬

‭if‬ ‭next_page_link_element‬‭and‬‭'href'‬‭in‬ ‭next_page_link_element.attrs:‬


‭next_page_relative_url = next_page_link_element[‬‭'href'‬‭]‬
‭# Construct the absolute URL for the‬‭next page‬
‭current_url = urllib.parse.urljoin(base_url, next_page_relative_url)‬
‭else‬‭:‬
‭print(‬‭"No 'Next Page' link found or‬‭end of pagination."‬‭)‬
‭current_url =‬‭None‬‭# Stop the loop‬

‭# Be polite: wait before the next request‬


‭time.sleep(‬‭2‬‭)‬‭# Wait for 2 seconds‬

‭except‬ ‭requests.exceptions.RequestException‬‭as‬ ‭e:‬


‭ rint(‬‭f"Error fetching or parsing page‬‭{current_url}‬‭:‬‭{e}‬‭"‬‭)‬
p
‭current_url =‬‭None‬‭# Stop on error‬
‭except‬ ‭Exception‬‭as‬ ‭e:‬
‭print(‬‭f"An unexpected error occurred on‬‭page‬‭{current_url}‬‭:‬‭{e}‬‭"‭)‬ ‬
‭current_url =‬‭None‬‭# Stop on error‬
‭return‬ ‭scraped_data_all_pages‬

‭if‬ ‭__name__ ==‬‭"__main__"‬‭:‬


‭# This is a placeholder. You'd need a real website‬‭to test this.‬
‭# For example, if a blog had pages like:‬
‭# base_url = "http://exampleblog.com"‬
‭# start_path = "/archive?page=1"‬
‭# And a next button linking to "/archive?page=2",‬‭etc.‬

‭# Let's simulate with a known site that has pagination‬‭if possible, or a local setup.‬
‭# For demonstration, imagine a site where 'quotes.toscrape.com'‬‭uses relative links for next.‬

‭# Example with 'http://quotes.toscrape.com/' which‬‭has clear 'Next' links‬


‭# Note: Always check robots.txt for any site you‬‭scrape. quotes.toscrape.com allows scraping.‬

‭ rint(‬‭"--- Scraping Multiple Pages Example (quotes.toscrape.com)‬‭---"‬‭)‬


p
‭base_scrape_url =‬‭"http://quotes.toscrape.com"‬
‭start_scrape_path =‬‭"/"‬‭# The first page‬

‭ ll_quotes = []‬
a
‭current_scrape_url = urllib.parse.urljoin(base_scrape_url, start_scrape_path)‬
‭page_num =‬‭0‬
‭max_scrape_pages =‬‭3‬‭# Scrape a few pages for‬‭demo‬

‭while‬ ‭current_scrape_url‬‭and‬ ‭page_num < max_scrape_pages:‬


‭ age_num +=‬‭1‬
p
‭print(‬‭f"Fetching page:‬‭{current_scrape_url}‬‭"‭)‬ ‬
‭try‬‭:‬
‭res = requests.get(current_scrape_url, headers={‬‭'User-Agent'‬‭:‬
‭'MyDemoScraper/1.0'‬‭})‬
‭res.raise_for_status()‬
‭page_soup = BeautifulSoup(res.text,‬‭'html.parser'‬‭)‬

‭quotes_on_this_page = page_soup.select(‬‭'div.quote‬‭span.text'‬‭)‬
‭for‬ ‭q_element‬‭in‬ ‭quotes_on_this_page:‬
‭quote_text = q_element.get_text(strip=‬‭True‬‭)‬
‭all_quotes.append(quote_text)‬
‭print(‬‭f" - Quote:‬‭{quote_text[:‬‭50‬‭]}‬‭..."‬‭)‬‭# Print first 50 chars‬
‭# Find 'Next' button link‬
‭next_li = page_soup.select_one(‬‭'li.next‬‭a'‬‭)‬‭# Selector for the 'Next >' link‬
‭if‬ ‭next_li‬‭and‬ ‭next_li.get(‬‭'href'‬‭):‬
‭next_page_url_relative = next_li[‬‭'href'‬‭]‬
‭current_scrape_url = urllib.parse.urljoin(base_scrape_url,‬
‭next_page_url_relative)‬
‭else‬‭:‬
‭print(‬‭"No more 'Next' pages or max‬‭pages reached."‬‭)‬
‭current_scrape_url =‬‭None‬

‭time.sleep(‬‭1‬‭)‬‭# Politeness delay‬

‭except‬ ‭requests.exceptions.RequestException‬‭as‬ ‭e_req:‬


‭ rint(‬‭f"Request error for‬‭{current_scrape_url}‬‭:‬‭{e_req}‬‭"‬‭)‬
p
‭current_scrape_url =‬‭None‬
‭except‬ ‭Exception‬‭as‬ ‭e_gen:‬
‭print(‬‭f"General error processing‬‭{current_scrape_url}‬‭:‬‭{e_gen}‬‭"‭)‬ ‬
‭current_scrape_url =‬‭None‬

‭print(‬‭f"\n--- Total quotes scraped:‬‭{‭l‬en‬‭(all_quotes)}‬‭---"‬‭)‬


‭# for i, q in enumerate(all_quotes):‬
‭# print(f"{i+1}. {q}")‬

‭Key considerations for scraping multiple pages:‬


‭●‬ ‭Website Structure Variability:‬‭The way to find the‬‭"next page" link can differ‬
s‭ ignificantly between websites. You must inspect each target site.‬
‭●‬ ‭Robustness:‬‭Account for missing "next" links (end‬‭of pagination), temporary‬
‭network errors, and changes in website layout.‬
‭●‬ ‭robots.txt and Terms of Service:‬‭Always comply with‬‭these.‬
‭●‬ ‭Data Storage:‬‭Decide how you will store the data accumulated‬‭from multiple‬
‭pages.‬
‭●‬ ‭Scrapy Framework:‬‭For complex multi-page scraping‬‭(crawling), Scrapy handles‬
‭many of these details (like request scheduling, URL tracking, depth limits) more‬
‭elegantly with its Spider classes and Request objects that can yield further‬
‭requests.‬

‭Q9 (a) Write a program to download all images from a web page in a python.‬‭(6)‬
‭To download all images from a web page, you generally follow these steps:‬
‭1.‬ ‭Fetch the Web Page HTML:‬‭Use requests to get the HTML‬‭content of the page.‬
‭2.‬ ‭Parse HTML to Find Image Tags:‬‭Use BeautifulSoup to‬‭parse the HTML and find‬
‭ ll <img> tags.‬
a
‭3.‬ ‭Extract Image URLs:‬‭For each <img> tag, get the value‬‭of its src attribute. These‬
‭URLs might be absolute or relative.‬
‭4.‬ ‭Resolve Relative URLs:‬‭If an image URL is relative‬‭(e.g., /images/pic.jpg), convert‬
‭it to an absolute URL by joining it with the base URL of the web page.‬
‭5.‬ ‭Download Each Image:‬‭For each absolute image URL:‬
‭○‬ ‭Send another requests.get() request to the image URL, this time with‬
‭stream=True.‬
‭○‬ ‭Check if the response is successful and the content type indicates an image.‬
‭○‬ ‭Open a local file in binary write mode ('wb').‬
‭○‬ ‭Write the content of the image response to this file.‬
‭6.‬ ‭Handle Naming and Storage:‬‭Decide on a naming convention‬‭for downloaded‬
‭images and a directory to store them. Ensure you don't overwrite files if they have‬
‭common names (or handle this).‬

‭Important Considerations:‬
‭●‬ ‭robots.txt and Terms of Service:‬‭Crucial to check‬‭and respect. Downloading‬
‭ opyrighted images without permission can be illegal.‬
c
‭●‬ ‭User-Agent:‬‭Set a User-Agent header to identify your‬‭script.‬
‭●‬ ‭Error Handling:‬‭Implement for network issues, missing‬‭src attributes, non-image‬
‭URLs, etc.‬
‭●‬ ‭Directory Creation:‬‭Create a directory to save images‬‭if it doesn't exist.‬
‭●‬ ‭Image Formats:‬‭This method typically downloads images‬‭as they are. You might‬
‭need other libraries if you intend to process or convert them.‬
‭●‬ ‭Dynamic Loading (JavaScript):‬‭If images are loaded‬‭by JavaScript (e.g., in‬
‭carousels, or via lazy loading), requests + BeautifulSoup alone might not find‬
‭them. You'd need Selenium or a similar tool to render the page first.‬

‭Python‬

‭import‬ ‭requests‬
‭from‬ ‭bs4‬‭import‬ ‭BeautifulSoup‬
‭import‬ ‭os‬
‭import‬ ‭urllib.parse‬‭# For urljoin and other URL manipulations‬
‭from‬ ‭urllib.parse‬‭import‬ ‭urlparse‬

‭def‬‭download_images_from_url‬‭(page_url, save_directory=‬‭"downloaded_images"‬‭):‬
"‭ ""‬
‭Downloads all images from a given web page URL.‬

‭Args:‬
‭page_url (str): The URL of the web page to scrape images from.‬
‭save_directory (str): The directory to save the downloaded images.‬
‭"""‬
‭if‬‭not‬ ‭page_url.startswith((‬‭'http://'‬‭,‬‭'https://'‬‭)):‬
‭print(‬‭f"Invalid URL:‬‭{page_url}‬‭. Must start‬‭with http:// or https://"‬‭)‬
‭return‬

‭try‬‭:‬
‭# Create the save directory if it doesn't‬‭exist‬
‭if‬‭not‬ ‭os.path.exists(save_directory):‬
‭ s.makedirs(save_directory)‬
o
‭print(‬‭f"Created directory:‬‭{save_directory}‬‭"‭)‬ ‬

‭headers = {‬‭'User-Agent'‬‭:‬‭'MyImageDownloader/1.0‬‭(Python)'‬‭}‬

‭# 1. Fetch the web page HTML‬


‭ rint(‬‭f"Fetching page:‬‭{page_url}‬‭"‬‭)‬
p
‭response = requests.get(page_url, headers=headers, timeout=‬‭10‬‭)‬
‭response.raise_for_status()‬ ‭# Raise an exception‬‭for bad status codes‬

‭# 2. Parse HTML‬
‭soup = BeautifulSoup(response.text,‬‭'html.parser'‬‭)‬

‭# 3. Find all <img> tags‬


‭img_tags = soup.find_all(‬‭'img'‬‭)‬

‭if‬‭not‬ ‭img_tags:‬
‭print(‬‭"No <img> tags found on the page."‬‭)‬
‭return‬

‭print(‬‭f"Found‬‭{‭l‬en‬‭(img_tags)}‬‭image tags.‬‭Attempting to download..."‬‭)‬


‭downloaded_count =‬‭0‬

‭for‬ ‭img_tag‬‭in‬ ‭img_tags:‬


‭# 4. Extract image URLs (src attribute)‬
‭img_url_relative = img_tag.get(‬‭'src'‬‭)‬

‭if‬‭not‬ ‭img_url_relative:‬
‭# Sometimes image URL is in 'data-src'‬‭for lazy loading‬
‭img_url_relative = img_tag.get(‬‭'data-src'‬‭)‬

‭if‬‭not‬ ‭img_url_relative:‬
‭print(‬‭" - Found img tag with no src‬‭or data-src attribute. Skipping."‬‭)‬
‭continue‬

‭# Skip data URIs (embedded images) if‬‭any, as they are not separate files to download‬
‭if‬ ‭img_url_relative.startswith(‬‭'data:image'‬‭):‬
‭print(‬‭f" - Skipping data URI image:‬‭{img_url_relative[:‬‭60‬‭]}‬‭..."‬‭)‬
‭continue‬

‭# 5. Resolve relative URLs to absolute‬‭URLs‬


‭# urllib.parse.urljoin handles both relative‬‭and absolute img_url_relative well‬
‭img_url_absolute = urllib.parse.urljoin(page_url, img_url_relative)‬

‭print(‬‭f" - Found image URL:‬‭{img_url_absolute}‬‭"‬‭)‬

‭try‬‭:‬
‭# 6. Download each image‬
‭img_response = requests.get(img_url_absolute, headers=headers,‬
‭stream=‬‭True‬‭, timeout=‬‭10‬‭)‬
‭img_response.raise_for_status()‬

‭# Check content type (optional but‬‭good practice)‬


‭content_type = img_response.headers.get(‬‭'Content-Type'‬‭,‬‭''‬‭).lower()‬
‭if‬‭not‬ ‭content_type.startswith(‬‭'image/'‬‭):‬
‭print(‬‭f" - URL‬‭{img_url_absolute}‬‭does not appear to be an image (Content-Type:‬
‭{content_type}‬‭). Skipping."‬‭)‬
‭continue‬

‭# 7. Handle naming and storage‬


‭# Create a filename from the URL or use a counter‬
‭# For simplicity, extract the last‬‭part of the URL path as filename‬
‭ arsed_img_url = urlparse(img_url_absolute)‬
p
‭img_filename = os.path.basename(parsed_img_url.path)‬
‭if‬‭not‬ ‭img_filename:‬‭# If path is‬‭just '/' or empty‬
‭img_filename =‬‭f"image_‬‭{downloaded_count‬‭+‬‭1‬‭}‬‭.unknown"‬

‭# Sanitize filename (basic)‬


i‭mg_filename =‬‭""‬‭.join(c‬‭for‬ ‭c‬‭in‬ ‭img_filename‬‭if‬ ‭c.isalnum()‬‭or‬ ‭c‬‭in‬ ‭[‭'‬.'‬‭,‬‭'_'‬‭,‬
‭'-'‬‭]).strip()‬
‭if‬‭not‬ ‭img_filename:‬‭# If sanitization‬‭results in empty filename‬
‭img_filename =‬‭f"image_‬‭{downloaded_count‬‭+‬‭1‭}‬ ‬‭.jpg"‬‭# Default name‬

‭img_save_path = os.path.join(save_directory, img_filename)‬

‭# Avoid overwriting by adding a number‬‭if file exists‬


‭ ounter =‬‭1‬
c
‭base, ext = os.path.splitext(img_save_path)‬
‭while‬ ‭os.path.exists(img_save_path):‬
‭img_save_path =‬‭f"‬‭{base}‬‭_‭{‬ counter}{ext}‬‭"‬
‭counter +=‬‭1‬

‭with‬‭open‬‭(img_save_path,‬‭'wb'‬‭)‬‭as‬ ‭f_img:‬
‭for‬ ‭chunk‬‭in‬ ‭img_response.iter_content(chunk_size=‬‭8192‬‭):‬
f‭ _img.write(chunk)‬
‭print(‬‭f" Successfully downloaded‬‭and saved to:‬‭{img_save_path}‬‭"‭)‬ ‬
‭downloaded_count +=‬‭1‬
‭time.sleep(‬‭0.5‬‭)‬‭# Small delay‬

‭except‬ ‭requests.exceptions.RequestException‬‭as‬ ‭e_img:‬


‭print(‬‭f" Error downloading‬‭{img_url_absolute}‬‭:‬‭{e_img}‬‭"‬‭)‬
‭except‬ ‭IOError‬‭as‬ ‭e_io:‬
‭print(‬‭f" Error saving image‬‭{img_url_absolute}‬‭:‬‭{e_io}‬‭"‭)‬ ‬
‭except‬ ‭Exception‬‭as‬ ‭e_inner:‬
‭print(‬‭f" An unexpected error occurred‬‭for image‬‭{img_url_absolute}‬‭:‬‭{e_inner}‬‭"‬‭)‬

‭print(‬‭f"\nFinished. Downloaded‬‭{downloaded_count}‬‭images to '‬‭{save_directory}‬‭'."‬‭)‬


‭except‬ ‭requests.exceptions.RequestException‬‭as‬ ‭e:‬
‭print(‬‭f"Error fetching page‬‭{page_url}‬‭:‬‭{e}‬‭"‬‭)‬
‭except‬ ‭IOError‬‭as‬ ‭e_dir:‬
‭print(‬‭f"Directory creation error:‬‭{e_dir}‬‭"‭)‬ ‬
‭except‬ ‭Exception‬‭as‬ ‭e_outer:‬
‭print(‬‭f"An unexpected outer error occurred:‬‭{e_outer}‬‭"‬‭)‬

‭if‬ ‭__name__ ==‬‭"__main__"‬‭:‬


‭# Test with a site known for allowing scraping‬‭of its public content‬
‭# Example: 'http://books.toscrape.com/' which‬‭has book cover images.‬
‭# ALWAYS check robots.txt and terms of service‬‭of any website.‬
‭# books.toscrape.com has /robots.txt : User-agent:‬‭* Allow: /‬

‭test_url =‬‭"http://books.toscrape.com/"‬
‭# Or try a simpler page with a few images that‬‭you own or have permission for.‬
‭# For instance, create a local HTML file with‬‭<img> tags pointing to existing images.‬

‭# To test with a local HTML file:‬


‭# 1. Create 'my_test_page.html':‬
‭# <html><body>‬
‭# <img src="https://www.python.org/static/community_logos/python-logo-master-v3-TM.png">‬
‭ <img‬
#
‭src="https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png">‬
‭# # # </body></html>‬
‭# 2. You might need a simple HTTP server to serve‬‭this file if it has relative paths,‬
‭# or use `file:///path/to/your/my_test_page.html`‬‭(requests might need adapter for file://)‬
‭# It's easier to test with a live HTTP URL.‬

‭print(‬‭"--- Image Downloader Program ---"‬‭)‬


‭# target_web_page_url = input("Enter the URL of‬‭the web page to download images from: ")‬
‭target_web_page_url = test_url‬‭# Using books.toscrape.com‬‭for demo‬

‭if‬ ‭target_web_page_url:‬
‭download_images_from_url(target_web_page_url,‬‭"scraped_book_covers"‬‭)‬
‭else‬‭:‬
‭print(‬‭"No URL provided."‬‭)‬
‭ his program provides a foundation. Real-world scenarios might require handling‬
T
‭more complex HTML structures, JavaScript rendering (using Selenium if images are‬
‭loaded dynamically), more sophisticated error handling, and respecting website‬
‭policies.‬

‭(b) How to create and view HTML files with Python.‬‭(6.5)‬

‭Creating HTML Files with Python:‬

‭ ou can create HTML files with Python by simply writing HTML content (as strings) to‬
Y
‭a file with an .html extension.‬

‭Methods:‬
‭1.‬ ‭Basic String Writing:‬
‭○‬ ‭Construct the HTML content as a Python string (multiline strings are useful‬
‭ ere).‬
h
‭ ‬ ‭Open a file in write mode ('w') with an .html extension.‬

‭○‬ ‭Write the string to the file.‬
‭ ython‬
P
‭def‬‭create_basic_html_file‬‭(filename=‬‭"basic_page.html"‬‭):‬
‭html_content =‬‭"""‬
‭<!DOCTYPE html>‬
‭<html lang="en">‬
‭<head>‬
‭<meta charset="UTF-8">‬
‭<meta name="viewport" content="width=device-width, initial-scale=1.0">‬
‭<title>My Python-Generated Page</title>‬
‭<style>‬
‭body { font-family: Arial, sans-serif; background-color: #f0f0f0; color: #333; }‬
‭h1 { color: navy; }‬
‭p { font-size: 1.1em; }‬
‭</style>‬
‭</head>‬
‭<body>‬
‭<h1>Hello from Python!</h1>‬
‭<p>This HTML file was generated by a Python script.</p>‬
‭<p>Current date and time can be dynamically inserted here.</p>‬
‭<ul>‬
‭<li>Item 1</li>‬
‭<li>Item 2</li>‬
‭</ul>‬
‭</body>‬
‭</html>‬
‭"""‬
‭try‬‭:‬
‭with‬‭open‬‭(filename,‬‭'w'‬‭)‬‭as‬ ‭f:‬
f‭ .write(html_content)‬
‭print(‬‭f"HTML file '‬‭{filename}‬‭' created successfully."‬‭)‬
‭return‬ ‭os.path.abspath(filename)‬
‭except‬ ‭IOError‬‭as‬ ‭e:‬
‭print(‬‭f"Error creating HTML file‬‭{filename}‬‭:‬‭{e}‬‭"‬‭)‬
‭return‬‭None‬

‭ Call the function‬


#
‭# created_file_path = create_basic_html_file()‬

‭2.‬ ‭Using Template Engines (More Advanced and Flexible):‬


‭○‬ ‭For more complex HTML generation, especially with dynamic data, template‬
‭ ngines like‬‭Jinja2‬‭(used by Flask and Django) or‬‭Mako‬‭are highly‬
e
‭recommended.‬
‭ ‬ ‭They allow you to create HTML templates with placeholders, loops,‬

‭conditionals, and then render these templates with Python data.‬
‭○‬ ‭This separates presentation (HTML) from logic (Python).‬

‭Example with Jinja2 (requires pip install Jinja2):‬‭Python‬


‭from‬ ‭jinja2‬‭import‬ ‭Environment, FileSystemLoader‬‭#‬‭Or DictLoader for string templates‬
‭import‬ ‭datetime‬

‭def‬‭create_html_with_jinja2‬‭(filename=‬‭"jinja_page.html"‬‭,‬‭data=‬‭None‬‭):‬
‭if‬ ‭data‬‭is‬‭None‬‭:‬
‭data = {‬
‭'title'‬‭:‬‭"Dynamic Page with Jinja2"‬‭,‬
‭'name'‬‭:‬‭"User"‬‭,‬
‭'items'‬‭: [‬‭"Apple"‬‭,‬‭"Banana"‬‭,‬‭"Cherry"‬‭],‬
‭'current_time'‬‭: datetime.datetime.now().strftime(‬‭"%Y-%m-%d‬‭%H:%M:%S"‬‭)‬
‭}‬

‭# Template can be in a string or a separate .html‬‭file‬


‭html_template_string =‬‭"""‬
‭<!DOCTYPE html>‬
‭<html lang="en">‬
‭<head>‬
‭<meta charset="UTF-8">‬
‭<title>{{ title }}</title>‬
‭<style> body { font-family: sans-serif; } </style>‬
‭</head>‬
‭<body>‬
‭<h1>Hello, {{ name }}!</h1>‬
‭<p>This page was generated using Jinja2.</p>‬
‭<p>The current time is: {{ current_time }}</p>‬
‭<h2>Your Items:</h2>‬
‭{% if items %}‬
‭<ul>‬
‭{% for item in items %}‬
‭<li>{{ item }}</li>‬
‭{% endfor %}‬
‭</ul>‬
‭{% else %}‬
‭<p>You have no items.</p>‬
‭{% endif %}‬
‭</body>‬
‭</html>‬
‭"""‬
‭try‬‭:‬
‭# Using DictLoader if template is a string‬
‭# For file-based templates: env = Environment(loader=FileSystemLoader('.'))‬‭# loads from‬
‭current dir‬
‭# template = env.get_template("my_template.html")‬

‭env = Environment(loader=FileSystemLoader(‬‭'.'‬‭))‬‭# Assuming template file is in‬


‭same dir‬
‭# For string template:‬
‭# from jinja2 import Template‬
‭# template = Template(html_template_string)‬

‭ If using FileSystemLoader, save the template_string‬‭to a file first, e.g., "template.html"‬


#
‭# For this example, let's quickly make it‬‭work by writing the template to a file first.‬
‭template_file_name =‬‭"_temp_jinja_template.html"‬
‭with‬‭open‬‭(template_file_name,‬‭"w"‬‭)‬‭as‬ ‭tf:‬
‭tf.write(html_template_string)‬

t‭ emplate = env.get_template(template_file_name)‬
‭rendered_html = template.render(data)‬‭# Pass‬‭data dictionary‬

‭os.remove(template_file_name)‬‭# Clean up temp‬‭template file‬

‭with‬‭open‬‭(filename,‬‭'w'‬‭)‬‭as‬ ‭f:‬
‭f.write(rendered_html)‬
‭print(‬‭f"HTML file '‬‭{filename}‬‭' created successfully‬‭using Jinja2."‬‭)‬
‭return‬ ‭os.path.abspath(filename)‬
‭except‬ ‭Exception‬‭as‬ ‭e:‬
‭print(‬‭f"Error creating HTML file‬‭{filename}‬‭with Jinja2:‬‭{e}‬‭"‬‭)‬
‭return‬‭None‬

‭ Call the function‬


#
‭# user_data = {'title': "Report for Alice", 'name': "Alice", 'items': ["Task 1", "Task 2"]}‬
‭# created_jinja_path = create_html_with_jinja2("report_alice.html", data=user_data)‬

‭3.‬ ‭Using Libraries for Specific HTML Structures (e.g., Tables):‬


‭○‬ ‭Libraries like Pandas can directly export DataFrames to HTML tables‬
‭(DataFrame.to_html()).‬

‭Viewing HTML Files with Python:‬

‭ ython's webbrowser module can be used to open an HTML file (or any URL) in the‬
P
‭default web browser.‬

‭Python‬

‭import‬ ‭webbrowser‬
‭import‬ ‭os‬

‭def‬‭view_html_file‬‭(file_path):‬
‭"""‬
‭ pens the given HTML file path in the default web browser.‬
O
‭"""‬
‭if‬ ‭file_path‬‭and‬ ‭os.path.exists(file_path):‬
‭# Create a file:// URL‬
‭# On Windows, path might need to be like 'file:///C:/path/to/file.html'‬
‭# On Unix/Mac, 'file:///path/to/file.html'‬
‭ le_url =‬‭'file:///'‬ ‭+ os.path.abspath(file_path).replace(‬‭'\\'‬‭,‬‭'/'‬‭)‬
fi
‭print(‬‭f"Attempting to open '‬‭{file_url}‬‭' in‬‭your default web browser..."‬‭)‬
‭try‬‭:‬
‭webbrowser.open_new_tab(file_url)‬‭# open_new_tab‬‭is often preferred‬
‭except‬ ‭Exception‬‭as‬ ‭e:‬
‭print(‬‭f"Could not open browser:‬‭{e}‬‭"‬‭)‬
‭print(‬‭"Please open the file manually by‬‭navigating to:"‬‭, os.path.abspath(file_path))‬
‭else‬‭:‬
‭print(‬‭f"File path '‬‭{file_path}‬‭' is invalid‬‭or does not exist."‬‭)‬

‭# --- Main execution to demonstrate ---‬


‭if‬ ‭__name__ ==‬‭"__main__"‬‭:‬
‭ rint(‬‭"--- Creating and Viewing Basic HTML File‬‭---"‬‭)‬
p
‭basic_file_path = create_basic_html_file(‬‭"my_generated_page.html"‬‭)‬
‭if‬ ‭basic_file_path:‬
‭view_html_file(basic_file_path)‬

‭print(‬‭"\n--- Creating and Viewing HTML File with‬‭Jinja2 ---"‬‭)‬


‭jinja_data = {‬
‭'title'‬‭:‬‭"Jinja2 Demo Page"‬‭,‬
‭'name'‬‭:‬‭"Pythonista"‬‭,‬
‭'items'‬‭: [‬‭"Learning Python"‬‭,‬‭"Generating HTML"‬‭,‬‭"Using Jinja2"‬‭],‬
‭'current_time'‬‭: datetime.datetime.now().strftime(‬‭"%A,‬‭%B %d, %Y %I:%M:%S %p"‬‭)‬
‭}‬
‭jinja_file_path = create_html_with_ jinja2(‬‭"my_jinja_generated_page.html"‬‭,‬
‭data=jinja_data)‬
‭if‬ ‭jinja_file_path:‬
‭view_html_file(jinja_file_path)‬

‭# Optional: Clean up generated files after viewing‬‭for a clean run next time‬
‭# if basic_file_path and os.path.exists(basic_file_path): os.remove(basic_file_path)‬
‭# if jinja_file_path and os.path.exists(jinja_file_path):‬‭os.remove(jinja_file_path)‬

‭When you run this script:‬


‭●‬ ‭It will create my_generated_page.html and my_ jinja_generated_page.html.‬
‭●‬ ‭It will then attempt to open these files in your system's default web browser. You‬
‭should see the rendered HTML pages.‬

‭ he webbrowser module provides a simple, cross-platform way to display local HTML‬


T
‭files or web URLs. For more complex server-side rendering and serving HTML as part‬
‭of a web application, frameworks like Flask or Django are used. They handle HTTP‬
‭requests, route them to Python functions that generate HTML (often using template‬
‭engines), and send the HTML back to the client's browser.‬

You might also like