0% found this document useful (0 votes)
19 views

The Python modules (1)

The document provides examples of Python programs that utilize user-defined modules and built-in modules such as math, random, and os. It includes a user-defined module for calculating the area and perimeter of rectangles, greeting users, and checking if numbers are even. Additionally, it details the functionalities of built-in modules, including mathematical operations, random number generation, and operating system interactions.

Uploaded by

Tanisha Waichal
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
19 views

The Python modules (1)

The document provides examples of Python programs that utilize user-defined modules and built-in modules such as math, random, and os. It includes a user-defined module for calculating the area and perimeter of rectangles, greeting users, and checking if numbers are even. Additionally, it details the functionalities of built-in modules, including mathematical operations, random number generation, and operating system interactions.

Uploaded by

Tanisha Waichal
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 63

Practical No.

16: Write a python program to create and use a user defined module for a given
problem

# File: my_module.py (This is your user-defined module)


def calculate_area_rectangle(length, width):
"""Calculates the area of a rectangle."""
return length * width
def calculate_perimeter_rectangle(length, width):
"""Calculates the perimeter of a rectangle."""
return 2 * (length + width)
def greet(name):
"""Greets a person by name."""
return f"Hello, {name}!"
def is_even(number):
"""Checks if a number is even."""
return number % 2 == 0

# File: main.py (This is your main program)


import my_module # Import the user-defined module
# Example usage of functions from the module
length = 5
width = 10
area = my_module.calculate_area_rectangle(length, width)
perimeter = my_module.calculate_perimeter_rectangle(length, width)
print(f"Area of rectangle: {area}")
print(f"Perimeter of rectangle: {perimeter}")
name = "Alice"
greeting = my_module.greet(name)
print(greeting)
number = 17
if my_module.is_even(number):
print(f"{number} is even.")
else:
print(f"{number} is odd.")

# Another example showing how to import specific functions.


from my_module import calculate_area_rectangle, greet
length2 = 7
width2 = 8
area2 = calculate_area_rectangle(length2, width2) #no need to write my_module.
print(f"Area of rectangle 2: {area2}")
print(greet("Bob")) #no need to write my_module.
Practical No. 17: Write a python program to demonstrate the use of following

module: 1. Math module 2. Random module 3. OS module

Built-in Modules in python

Python's power is significantly enhanced by its extensive collection of built-in modules. These
modules provide pre-written code for a wide range of tasks, eliminating the need to reinvent the
wheel. Here's a breakdown of some commonly used and important built-in modules:

Core Functionality:

● os:
○ Provides an interface to the operating system.
○ Functions for file and directory manipulation, process management, and environment
variables.
● sys:
○ Provides access to system-specific parameters and functions.
○ Command-line arguments, standard input/output, and system paths.
● math:
○ Mathematical functions and constants (e.g., trigonometric, logarithmic, exponential).
● random:
○ Generates pseudo-random numbers.
○ Functions for random selection, shuffling, and generating random values.
● datetime:
○ Works with dates and times.
○ Provides classes for representing dates, times, and time intervals.
● time:
○ Time-related functions.
○ Measuring execution time, pausing execution, and working with time representations.
● io:
○ Input/output operations.
○ Working with streams of data (files, memory buffers).
● json:
○ Working with JSON (JavaScript Object Notation) data.
○ Encoding and decoding JSON.
● re:
○ Regular expressions.
○ Pattern matching and text manipulation.
● collections:
○ Specialized container datatypes (e.g., deque, Counter, defaultdict).
○ Enhanced data structures beyond basic lists, tuples, and dictionaries.
● typing:
○ Support for type hints.
○ Enables static type checking to improve code quality.

Networking and Internet:

● socket:
○ Low-level network communication.
○ Creating network sockets for TCP/IP and UDP connections.
● http:
○ HTTP clients and servers.
● urllib:
○ Working with URLs.
○ Fetching data from web pages.

File and Data Handling:

● csv:
○ Working with CSV (Comma Separated Values) files.
● pickle:
○ Object serialization.
○ Saving and loading Python objects to/from files.
● sqlite3:
○ SQLite database interface.
○ Working with SQLite databases.
● zlib:
○ Compression and decompression of data.

Concurrency and Multiprocessing:

● threading:
○ Thread-based parallelism.
● multiprocessing:
○ Process-based parallelism.

Other Important Modules:

● argparse:
○ Parsing command-line arguments.
● logging:
○ Logging messages for debugging and monitoring.
● unittest:
○ Unit testing framework.
● hashlib:
○ Secure hashes and message digests.

How to Use Modules:

1. Import: Use the import statement to bring a module into your code.
○ import os
2. Access Functions: Use the module name followed by a dot (.) to access functions and
variables within the module.
○ os.getcwd()

The Python math module provides access to a wide range of mathematical functions and
constants. It's a fundamental tool for numerical computations in Python. Here's a breakdown of its key
features:

Key Features and Constants:


● Constants:
○ math.pi: The mathematical constant π (pi).
○ math.e: The mathematical constant e.
○ math.tau: The mathematical constant τ (tau), which is 2π.
○ math.inf: Positive infinity.
○ math.nan: "Not a number" (NaN).
● Number-Theoretic and Representation Functions:
○ math.ceil(x): Returns the smallest integer greater than or equal to x.
○ math.floor(x): Returns the largest integer less than or equal to x.
○ math.trunc(x): Returns the integer part of x.
○ math.fabs(x): Returns the absolute value of x.
○ math.factorial(x): Returns the factorial of x.
○ math.gcd(a, b): Returns the greatest common divisor of integers a and b.
○ math.isclose(a, b, rel_tol=1e-09, abs_tol=0.0): Returns True if the
values a and b are close to each other.
○ math.isfinite(x): Returns True if x is neither an infinity nor a NaN.
○ math.isinf(x): Returns True if x is a positive or negative infinity.
○ math.isnan(x): Returns True if x is a NaN.
○ math.modf(x): Returns the fractional and integer parts of x.
● Power and Logarithmic Functions:
○ math.exp(x): Returns e raised to the power x.
○ math.expm1(x): Returns e raised to the power x, minus 1.
○ math.log(x[, base]): Returns the logarithm of x to the given base.
○ math.log1p(x): Returns the natural logarithm of 1+x.
○ math.log2(x): Returns the base-2 logarithm of x.
○ math.log10(x): Returns the base-10 logarithm of x.
○ math.pow(x, y): Returns x raised to the power y.
○ math.sqrt(x): Returns the square root of x.
● Trigonometric Functions:
○ math.acos(x): Returns the arc cosine of x.
○ math.asin(x): Returns the arc sine of x.
○ math.atan(x): Returns the arc tangent of x.
○ math.atan2(y, x): Returns the arc tangent of y/x.
○ math.cos(x): Returns the cosine of x.
○ math.dist(p, q): Returns the Euclidean distance between two points p and q.
○ math.hypot(*coordinates): Returns the Euclidean norm.
○ math.sin(x): Returns the sine of x.
○ math.tan(x): Returns the tangent of x.
○ math.degrees(x): Converts angle x from radians to degrees.
○ math.radians(x): Converts angle x from degrees to radians.
● Hyperbolic Functions:
○ math.acosh(x): Returns the inverse hyperbolic cosine of x.
○ math.asinh(x): Returns the inverse hyperbolic sine of x.
○ math.atanh(x): Returns the inverse hyperbolic tangent of x.
○ math.cosh(x): Returns the hyperbolic cosine of x.
○ math.sinh(x): Returns the hyperbolic sine of x.
○ math.tanh(x): Returns the hyperbolic tangent of x.
● Special Functions:
○ math.erf(x): Returns the error function at x.
○ math.erfc(x): Returns the complementary error function at x.
○ math.gamma(x): Returns the Gamma function at x.
○ math.lgamma(x): Returns the natural logarithm of the absolute value of the Gamma
function at x.

import math

# Constants
print("Constants:")
print(f"pi: {math.pi}")
print(f"e: {math.e}")
print(f"tau: {math.tau}")
print(f"inf: {math.inf}")
print(f"nan: {math.nan}")
print("-" * 20)

# Number-Theoretic and Representation Functions


print("Number-Theoretic and Representation Functions:")
print(f"ceil(3.14): {math.ceil(3.14)}")
print(f"floor(3.99): {math.floor(3.99)}")
print(f"trunc(3.7): {math.trunc(3.7)}")
print(f"fabs(-5): {math.fabs(-5)}")
print(f"factorial(5): {math.factorial(5)}")
print(f"gcd(12, 18): {math.gcd(12, 18)}")
print(f"isclose(1.00000001, 1.0): {math.isclose(1.00000001, 1.0)}")
print(f"isfinite(10): {math.isfinite(10)}")
print(f"isinf(math.inf): {math.isinf(math.inf)}")
print(f"isnan(math.nan): {math.isnan(math.nan)}")
print(f"modf(3.14): {math.modf(3.14)}")
print("-" * 20)

# Power and Logarithmic Functions


print("Power and Logarithmic Functions:")
print(f"exp(2): {math.exp(2)}")
print(f"expm1(2): {math.expm1(2)}")
print(f"log(10): {math.log(10)}")
print(f"log(10, 2): {math.log(10, 2)}") # log base 2 of 10
print(f"log1p(9): {math.log1p(9)}")
print(f"log2(8): {math.log2(8)}")
print(f"log10(100): {math.log10(100)}")
print(f"pow(2, 3): {math.pow(2, 3)}")
print(f"sqrt(16): {math.sqrt(16)}")
print("-" * 20)

# Trigonometric Functions
print("Trigonometric Functions:")
print(f"acos(0): {math.acos(0)}")
print(f"asin(1): {math.asin(1)}")
print(f"atan(1): {math.atan(1)}")
print(f"atan2(1, 1): {math.atan2(1, 1)}")
print(f"cos(0): {math.cos(0)}")
print(f"dist((1, 1), (4, 5)): {math.dist((1, 1), (4, 5))}")
print(f"hypot(3, 4): {math.hypot(3, 4)}")
print(f"sin(math.pi / 2): {math.sin(math.pi / 2)}")
print(f"tan(0): {math.tan(0)}")
print(f"degrees(math.pi): {math.degrees(math.pi)}")
print(f"radians(180): {math.radians(180)}")
print("-" * 20)

# Hyperbolic Functions
print("Hyperbolic Functions:")
print(f"acosh(2): {math.acosh(2)}")
print(f"asinh(1): {math.asinh(1)}")
print(f"atanh(0.5): {math.atanh(0.5)}")
print(f"cosh(0): {math.cosh(0)}")
print(f"sinh(0): {math.sinh(0)}")
print(f"tanh(1): {math.tanh(1)}")
print("-" * 20)

# Special Functions
print("Special Functions:")
print(f"erf(0): {math.erf(0)}")
print(f"erfc(0): {math.erfc(0)}")
print(f"gamma(5): {math.gamma(5)}")
print(f"lgamma(5): {math.lgamma(5)}")
print("-" * 20)
The Python os module provides a portable way of using operating system dependent functionality.
It allows your Python scripts to interact with the underlying operating system, performing tasks like file
manipulation, directory navigation, process management, and more. Here's a breakdown of
commonly used functions and features:

Key Functionality:

● File and Directory Operations:


○ os.getcwd(): Returns the current working directory.
○ os.chdir(path): Changes the current working directory to the specified path.

○ os.listdir(path='.'): Returns a list of the entries in the directory given by path.
The list is in arbitrary order.

○ os.mkdir(path): Creates a directory named path.
○ os.makedirs(path): Creates directories recursively.
○ os.rmdir(path): Removes (deletes) the directory path.
○ os.remove(path): Removes (deletes) the file path.
○ os.rename(src, dst): Renames the file or directory src to dst.
○ os.path.exists(path): Returns True if path refers to an existing path or an open
file descriptor.
○ os.path.isfile(path): Returns True if path is an existing regular file.
○ os.path.isdir(path): Returns True if path is an existing directory.
○ os.path.join(path, *paths): Joins one or more path components intelligently.
○ os.path.split(path): Splits the pathname path into a pair, (head, tail) where tail
is the last pathname component.
○ os.path.splitext(path): Splits the pathname path into a pair (root, ext) such that
root + ext == path and ext is empty or begins with a period and contains at most one
period.

● Process Management:
○ os.system(command): Executes the command (a string) in a subshell.
○ os.getpid(): Returns the current process ID.
○ os.getppid(): Returns the parent's process ID.
● Environment Variables:
○ os.environ: A dictionary-like object representing the user's environment variables.
○ os.getenv(varname, default=None): Gets the value of the environment
variable varname.
● Permissions:
○ os.chmod(path, mode): Changes the permission of path to the numeric mode.
import os

# Current working directory


cwd = os.getcwd()
print(f"Current working directory: {cwd}")

# List files in a directory


files = os.listdir('.')
print(f"Files in current directory: {files}")

# Create a directory
try:
os.mkdir("test_dir")
print("Directory 'test_dir' created.")
except FileExistsError:
print("Directory 'test_dir' already exists.")

# Create a file inside the directory


with open(os.path.join("test_dir", "test_file.txt"), "w") as f:
f.write("Hello, OS module!")

# Check if a file exists


file_path = os.path.join("test_dir", "test_file.txt")
if os.path.exists(file_path):
print(f"File '{file_path}' exists.")

# Get environment variables


home_dir = os.getenv("HOME") # works on linux/mac. On windows use
"USERPROFILE"
print(f"Home directory: {home_dir}")

# Remove the created file and directory


os.remove(file_path)
os.rmdir("test_dir")
print("File and directory removed.")
The Python random module provides functions for generating pseudo-random numbers. It's
essential for tasks like simulations, games, security, and data generation. Here's a comprehensive
overview:

Key Functionality:

● Integer Functions:
○ random.randint(a, b): Returns a random integer N such that a <= N <= b.
○ random.randrange(start, stop[, step]): Returns a randomly selected
element from range(start, stop, step).
● Sequence Functions:
○ random.choice(seq): Returns a random element from the non-empty sequence
seq.
○ random.choices(population, weights=None, *, cum_weights=None,
k=1): Returns a k sized list of elements chosen from the population with replacement.

○ random.shuffle(x): Shuffle the sequence x in place.
○ random.sample(population, k): Returns a k length list of unique elements
chosen from the population sequence.
● Real-Valued Distributions:
○ random.random(): Returns the next random floating point number in the range [0.0,
1.0).
○ random.uniform(a, b): Returns a random floating point number N such that a <=
N <= b for a <= b and b <= N <= a for b < a.
○ random.triangular(low, high, mode): Returns a random floating point
number N such that low <= N <= high and with the specified mode between those
bounds.
○ random.betavariate(alpha, beta): Beta distribution.

○ random.expovariate(lambd): Exponential distribution.
○ random.gammavariate(alpha, beta): Gamma distribution.
○ random.gauss(mu, sigma): Gaussian distribution.
○ random.lognormvariate(mu, sigma): Log normal distribution.
○ random.normalvariate(mu, sigma): Normal distribution.
○ random.vonmisesvariate(mu, kappa): Von Mises distribution.
○ random.paretovariate(alpha): Pareto distribution.
○ random.weibullvariate(alpha, beta): Weibull distribution.
● Seeding:
○ random.seed(a=None, version=2): Initialize the random number generator.
import random

# Integer functions
print("Random integer between 1 and 10:", random.randint(1, 10))
print("Random range element:", random.randrange(0, 10, 2))

# Sequence functions
my_list = [1, 2, 3, 4, 5]
print("Random choice from list:", random.choice(my_list))
print("Sample of 3 unique elements:", random.sample(my_list, 3))
random.shuffle(my_list)
print("Shuffled list:", my_list)
print("choices with replacement:", random.choices(my_list, k = 4))

# Real-valued distributions
print("Random float between 0 and 1:", random.random())
print("Random float between 5 and 10:", random.uniform(5, 10))
print("Gaussian distribution:", random.gauss(0, 1))

# Seeding
random.seed(42) # Set seed for reproducibility
print("Random number with seed 42:", random.random())
random.seed(42) # reset the seed to reproduce the previous random number.
print("Random number with seed 42 again:", random.random())
Practical No. 18: Write python program to create and use a user defined package for a given
problem

# File: my_package/geometry.py
def calculate_area_rectangle(length, width):
"""Calculates the area of a rectangle."""
return length * width

def calculate_perimeter_rectangle(length, width):


"""Calculates the perimeter of a rectangle."""
return 2 * (length + width)

# File: my_package/greetings.py
def greet(name):
"""Greets a person by name."""
return f"Hello, {name}!"

# File: my_package/__init__.py
# This file marks the directory as a package

# File: main.py
import my_package.geometry as geometry
import my_package.greetings as greetings

length = 5
width = 10

area = geometry.calculate_area_rectangle(length, width)


perimeter = geometry.calculate_perimeter_rectangle(length, width)

print(f"Area of rectangle: {area}")


print(f"Perimeter of rectangle: {perimeter}")

name = "Alice"
greeting = greetings.greet(name)
print(greeting)

Explanation and How to Run:

1. Create the Package Directory:

○ Create a new directory named my_package. This will be your package.


2. Create Modules within the Package:

○ Inside my_package, create Python files for your modules. In this example, we have
geometry.py and greetings.py.
○ Define your functions within these modules (e.g., calculate_area_rectangle in
geometry.py, greet in greetings.py).
3. Create __init__.py:

○ Create an empty file named __init__.py inside the my_package directory. This file
tells Python to treat the directory as a package.
4. Create main.py:

○ Create a Python file named main.py in the same directory as my_package.


○ Import the modules from your package using:
■ import my_package.geometry as geometry
■ import my_package.greetings as greetings
○ Access the functions using the module alias (e.g.,
geometry.calculate_area_rectangle()).
5. Run main.py:

○ Open a terminal or command prompt.


○ Navigate to the directory where you saved my_package and main.py.
○ Run the command python main.py.
○ You will see the output of the functions from your package.

Key Concepts:

● Packages: A package is a way of organizing related modules into a directory hierarchy.


● __init__.py: This file is required to make Python treat a directory as a package. It can be
empty or contain initialization code for the package.
● Importing from Packages: You can import modules from a package using dot notation (e.g.,
my_package.geometry).
● Module Aliases: You can use as to give a module a shorter alias when importing (e.g.,
import my_package.geometry as geometry).

Benefits of Using Packages:

● Organization: Packages help to organize your code into logical units, making it easier to
manage and maintain.
● Reusability: You can reuse your packages in multiple projects.
● Namespace Management: Packages provide a hierarchical namespace to avoid naming
conflicts between modules.
# Practical No. 19: Numpy and Matplotlib

import numpy as np
import matplotlib.pyplot as plt

# --- Numpy Operations on 2D Matrix ---

# Create a 2D numpy array (matrix)


matrix1 = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

matrix2 = np.array([[9, 8, 7],


[6, 5, 4],
[3, 2, 1]])

print("Matrix 1:\n", matrix1)


print("Matrix 2:\n", matrix2)

# Matrix addition
matrix_sum = matrix1 + matrix2
print("\nMatrix Sum:\n", matrix_sum)

# Matrix multiplication (element-wise)


matrix_product_elementwise = matrix1 * matrix2
print("\nElement-wise Matrix Product:\n", matrix_product_elementwise)

# Matrix multiplication (dot product)


matrix_product_dot = np.dot(matrix1, matrix2)
print("\nDot Product Matrix Multiplication:\n", matrix_product_dot)

# Transpose of a matrix
matrix1_transpose = matrix1.T
print("\nTranspose of Matrix 1:\n", matrix1_transpose)

# Mean of matrix elements


matrix1_mean = np.mean(matrix1)
print("\nMean of Matrix 1:", matrix1_mean)

# Sum of matrix elements


matrix1_sum_elements = np.sum(matrix1)
print("\nSum of Matrix 1 elements:", matrix1_sum_elements)

# --- Matplotlib Data Representation ---

# Sample data
x = np.linspace(0, 10, 100) # 100 evenly spaced points from 0 to 10
y1 = np.sin(x)
y2 = np.cos(x)

# Create a plot
plt.figure(figsize=(10, 6)) # Set figure size

plt.plot(x, y1, label='sin(x)', color='blue', linestyle='-')


plt.plot(x, y2, label='cos(x)', color='red', linestyle='--')

plt.title('Sine and Cosine Waves')


plt.xlabel('X-axis')
plt.ylabel('Y-axis')
plt.legend()
plt.grid(True) # Add grid lines

plt.show() # Display the plot

# Bar chart example


categories = ['A', 'B', 'C', 'D']
values = [25, 40, 30, 55]

plt.figure(figsize=(8, 5))
plt.bar(categories, values, color='green')
plt.title('Bar Chart Example')
plt.xlabel('Categories')
plt.ylabel('Values')
plt.show()

# Scatter plot example


x_scatter = np.random.rand(50)
y_scatter = np.random.rand(50)

plt.figure(figsize=(8, 5))
plt.scatter(x_scatter, y_scatter, color='purple', marker='o')
plt.title('Scatter Plot Example')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()

Explanation and How to Run:

1. Install Libraries:

If you don't have them already, install NumPy and Matplotlib:


Bash
pip install numpy matplotlib

2. Save the Code:


○ Save the code as a Python file (e.g., numpy_matplotlib_demo.py).
3. Run the Script:
○ Open a terminal or command prompt.
○ Navigate to the directory where you saved the file.
○ Run the command python numpy_matplotlib_demo.py.

Numpy Section:

● Creates two 2D NumPy arrays (matrices).


● Demonstrates matrix addition, element-wise multiplication, dot product, transpose, mean, and
sum.
● Prints the results to the console.

Matplotlib Section:

● Generates sample data using np.linspace() and trigonometric functions.


● Creates a line plot of sine and cosine waves using plt.plot().
● Adds a title, labels, legend, and grid lines.
● Displays the plot using plt.show().
● Creates a bar chart using plt.bar().
● Creates a scatter plot using plt.scatter().

Key Concepts:

● NumPy:
○ Provides efficient array operations for numerical computing.
○ np.array() creates NumPy arrays.
○ np.dot() performs matrix dot product.
○ .T gives the transpose of a matrix
○ np.mean() and np.sum() calculate mean and sum of array elements.
● Matplotlib:
○ Provides a powerful plotting library for creating various types of graphs.
○ plt.plot() creates line plots.
○ plt.bar() creates bar charts.
○ plt.scatter() creates scatter plots.
○ plt.title(), plt.xlabel(), plt.ylabel(), and plt.legend() add labels
and legends to plots.
○ plt.show() displays the plots.
NumPy (Numerical Python) is a fundamental library for numerical computing in Python. It provides
support for large, multi-dimensional arrays and matrices, along with a collection of high-level
mathematical functions to operate on these arrays.

Here's a breakdown of various NumPy functions, categorized for clarity, with examples:

1. Array Creation and Manipulation:

● np.array(object, dtype=None): Creates a NumPy array from a list, tuple, or other array-like
object.
Python

import numpy as np
arr = np.array([1, 2, 3, 4, 5])
print(arr) # Output: [1 2 3 4 5]

● np.zeros(shape, dtype=float): Creates an array filled with zeros.


Python

zeros_arr = np.zeros((2, 3)) # 2x3 matrix of zeros


print(zeros_arr)

● np.ones(shape, dtype=float): Creates an array filled with ones.


Python

ones_arr = np.ones((3, 2)) # 3x2 matrix of ones


print(ones_arr)

● np.empty(shape, dtype=float): Creates an array of the given shape and dtype, without
initializing elements to particular values.
Python

empty_arr = np.empty((2, 2))


print(empty_arr) # Output will be filled with garbage values

● np.arange([start,] stop[, step,], dtype=None): Returns evenly spaced values within a given
range.
Python

range_arr = np.arange(0, 10, 2) # From 0 to 10 with step 2


print(range_arr) # Output: [0 2 4 6 8]
● np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None): Returns evenly
spaced numbers over a specified interval.
Python

linear_arr = np.linspace(0, 1, 5) # 5 evenly spaced numbers between 0 and 1


print(linear_arr) # Output: [0. 0.25 0.5 0.75 1. ]

● np.reshape(a, newshape): Gives a new shape to an array without changing its data.
Python

arr = np.arange(6)
reshaped_arr = arr.reshape(2, 3)
print(reshaped_arr)

● np.transpose(a, axes=None) or a.T: Reverse or permute the axes of an array; returns the
modified array.
Python

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


transposed_arr = arr.T
print(transposed_arr)

● np.concatenate((a1, a2, ...), axis=0): Join a sequence of arrays along an existing axis.
Python

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


arr2 = np.array([[5, 6], [7, 8]])
concatenated_arr = np.concatenate((arr1, arr2), axis=0) # Concatenate along
rows
print(concatenated_arr)

● np.split(ary, indices_or_sections, axis=0): Split an array into multiple sub-arrays as a list.

Python

arr = np.arange(9)
split_arr = np.split(arr, 3) # Split into 3 equal parts
print(split_arr)

2. Array Indexing and Slicing:


● NumPy arrays can be indexed and sliced much like Python lists, but with more powerful
capabilities.

Python

arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])


print(arr[0]) # Output: 0
print(arr[2:5]) # Output: [2 3 4]
print(arr[:5]) # Output: [0 1 2 3 4]
print(arr[5:]) # Output: [5 6 7 8 9]
print(arr[-1]) # Output: 9 (last element)
print(arr[::2]) # Output: [0 2 4 6 8] (every other element)

# Multi-dimensional array indexing


arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(arr2d[0, 0]) # Output: 1 (element at row 0, column 0)
print(arr2d[1, :]) # Output: [4 5 6] (entire row 1)
print(arr2d[:, 1]) # Output: [2 5 8] (entire column 1)
print(arr2d[0:2, 0:2]) # Output: [[1 2] [4 5]] (2x2 sub-matrix)

● Boolean Indexing: Select elements based on a condition.

Python

arr = np.array([10, 20, 30, 40, 50])


mask = arr > 25
print(mask) # Output: [False False True True True]
print(arr[mask]) # Output: [30 40 50]

3. Mathematical Functions:

● np.add(x1, x2) or x1 + x2: Element-wise addition.

Python

arr1 = np.array([1, 2, 3])


arr2 = np.array([4, 5, 6])
sum_arr = np.add(arr1, arr2)
print(sum_arr) # Output: [5 7 9]

● np.subtract(x1, x2) or x1 - x2: Element-wise subtraction.

Python
diff_arr = np.subtract(arr2, arr1)
print(diff_arr) # Output: [3 3 3]

● np.multiply(x1, x2) or x1 * x2: Element-wise multiplication.

Python

prod_arr = np.multiply(arr1, arr2)


print(prod_arr) # Output: [ 4 10 18]

● np.divide(x1, x2) or x1 / x2: Element-wise division.

Python

div_arr = np.divide(arr2, arr1)


print(div_arr) # Output: [4. 2.5 2. ]
np.dot(a, b): Dot product of two arrays.
Python

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


arr2 = np.array([[5, 6], [7, 8]])
dot_prod = np.dot(arr1, arr2)
print(dot_prod)

● np.sqrt(x): Element-wise square root.

Python

arr = np.array([1, 4, 9, 16])


sqrt_arr = np.sqrt(arr)
print(sqrt_arr) # Output: [1. 2. 3. 4.]

● np.sum(a, axis=None): Sum of array elements.

Python

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


sum_arr = np.sum(arr)
print(sum_arr) # Output: 10
sum_row = np.sum(arr, axis=1) # Sum of each row
print(sum_row) # Output: [3 7]

● np.mean(a, axis=None): Mean of array elements.


Python

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


mean_arr = np.mean(arr)
print(mean_arr) # Output: 2.5
mean_col = np.mean(arr, axis=0) # Mean of each column
print(mean_col) # Output: [2. 3.]

● np.max(a, axis=None): Maximum of array elements.

Python

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


max_arr = np.max(arr)
print(max_arr) # Output: 4

● np.min(a, axis=None): Minimum of array elements.

Python

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


min_arr = np.min(arr)
print(min_arr) # Output: 1

● np.exp(x): Element-wise exponential.

Python

arr = np.array([0, 1, 2])


exp_arr = np.exp(arr)
print(exp_arr) # Output: [1. 2.71828183 7.3890561 ]

● np.log(x): Element-wise natural logarithm.

Python

arr = np.array([1, np.e, np.e**2])


log_arr = np.log(arr)
print(log_arr) # Output: [0. 1. 2.]

● np.sin(x): Element-wise sine.

Python

arr = np.array([0, np.pi/2, np.pi])


sin_arr = np.sin(arr)
print(sin_arr) # Output: [0.e+00 1.e+00 1.e-16]

● np.cos(x): Element-wise cosine.

Python

cos_arr = np.cos(arr)
print(cos_arr) # Output: [ 1. -1. -1.]

4. Array Manipulation Functions

● np.reshape(a, newshape): Gives a new shape to an array without changing its data.
Python

arr = np.arange(6)
reshaped_arr = arr.reshape(2, 3)
print(reshaped_arr)

● np.resize(a, new_shape): Return a new array with the specified shape. If the new array is
larger than the original array, then the new array is filled with repeated copies of a. Note that
this behavior is different from a.resize() which modifies a in place

Python

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


resized_arr = np.resize(arr, (3, 2))
print(resized_arr)

● np.flatten(): Return a copy of the array collapsed into one dimension.

Python

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


flattened_arr = arr.flatten()
print(flattened_arr)

● np.ravel(a, order='C'): Return a contiguous flattened array.

Python

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


raveled_arr = np.ravel(arr)
print(raveled_arr)
● np.concatenate((a1, a2, ...), axis=0): Join a sequence of arrays along an existing axis.

Python

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


arr2 = np.array([[5, 6], [7, 8]])
concatenated_arr = np.concatenate((arr1, arr2), axis=0) # Concatenate along
rows
print(concatenated_arr)

● np.stack(arrays, axis=0, out=None): Join a sequence of arrays along a new axis.

Python

arr1 = np.array([1, 2, 3])


arr2 = np.array([4, 5, 6])
stacked_arr = np.stack((arr1, arr2))
print(stacked_arr)

● np.hstack(tup): Stack arrays in sequence horizontally (column wise).

Python

arr1 = np.array([1, 2, 3])


arr2 = np.array([4, 5, 6])
hstack_arr = np.hstack((arr1, arr2))
print(hstack_arr)

● np.vstack(tup): Stack arrays in sequence vertically (row wise).

Python

arr1 = np.array([1, 2, 3])


arr2 = np.array([4, 5, 6])
vstack_arr = np.vstack((arr1, arr2))
print(vstack_arr)

5. Linear Algebra Functions:

● np.linalg.det(a): Compute the determinant of a matrix.


Python

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


det_arr = np.linalg.det(arr)
print(det_arr) # Output: -2.0

● np.linalg.inv(a): Compute the (multiplicative) inverse of a matrix.

Python

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


inv_arr = np.linalg.inv(arr)
print(inv_arr)

● np.linalg.eig(a): Compute the eigenvalues and right eigenvectors of a square array.

Python

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


eig_val, eig_vec = np.linalg.eig(arr)
print("Eigenvalues:", eig_val)
print("Eigenvectors:\n", eig_vec)

● np.linalg.solve(a, b): Solve a linear matrix equation, or system of linear scalar equations.

Python

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


b = np.array([1, 2])
x = np.linalg.solve(a, b)
print(x)

6. Random Number Generation:

● np.random.rand(d0, d1, ..., dn): Create an array of the given shape and populate it with
random samples from a uniform distribution over [0, 1).
Python

rand_arr = np.random.rand(2, 3)
print(rand_arr)

● np.random.randn(d0, d1, ..., dn): Return a sample (or samples) from the "standard normal"
distribution.
Python

randn_arr = np.random.randn(2, 3)
print(randn_arr)
● np.random.randint(low, high=None, size=None, dtype=int): Return random integers from the
“discrete uniform” distribution of the specified dtype in the “half-open” interval [low, high).

Python

randint_arr = np.random.randint(1, 10, size=(3, 3))


print(randint_arr)

● np.random.choice(a, size=None, replace=True, p=None): Generates a random sample from a


given 1-D array.
Python

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


choice_arr = np.random.choice(arr, size=3, replace=False)
print(choice_arr)
7. Array Comparison

● np.array_equal(a1, a2): True if two arrays have the same shape and elements, False
otherwise.
Python

arr1 = np.array([1, 2, 3])


arr2 = np.array([1, 2, 3])
arr3 = np
Practical No. 20: Develop a python program to perform following operations:

1. Creating a Class with method 2. Creating Objects of class 3. Accessing method using object

1. Creating a Class with Methods

● Class Definition:

○ A class is like a blueprint for creating objects. It defines the attributes (data) and
methods (functions) that objects of that class will have.

○ In Python, you define a class using the class keyword.
○ The convention is to use CamelCase for class names (e.g., MyClass).
● Methods:

○ Methods are functions defined within a class. They operate on the objects of that
class.
○ The first parameter of a method is always self. self refers to the instance of the
object that the method is called on.
○ Methods define the behavior of objects.
● __init__ (Constructor):

○ The __init__ method is a special method called a constructor.


○ It's automatically called when an object of the class is created.
○ It's used to initialize the object's attributes.

2. Creating Objects of a Class (Instantiation)

● Object Creation:
○ An object is an instance of a class.
○ Creating an object is called instantiation.
○ You create an object by calling the class name as if it were a function.

3. Accessing Methods Using Objects

● Method Invocation:
○ To call a method on an object, you use the dot notation (object.method()).

Deep Explanation and Demonstration:


class Dog:
"""A simple class representing a dog."""

def __init__(self, name, breed, age):


"""Initializes the dog's attributes."""
self.name = name
self.breed = breed
self.age = age

def bark(self):
"""Simulates the dog barking."""
return "Woof!"

def get_info(self):
"""Returns a string describing the dog."""
return f"{self.name} is a {self.breed} and is {self.age} years old."

# Creating Objects (Instantiation)


dog1 = Dog("Buddy", "Golden Retriever", 3)
dog2 = Dog("Max", "German Shepherd", 5)

# Accessing Methods Using Objects


print(dog1.bark()) # Output: Woof!
print(dog2.get_info()) # Output: Max is a German Shepherd and is 5 years old.

# Accessing Attributes directly.


print(dog1.name) #output: Buddy.

Explanation:

1. Class Dog:

○ We define a class called Dog.


○ The __init__ method initializes the name, breed, and age attributes.
○ The bark method returns the string "Woof!".
○ The get_info method returns a descriptive string about the dog.
2. Object Creation:

○dog1 = Dog("Buddy", "Golden Retriever", 3) creates an object named


dog1 of the Dog class.
○ dog2 = Dog("Max", "German Shepherd", 5) creates an object named dog2 of
the Dog class.
3. Method Access:

○ dog1.bark() calls the bark method on the dog1 object.


○ dog2.get_info() calls the get_info method on the dog2 object.
○ dog1.name directly accesses the name attribute of the dog1 object.

Key Concepts in OOP:

● Encapsulation: Bundling data (attributes) and methods that operate on the data within a
single unit (class).
● Abstraction: Hiding complex implementation details and showing only necessary information.
● Inheritance: Creating new classes (derived classes) from existing classes (base classes).
● Polymorphism: The ability of objects of different classes to respond to the same method call
in different ways.
The __init__ method, which is a special method in Python classes. It's the constructor, and it's
essential for initializing the attributes (data) of objects when they are created.

Let's break down def __init__(self, name, breed, age): in detail:

def __init__(self, name, breed, age):

● def __init__(...):

○ def indicates that we are defining a function (in this case, a method).
○ __init__ is a special method name. In Python, methods with double underscores at
the beginning and end (dunder methods) have special meanings. __init__
specifically is the constructor.
● self:

○self is the first parameter of any method within a class.


○It represents the instance of the object itself.
○When you create an object (e.g., dog1 = Dog("Buddy", "Golden Retriever",
3)), Python automatically passes the object as the self argument.
○ You use self to access and modify the object's attributes.
● name, breed, age:

○ These are the parameters that the __init__ method takes.


○ When you create an object, you provide values for these parameters.
○ In the Example dog1 = Dog("Buddy", "Golden Retriever", 3)
■ "Buddy" is assigned to the name parameter.
■ "Golden Retriever" is assigned to the breed parameter.
■ 3 is assigned to the age parameter.
● Inside the __init__ Method:

○ The code inside the __init__ method typically assigns the parameter values to the
object's attributes.
○ Example:
■ self.name = name assigns the value of the name parameter to the name
attribute of the object.
■ self.breed = breed assigns the value of the breed parameter to the
breed attribute of the object.
■ self.age = age assigns the value of the age parameter to the age attribute
of the object.

Purpose of __init__:

● Initialization: The primary purpose of __init__ is to initialize the object's attributes when
the object is created.
● Setting Initial State: It allows you to set the initial state of an object, ensuring that it has the
necessary data to function correctly.
● Automatic Execution: Python automatically calls the __init__ method when you create an
object, so you don't have to call it explicitly.
Practical No. 21: Write a python program to demonstrate the use of constructors:
1. Default 2. Parameterized 3. Constructor Overloading

What is a Constructor?

● A constructor is a special method in a class that is automatically called (invoked) when an


object of that class is created (instantiated).
● Its primary purpose is to initialize the attributes (data) of the newly created object.
● In Python, the constructor method is always named __init__.

Key Characteristics of Constructors in Python:

1. Special Method Name:

Constructors in Python have a fixed name: __init__. The double underscores



(dunderscores) at the beginning and end of the name indicate that it's a special
method with a predefined meaning.
2. Automatic Invocation:

○ You don't need to call the constructor explicitly. When you create an object using the
class name followed by parentheses (like my_object = MyClass()), Python
automatically calls the __init__ method.
3. Initialization of Attributes:

○The most common use of a constructor is to set the initial values of an object's
attributes.
○ Attributes are variables that store data associated with an object.
○ Inside the constructor, you use the self keyword to refer to the object itself and
assign values to its attributes.
4. self Parameter:

○Every method in a class definition, including the constructor, must have self as its
first parameter.
○ self is a reference to the instance (the object) that the method is called on.
○ When you create an object, Python automatically passes the object itself as the self
argument.
○ You use self to access and modify the object's attributes within the method.
5. Return Value:

○ Constructors in Python implicitly return None. You should not explicitly return any
value from the __init__ method.

Types of Constructors (with nuances in Python):

● Default Constructor:

○ A constructor that takes no arguments (other than self).


○ If you don't define any constructor in your class, Python provides a default constructor
implicitly.
○ However, if you define any constructor (even a parameterized one), Python does not
provide the default constructor.
○ In Python, you effectively lose the default constructor when you define any custom
constructor.
● Parameterized Constructor:

○ A constructor that takes one or more arguments in addition to self.


○ It's used to initialize the object's attributes with specific values provided when the
object is created.
○ This is the most common type of constructor you'll use.
● Constructor Overloading (Simulation):

○ Constructor overloading means defining multiple constructors in a class with different


parameter lists.
○ Some languages (like C++ or Java) support constructor overloading directly.
○ Python does not support constructor overloading in the same way. If you define
multiple __init__ methods, the last one defined will override any previous ones.
○ Instead, we simulate constructor overloading in Python using default argument values
and conditional logic within a single __init__ method.

class Car:
def __init__(self, make="Unknown", model="Unknown", year=2000):
"""
Constructor for the Car class.
Args:
make (str, optional): The make of the car. Defaults to "Unknown".
model (str, optional): The model of the car. Defaults to Unknown".
year (int, optional): The year the car was made. Defaults to 2000.
"""
self.make = make
self.model = model
self.year = year
self.odometer = 0 # Initialize odometer to 0

def drive(self, miles):


"""Simulates driving the car."""
self.odometer += miles
return f"Drove {miles} miles. Total odometer: {self.odometer}"

# Creating Car objects


my_car = Car("Toyota", "Camry", 2022) # Parameterized constructor
another_car = Car() # Simulated default constructor (using defaults)

print(my_car.make) # Output: Toyota


print(another_car.model) # Output: Unknown
print(my_car.drive(100)) # Output: Drove 100 miles. Total odometer: 100
In summary: Constructors are vital for setting up objects when they are created. Python uses the
__init__ method for this purpose, and while it doesn't support overloading directly, you can achieve
similar flexibility using default arguments.

# Practical No. 21: Constructors in Python

class MyClass:
# 1. Default Constructor
def __init__(self):
"""Default constructor with no parameters."""
self.message = "Default Constructor Called"
print("Default Constructor Called")

# 2. Parameterized Constructor
def __init__(self, value):
"""Parameterized constructor with one parameter."""
self.value = value
print(f"Parameterized Constructor Called with value: {value}")

def display(self):
"""Displays the value or message."""
try:
print(f"Value: {self.value}")
except AttributeError:
print(self.message)

# Demonstration

# Using the parameterized constructor


obj1 = MyClass(10)
obj1.display()

# Using the parameterized constructor again with a different value


obj2 = MyClass("Hello")
obj2.display()

# Trying to use the default constructor directly will lead to an error because
# the parameterized constructor overrides the default one.
# obj3 = MyClass() # This will cause a TypeError

# 3. Constructor Overloading (Simulated using Default Arguments)


class OverloadedClass:
def __init__(self, arg1=None, arg2=None):
"""Simulated constructor overloading using default arguments."""
if arg1 is None and arg2 is None:
self.message = "Default Constructor Called (Simulated)"
print("Default Constructor Called (Simulated)")
elif arg1 is not None and arg2 is None:
self.value = arg1
print(f"Parameterized Constructor Called (Simulated) with value:
{arg1}")
elif arg1 is not None and arg2 is not None:
self.value1 = arg1
self.value2 = arg2
print(f"Parameterized Constructor Called (Simulated) with values:
{arg1}, {arg2}")

def display(self):
"""Displays the values or message."""
try:
print(f"Value 1: {self.value1}, Value 2: {self.value2}")
except AttributeError:
try:
print(f"Value: {self.value}")
except AttributeError:
print(self.message)

# Demonstration of Simulated Constructor Overloading


obj4 = OverloadedClass()
obj4.display()

obj5 = OverloadedClass(20)
obj5.display()

obj6 = OverloadedClass(30, 40)


obj6.display()

Explanation and How to Run:

1. Save the Code:

○ Save the Python code as a .py file (e.g., constructors_demo.py).


2. Run the Script:

○ Open a terminal or command prompt.


○ Navigate to the directory where you saved the file.
○ Run the command python constructors_demo.py.

Concepts Demonstrated:

● 1. Default Constructor:
○ A constructor with no parameters. In Python, if you define a parameterized constructor,
the default constructor is implicitly overridden.
● 2. Parameterized Constructor:
○ A constructor with one or more parameters.
● 3. Constructor Overloading (Simulated):
○ Python does not support true constructor overloading like some other languages (e.g.,
C++ or Java).
○ However, you can simulate constructor overloading by using default arguments in a
single constructor.
○ By checking if the arguments are none, we can make the constructor act as multiple
constructors.
○ This allows you to create objects with different sets of arguments.

Key Points:

● In Python, if you define a parameterized constructor, you cannot create an object using the
default constructor (without arguments).
● The __init__ method is automatically called when an object is created.
● The try...except blocks in the display methods handle the case where the object might
not have a certain attribute (e.g., self.value).
● Constructor overloading in Python is generally simulated using default argument values and
conditional logic within a single __init__ method.
Practical No. 22: Implement a python program to demonstrate
1. Method Overloading 2. Method Overriding

Method Overloading and Method Overriding, two important aspects of object-oriented programming
(OOP).

1. Method Overloading

● Definition:

○ Method overloading is the ability of a class to have multiple methods with the same
name but different parameter lists (different number, types, or order of parameters).
○ The compiler or interpreter selects the correct method to call based on the arguments
passed during the method invocation.
● Purpose:

○ Method overloading provides a way to implement methods that perform similar actions
but can handle different types or amounts of input data.
○ It enhances code flexibility and readability by allowing you to use the same method
name for related operations.
● How it Works (in theory):

○ When you call an overloaded method, the compiler/interpreter examines the


arguments you've provided.
○ It then chooses the method with the signature (name and parameter types) that best
matches the arguments.
● Method Overloading in Python:

○ Python does not support method overloading in the traditional sense like some other
languages (e.g., C++, Java).
○ If you define multiple methods with the same name in a Python class, the last definition
will override any previous definitions.
○ However, we can achieve similar functionality using default arguments, variable-length
argument lists (*args, **kwargs), and conditional logic within a single method.
● Example (Simulation of Method Overloading in Python):

class Calculator:
def add(self, a, b=None, c=None):
"""
Simulates method overloading for addition.
"""
if b is None and c is None:
return a # Returns a if only one argument is provided
elif b is not None and c is None:
return a + b
elif b is not None and c is not None:
return a + b + c
else:
return 0 # Or handle this case as needed
calc = Calculator()
print(calc.add(5)) # Output: 5
print(calc.add(5, 10)) # Output: 15
print(calc.add(5, 10, 15)) # Output: 30

2. Method Overriding

● Definition:

○ Method overriding occurs in inheritance when a subclass (derived class) provides a


different implementation for a method that is already defined in its superclass (base
class).
○ When you call the overridden method on an object of the subclass, the subclass's
version of the method is executed, not the superclass's version.
● Purpose:

○ Method overriding allows subclasses to specialize or modify the behavior inherited


from their superclasses.
○ It's a key mechanism for achieving polymorphism, where objects of different classes
can respond to the same method call in different ways.
● How it Works:

○ The subclass defines a method with the same name and the same signature
(parameter list) as a method in its superclass.
○ When the method is called on an object of the subclass, Python's method resolution
order (MRO) ensures that the subclass's method is found and executed first.
● Method Overriding in Python:

○ Python supports method overriding directly. You simply define a method with the same
name in the subclass.
● Example:

class Animal:
def make_sound(self):
"""
Generic sound-making method.
"""
return "Generic animal sound"

class Dog(Animal):
def make_sound(self):
"""
Overrides the make_sound method for dogs.
"""
return "Woof!"
class Cat(Animal):
def make_sound(self):
"""
Overrides the make_sound method for cats.
"""
return "Meow!"

animal = Animal()
dog = Dog()
cat = Cat()

print(animal.make_sound()) # Output: Generic animal sound


print(dog.make_sound()) # Output: Woof!
print(cat.make_sound()) # Output: Meow!

Key Differences Summarized:

● Overloading:
○ Same method name, different parameter lists (within the same class).
○ Python "simulates" it using default arguments, etc.
● Overriding:
○ Same method name, same signature (in different classes related by inheritance).
○ Used for specialization in subclasses.

# Practical No. 22: Method Overloading and Overriding in Python

# 1. Method Overloading (Simulated)

class Calculator:
def add(self, a, b=None, c=None):
"""
Simulates method overloading for addition.
"""
if b is None and c is None:
return a # Returns a if only one argument is provided
elif b is not None and c is None:
return a + b
elif b is not None and c is not None:
return a + b + c
else:
return 0 # Or handle this case as needed

def multiply(self, a, b=1): # Another example with default argument


"""
Simulates method overloading for multiplication.
"""
return a * b

# Demonstration of Method Overloading (Simulated)


calc = Calculator()
print("Method Overloading (Simulated):")
print("calc.add(5):", calc.add(5))
print("calc.add(5, 10):", calc.add(5, 10))
print("calc.add(5, 10, 15):", calc.add(5, 10, 15))
print("calc.multiply(5):", calc.multiply(5))
print("calc.multiply(5, 4):", calc.multiply(5, 4))
print("-" * 30)

# 2. Method Overriding

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

def make_sound(self):
"""
Generic sound-making method.
"""
return "Generic animal sound"

def get_info(self):
"""Returns information about the animal."""
return f"This animal is a {type(self).__name__} named {self.name}."

class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # Call the superclass constructor
self.breed = breed

def make_sound(self):
"""
Overrides the make_sound method for dogs.
"""
return "Woof!"
def get_info(self):
"""Overrides get_info to include breed information"""
return f"{self.name} is a {self.breed} Dog."

class Cat(Animal):
def __init__(self, name, color):
super().__init__(name) # Call the superclass constructor
self.color = color

def make_sound(self):
"""
Overrides the make_sound method for cats.
"""
return "Meow!"

def get_info(self):
"""Overrides get_info to include color information"""
return f"{self.name} is a {self.color} Cat."

# Demonstration of Method Overriding


print("Method Overriding:")
animal = Animal("Generic")
dog = Dog("Buddy", "Golden Retriever")
cat = Cat("Whiskers", "Tabby")

print("animal.make_sound():", animal.make_sound())
print("dog.make_sound():", dog.make_sound())
print("cat.make_sound():", cat.make_sound())

print(animal.get_info())
print(dog.get_info())
print(cat.get_info())

Explanation and How to Run:

1. Save the Code:

○ Save the Python code as a .py file (e.g., method_demo.py).


2. Run the Script:

○ Open a terminal or command prompt.


○ Navigate to the directory where you saved the file.
○ Run the command python method_demo.py.

Key Concepts Demonstrated:

● 1. Method Overloading (Simulated):


○ The Calculator class demonstrates how to simulate method overloading using
default arguments.
○ The add() method can take one, two, or three arguments. The logic inside the
method handles different cases.
○ The multiply() method demonstrates another way by using a default value for the
second argument.
● 2. Method Overriding:
○ The Animal, Dog, and Cat classes demonstrate method overriding through
inheritance.
○ The Dog and Cat classes inherit from the Animal class.
○ The make_sound() method is defined in the Animal class and then overridden in the
Dog and Cat classes to provide specific implementations for each animal.
○ The get_info() method is also overridden to provide more specific information
about the derived classes.
○ The super() function is used to call the constructor of the superclass (Animal) from
the constructors of the subclasses (Dog and Cat). This is good practice to ensure
proper initialization of inherited attributes.
Practical No. 23: Write python program to demonstrate data hiding

Data hiding is a fundamental concept in object-oriented programming (OOP) that refers to the
practice of restricting access to certain components of an object. It's about controlling the visibility of
an object's data (attributes) and methods.

The Core Idea:

The primary goal of data hiding is to protect the internal state of an object from unintended or
inappropriate external access. It's about saying, "These parts of the object are internal details; don't
mess with them directly."

Why is Data Hiding Important?

● Preventing Accidental Modification: Data hiding helps prevent accidental changes to an


object's data that could lead to inconsistent or erroneous states. By hiding internal data, you
reduce the risk of bugs caused by external code directly manipulating the object's attributes in
unexpected ways.
● Encapsulation: Data hiding is a key aspect of encapsulation, one of the core principles of
OOP. Encapsulation is the bundling of data and the methods that operate on that data within a
single unit (an object). Data hiding strengthens encapsulation by defining a clear boundary
between the object's internal state and its external interface.
● Abstraction: Data hiding supports abstraction by allowing you to hide the complex internal
implementation details of an object. Users of the object only need to know how to interact with
its public interface (its methods) without needing to understand how it works internally. This
simplifies the use of objects and reduces dependencies between different parts of a program.
● Modularity: Data hiding promotes modularity by creating self-contained objects with well-
defined interfaces. This makes it easier to develop, test, and maintain individual components
of a program independently.
● Flexibility and Maintainability: By hiding internal data, you gain the flexibility to change the
internal implementation of an object without affecting the code that uses it. If the public
interface remains the same, external code will continue to work correctly even if the internal
data representation or algorithms are modified. This improves the maintainability of the code
by allowing you to make changes without introducing unintended side effects in other parts of
the system.

How Data Hiding is Achieved (in General OOP):

Different object-oriented languages provide various mechanisms to achieve data hiding:

● Access Modifiers: Many languages use access modifiers (also called visibility modifiers) to
control access to class members (attributes and methods). Common access modifiers include:
○ Public: Members declared as public are accessible from anywhere.
○ Private: Members declared as private are only accessible within the class itself.
○ Protected: Members declared as protected are accessible within the class itself and
its subclasses.

Data Hiding in Python:

Python takes a slightly different approach to data hiding compared to languages like Java or C++. It
relies more on convention and a concept called "name mangling" rather than strict access modifiers.
● Naming Conventions:
○ Single Underscore Prefix (_): A single underscore prefix before an attribute or
method name (e.g., _my_attribute) is a convention that indicates to other
programmers that this member is intended for internal use. It's a signal that you
shouldn't access it directly from outside the class. However, Python doesn't prevent
you from doing so. It's more of a guideline.
○ Double Underscore Prefix (__): A double underscore prefix (e.g.,
__my_attribute) triggers name mangling. Python interpreter renames the attribute
to _ClassName__my_attribute. This makes it harder (but not impossible) to
access the attribute from outside the class. Name mangling is primarily intended to
avoid namespace clashes in inheritance.

Example in Python:

class MyClass:
def __init__(self):
self.public_attribute = "This is public"
self._internal_attribute = "This is internal (by convention)"
self.__private_attribute = "This is 'private' (name mangled)"

def public_method(self):
print(self.public_attribute)
print(self._internal_attribute)
print(self.__private_attribute)

def _internal_method(self):
print("This is an internal method")

obj = MyClass()

print(obj.public_attribute) # Accessible
print(obj._internal_attribute) # Accessible (but discouraged)

# Trying to access __private_attribute directly will raise an AttributeError


# print(obj.__private_attribute) # This will result in an error

# You can access the name-mangled attribute like this (but it's generally not
recommended):
print(obj._MyClass__private_attribute)

obj.public_method() # Works fine


obj._internal_method() # Works (but discouraged)

Important Points about Data Hiding in Python:


● No True Private: Python doesn't have true private access modifiers like some other
languages. Name mangling makes it harder to access "private" members, but it doesn't
completely prevent it.
● Convention over Enforcement: Python emphasizes convention and programmer
responsibility. The single underscore prefix is a strong guideline, and programmers are
expected to respect it.
● Flexibility: Python's approach provides flexibility. You can still access "internal" members if
you really need to, but you should be aware that you might be breaking the intended design of
the class.

In summary, data hiding in Python is a mechanism to control access to the internal state of objects.
It's achieved through naming conventions and name mangling, promoting encapsulation, abstraction,
modularity, and maintainability. While Python's approach is based more on convention than strict
enforcement, it's an important practice for writing well-structured and robust object-oriented code.

# Practical No. 23: Data Hiding in Python

class BankAccount:
def __init__(self, account_number, balance):
self.account_number = account_number # Public attribute
self._balance = balance # Internal attribute (by convention)
self.__secret_pin = "1234" # Name-mangled attribute (simulating
private)

def deposit(self, amount):


if amount > 0:
self._balance += amount
print(f"Deposited {amount}. New balance: {self._balance}")
else:
print("Invalid deposit amount.")

def _internal_check_balance(self): # Internal method (by convention)


return self._balance

def __access_pin(self): #Name mangled method


return self.__secret_pin

def get_balance(self):
return self._internal_check_balance()

def authenticate_and_get_pin(self, attempt):


if attempt == self.__secret_pin:
return self.__access_pin()
else:
return "Incorrect Pin"
# Demonstration
account = BankAccount("123456789", 1000)

# Accessing public attribute


print("Account Number:", account.account_number)

# Accessing internal attribute (by convention) - Works, but discouraged


print("Current Balance (using internal attribute):", account._balance)

# Accessing internal method (by convention) - Works, but discouraged


print("Balance (using internal method):", account._internal_check_balance())

# Using the public interface to deposit and get balance


account.deposit(500)
print("Balance (using public get_balance method):", account.get_balance())

# Attempting to access the name-mangled attribute directly (will cause an


AttributeError)
# print(account.__secret_pin) # This will cause an error

# Accessing the name-mangled attribute using the mangled name (works, but
strongly discouraged)
print("Mangled pin access (strongly discouraged):",
account._BankAccount__secret_pin)

# Accessing name mangled method.


print(account.authenticate_and_get_pin("1234"))
print(account.authenticate_and_get_pin("0000"))

Explanation and How to Run:

1. Save the Code:

○ Save the Python code as a .py file (e.g., data_hiding_demo.py).


2. Run the Script:

○ Open a terminal or command prompt.


○ Navigate to the directory where you saved the file.
○ Run the command python data_hiding_demo.py.

Key Concepts Demonstrated:

● Public Attribute:
○ account_number is a public attribute, accessible from anywhere.
● Internal Attribute (by Convention):
○ _balance is an internal attribute. The single underscore indicates that it's intended for
internal use.
○ While you can access it from outside the class, it's considered bad practice.
● Name-Mangled Attribute (Simulating Private):
○ __secret_pin is a name-mangled attribute. Python renames it internally to
_BankAccount__secret_pin.
○ This makes it harder to access from outside the class, but it's not truly private.
● Internal Method (by Convention):
○ _internal_check_balance() is an internal method, following the single
underscore convention.
● Name-Mangled Method:
○ __access_pin() is a name mangled method that can only be accessed by the
mangled name or by calling a public method that calls it.
● Public Interface:
○ deposit() and get_balance() provide a public interface for interacting with the
BankAccount object.
○ These methods control access to the internal data and ensure that operations are
performed correctly.
● Accessing Mangled Attributes:
○ The example shows how to access the name-mangled attribute using its mangled
name (account._BankAccount__secret_pin). This is strongly discouraged
because it breaks encapsulation.
● Authentication:
○ The authenticate_and_get_pin() method demonstrates how to use the name
mangled pin in a controlled manner.

Important Notes:

● Python's data hiding is based more on convention than strict enforcement.


● The single underscore is a signal to other programmers that a member is intended for internal
use.
● Name mangling makes it harder to access "private" members, but it doesn't completely
prevent it.
● The goal is to encourage good programming practices and maintainability.

Practical No. 24: Write a python program to implement


1. Single inheritance 2. Multiple Inheritance 3. Multilevel inheritance

Inheritance is a fundamental concept in object-oriented programming (OOP) that allows you to create
new classes (called subclasses or derived classes) based on existing classes (called superclasses,
base classes, or parent classes). The subclass inherits the attributes and methods of the superclass,
which promotes code reuse and creates a hierarchical relationship between classes.

Here's a breakdown of inheritance in Python:

Key Concepts:

● Superclass (Base Class or Parent Class):


○ The existing class from which other classes inherit.
○ It defines the common attributes and methods that will be shared by its subclasses.
● Subclass (Derived Class or Child Class):
○ The new class that inherits from the superclass.
○ It can add new attributes and methods, as well as override or modify the inherited
ones.
● Inheritance Relationship ("is-a" Relationship):
○ Inheritance models an "is-a" relationship between classes.
○ For example, a "Dog" is-a "Animal."
● Code Reusability:
○ Inheritance promotes code reuse by allowing you to define common attributes and
methods in a superclass and then reuse them in multiple subclasses.

● Polymorphism:
○ Inheritance is closely related to polymorphism, which allows objects of different
classes to be treated as objects of a common superclass.

How Inheritance Works in Python:

1. Defining a Subclass:

○ To create a subclass, you specify the superclass in parentheses after the subclass's
name in the class definition.
○ class Subclass(Superclass):
2. Inheriting Attributes and Methods:

○ The subclass automatically inherits all the attributes and methods of the superclass.
○ You can access these inherited members using the subclass's objects.
3. Overriding Methods:

○A subclass can provide its own implementation of a method that is already defined in
the superclass. This is called method overriding.
○ When you call an overridden method on an object of the subclass, the subclass's
version of the method is executed.
4. Adding New Attributes and Methods:

○ A subclass can add new attributes and methods that are specific to that subclass.
5. Calling Superclass Methods Using super():

○ The super() function allows you to call methods of the superclass from within the
subclass.
○ This is particularly useful when you want to extend the functionality of a superclass
method rather than completely replace it.

Example:

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

def make_sound(self):
return "Generic animal sound"
def get_name(self):
return self.name

class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # Call the Animal constructor
self.breed = breed

def make_sound(self):
return "Woof!" # Override the Animal method

def get_breed(self):
return self.breed

# Creating objects
animal = Animal("Generic Animal")
dog = Dog("Buddy", "Golden Retriever")

# Accessing methods
print(animal.make_sound()) # Output: Generic animal sound
print(dog.make_sound()) # Output: Woof!

print(animal.get_name()) #output: Generic Animal.


print(dog.get_name()) #output: Buddy.

print(dog.get_breed()) #output: Golden Retriever.

Explanation:

● The Animal class is the superclass.


● The Dog class is the subclass, inheriting from Animal.
● The Dog class overrides the make_sound() method.
● The Dog class adds a new attribute (breed) and a new method (get_breed()).
● The super().__init__(name) line calls the constructor of the Animal class to initialize
the name attribute.

Benefits of Inheritance:

● Code Reuse: Reduces code duplication.


● Maintainability: Easier to modify and extend code.
● Organization: Creates a clear hierarchy of classes.
● Polymorphism: Enables objects of different subclasses to be treated as objects of the
superclass.
# Practical No. 24: Inheritance in Python

# 1. Single Inheritance

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

def speak(self):
return "Generic animal sound"

class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # Call the Animal constructor
self.breed = breed

def speak(self):
return "Woof!" # Override the Animal method

def get_breed(self):
return self.breed

print("--- Single Inheritance ---")


dog = Dog("Buddy", "Golden Retriever")
print(f"{dog.name} ({dog.breed}) says: {dog.speak()}")
print(f"Breed: {dog.get_breed()}")
print("-" * 20)

# 2. Multiple Inheritance

class Swimmer:
def swim(self):
return "Swimming..."

class Walker:
def walk(self):
return "Walking..."

class Frog(Animal, Swimmer, Walker): #Multiple inheritance


def __init__(self, name):
super().__init__(name)
def speak(self):
return "Croak!"

print("--- Multiple Inheritance ---")


frog = Frog("Ribbit")
print(f"{frog.name} says: {frog.speak()}")
print(f"{frog.name} says: {frog.swim()}")
print(f"{frog.name} says: {frog.walk()}")
print("-" * 20)

# 3. Multilevel Inheritance

class Vehicle:
def __init__(self, make, model):
self.make = make
self.model = model

def start(self):
return "Vehicle started"

class Car(Vehicle):
def __init__(self, make, model, num_doors):
super().__init__(make, model)
self.num_doors = num_doors

def drive(self):
return "Car is driving"

class SportsCar(Car):
def __init__(self, make, model, num_doors, top_speed):
super().__init__(make, model, num_doors)
self.top_speed = top_speed

def accelerate(self):
return "Sports car accelerating!"

print("--- Multilevel Inheritance ---")


sports_car = SportsCar("Ferrari", "F8 Tributo", 2, "211 mph")
print(f"{sports_car.make} {sports_car.model} ({sports_car.num_doors} doors)
has a top speed of {sports_car.top_speed}")
print(sports_car.start())
print(sports_car.drive())
print(sports_car.accelerate())

Explanation and How to Run:

1. Save the Code:


○ Save the Python code as a .py file (e.g., inheritance_demo.py).
2. Run the Script:
○ Open a terminal or command prompt.
○ Navigate to the directory where you saved the file.
○ Run the command python inheritance_demo.py.

Concepts Demonstrated:

● 1. Single Inheritance:
○ The Dog class inherits from the Animal class.
○ The Dog class overrides the speak() method and adds a get_breed() method.
● 2. Multiple Inheritance:
○ The Frog class inherits from Animal, Swimmer, and Walker.
○ The Frog class inherits and uses methods from all three parent classes.
● 3. Multilevel Inheritance:
○ The SportsCar class inherits from the Car class, which in turn inherits from the
Vehicle class.
○ This creates a chain of inheritance, where SportsCar inherits attributes and methods
from both Car and Vehicle.
○ The use of super() is demonstrated to call the constructor of the parent class.

Key Points:

● super() is used to call the constructor of the superclass, ensuring that the superclass's
attributes are initialized.
● Method overriding allows subclasses to provide their own implementations of methods defined
in superclasses.
● Multiple inheritance allows a class to inherit from multiple parent classes.
● Multilevel inheritance enables a class to inherit from a class that inherits from another class,
creating a hierarchy.
Practical No. 25: Implement Python program to perform following operations using panda
package:
1. Create Series from Array
2. Create Series from List
3. Access element of series
4. Create DataFrame using List or dictionary

Pandas is a powerful Python library used for data manipulation and analysis. It provides data
structures for efficiently storing and working with structured data, such as tables (similar to
spreadsheets or SQL tables). Here's a breakdown of the Pandas package and its key features:

Core Data Structures:

● Series: A one-dimensional labeled array capable of holding any data type (integers, strings,
floating-point numbers, Python objects, etc.). You can think of it as a single column of a
spreadsheet.
● DataFrame: A two-dimensional labeled data structure with columns of potentially different data
types. It's like a spreadsheet or a SQL table, with rows and columns.

Key Functionality:

● Data Input/Output:
○ pd.read_csv(filepath_or_buffer, ...): Reads data from a CSV (Comma
Separated Values) file.
○ pd.read_excel(io, ...): Reads data from an Excel file.
○ pd.read_sql(sql, con, ...): Reads data from a SQL database.
○ df.to_csv(filepath_or_buffer, ...): Writes DataFrame to a CSV file.
○ df.to_excel(excel_writer, ...): Writes DataFrame to an Excel file.
○ df.to_sql(name, con, ...): Writes DataFrame to a SQL database.
● Data Selection/Indexing:
○ df[colname]: Selects a column by its label (name).
○ df[[col1, col2, ...]]: Selects multiple columns.
○ df.loc[row_label, col_label]: Accesses a single value by label.
○ df.iloc[row_position, col_position]: Accesses a single value by integer
position.
○ df.loc[start_row:end_row, start_col:end_col]: Selects rows and
columns by label.
○ df.iloc[start_row:end_row, start_col:end_col]: Selects rows and
columns by integer position.
○ Boolean indexing: Selects rows based on a condition (e.g., df[df['column'] >
value]).
● Data Manipulation:
○ df.head(n): Returns the first n rows.
○ df.tail(n): Returns the last n rows.
○ df.info(): Provides information about the DataFrame (data types, non-null
values, etc.).
○ df.describe(): Generates descriptive statistics (mean, std, min, max, etc.) for
numeric columns.
○ df.shape: Returns the dimensions (rows, columns) of the DataFrame.
○ df.dtypes: Returns the data types of each column.
○ df.sort_values(by, ...): Sorts the DataFrame by one or more columns.
○ df.drop(labels, axis, ...): Removes rows or columns.
○ df.fillna(value): Fills missing values.
○ df.dropna(...): Removes rows or columns with missing values.
○ df.apply(func, axis): Applies a function along an axis (rows or columns).
○ df.groupby(by, ...): Groups data based on one or more columns for
aggregation.
○ df.merge(right, how, on, ...): Joins two DataFrames based on a
common column.
○ df.concat(objs, axis, ...): Concatenates DataFrames along an axis.
○ df.pivot_table(values, index, columns, ...): Creates a pivot table.
● Data Cleaning:
○ Handling missing values (e.g., fillna(), dropna()).
○ Removing duplicates (e.g., df.drop_duplicates()).
○ Converting data types (e.g., df['column'].astype(dtype)).
○ Cleaning string data (e.g., using string methods like str.replace(),
str.lower()).

import pandas as pd

# Create a DataFrame
data = {'Name': ['Alice', 'Bob', 'Charlie', 'David'],
'Age': [25, 30, 28, 35],
'City': ['New York', 'Los Angeles', 'Chicago', 'Houston']}
df = pd.DataFrame(data)

print("Original DataFrame:\n", df)

# Read from CSV


# df_csv = pd.read_csv('my_data.csv')

# Select a column
print("\nSelected column 'Name':\n", df['Name'])

# Access a value by label


print("\nAge of Alice:", df.loc[0, 'Age'])

# Filter rows
print("\nPeople older than 29:\n", df[df['Age'] > 29])

# Calculate mean age


mean_age = df['Age'].mean()
print("\nMean age:", mean_age)
# Sort by age
df_sorted = df.sort_values(by='Age')
print("\nSorted DataFrame:\n", df_sorted)

1. Create Series from Array


2. Create Series from List
3. Access element of series
4. Create DataFrame using List or dictionary

import pandas as pd
import numpy as np

# 1. Create Series from Array

# Creating a Series using a NumPy array


arr = np.array([10, 20, 30, 40, 50])
series_from_array = pd.Series(arr)
print("Series from Array:\n", series_from_array)

# You can also provide custom indices


series_from_array_custom_index = pd.Series(arr, index=['A', 'B', 'C', 'D',
'E'])
print("\nSeries from Array with Custom Index:\n",
series_from_array_custom_index)

# 2. Create Series from List

# Creating a Series using a Python list


my_list = ['apple', 'banana', 'orange', 'grape']
series_from_list = pd.Series(my_list)
print("\nSeries from List:\n", series_from_list)

# You can also provide custom indices


series_from_list_custom_index = pd.Series(my_list, index=[100, 101, 102, 103])
print("\nSeries from List with Custom Index:\n",
series_from_list_custom_index)

# 3. Access element of Series

# Accessing elements of a Series


print("\nAccessing elements of series_from_array:")
print("Element at index 0:", series_from_array[0])
print("Element at index 3:", series_from_array[3])

print("\nAccessing elements of series_from_array_custom_index:")


print("Element at index 'A':", series_from_array_custom_index['A'])
print("Element at index 'D':", series_from_array_custom_index['D'])

# Accessing elements using .loc (label-based access)


print("\nAccessing elements using .loc:")
print("Element at index 'C' (using .loc):",
series_from_array_custom_index.loc['C'])
print("Element at index 102 (using .loc):",
series_from_list_custom_index.loc[102])

# Accessing elements using .iloc (integer-based access)


print("\nAccessing elements using .iloc:")
print("Element at position 1 (using .iloc):", series_from_array.iloc[1])
print("Element at position 2 (using .iloc):", series_from_list.iloc[2])

# 4. Create DataFrame using List or Dictionary

# Creating a DataFrame using a List of Lists


data_list_of_lists = [['John', 25, 'New York'],
['Emily', 30, 'London'],
['Peter', 28, 'Paris']]
df_from_list_of_lists = pd.DataFrame(data_list_of_lists, columns=['Name',
'Age', 'City'])
print("\nDataFrame from List of Lists:\n", df_from_list_of_lists)

# Creating a DataFrame using a List of Dictionaries


data_list_of_dicts = [{'Name': 'John', 'Age': 25, 'City': 'New York'},
{'Name': 'Emily', 'Age': 30, 'City': 'London'},
{'Name': 'Peter', 'Age': 28, 'City': 'Paris'}]
df_from_list_of_dicts = pd.DataFrame(data_list_of_dicts)
print("\nDataFrame from List of Dictionaries:\n", df_from_list_of_dicts)

# Creating a DataFrame using a Dictionary of Lists


data_dict_of_lists = {'Name': ['John', 'Emily', 'Peter'],
'Age': [25, 30, 28],
'City': ['New York', 'London', 'Paris']}
df_from_dict_of_lists = pd.DataFrame(data_dict_of_lists)
print("\nDataFrame from Dictionary of Lists:\n", df_from_dict_of_lists)

Explanation and How to Run:

1. Install Pandas:
pip install pandas

2. Save the Code:

○ Save the code as a Python file (e.g., pandas_operations.py).


3. Run the Script:

○ Open a terminal or command prompt.


○ Navigate to the directory where you saved the file.
○ Run the command python pandas_operations.py.

Key Concepts Demonstrated:

● Series Creation:
○ pd.Series(data, index): Creating Series from NumPy arrays and Python
lists, with and without custom indices.
● Series Element Access:
○ Using square brackets `` for basic indexing.
○ .loc for label-based indexing (using index names).
○ .iloc for integer-based indexing (using numerical positions).
● DataFrame Creation:
○ pd.DataFrame(data, columns): Creating DataFrames from:
■ List of Lists
■ List of Dictionaries
■ Dictionary of Lists
Practical No. 26: Implement python program to load a CSV file into a Pandas DataFrame and
perform operations.

import pandas as pd

# 1. Load a CSV file into a Pandas DataFrame

# Assuming you have a CSV file named 'data.csv' in the same directory
# You can replace 'data.csv' with the actual path to your CSV file.

try:
df = pd.read_csv('data.csv') # Read the CSV file into a DataFrame
print("CSV file loaded successfully!\n")
except FileNotFoundError:
print("Error: CSV file not found. Please make sure 'data.csv' exists in
the same directory.")
exit() # Exit the program if the file is not found

# 2. Perform Operations on the DataFrame

# Display the first few rows


print("First 5 rows of the DataFrame:\n", df.head())

# Display the last few rows


print("\nLast 5 rows of the DataFrame:\n", df.tail())

# Get information about the DataFrame (data types, non-null values, etc.)
print("\nDataFrame Information:\n", df.info())

# Get descriptive statistics for numeric columns


print("\nDescriptive Statistics:\n", df.describe())

# Get the shape of the DataFrame (number of rows and columns)


print("\nDataFrame Shape (Rows, Columns):", df.shape)

# Get the data types of each column


print("\nData Types of Columns:\n", df.dtypes)

# Select a column by name


print("\n'Name' column:\n", df['Name'])

# Select multiple columns


print("\n'Name' and 'Age' columns:\n", df[['Name', 'Age']])

# Access a specific value using loc (label-based)


print("\nValue at row 0, column 'Age':", df.loc[0, 'Age'])

# Access a specific value using iloc (integer-based)


print("\nValue at row 1, column 2 (integer position):", df.iloc[1, 2])

# Filter rows based on a condition


print("\nRows where Age is greater than 30:\n", df[df['Age'] > 30])

# Calculate the mean of a numeric column


try:
mean_age = df['Age'].mean()
print("\nMean Age:", mean_age)
except KeyError:
print("\nError: 'Age' column not found.")

# Sort the DataFrame by a column


try:
df_sorted = df.sort_values(by='Age')
print("\nDataFrame sorted by Age:\n", df_sorted)
except KeyError:
print("\nError: 'Age' column not found.")

# Example of adding a new column


try:
df['Age_Plus_5'] = df['Age'] + 5
print("\nDataFrame with a new column 'Age_Plus_5':\n", df)
except KeyError:
print("\nError: 'Age' column not found.")

# Example of filling missing values (if any)


# df.fillna(0, inplace=True) # Fills missing values with 0
# df.dropna(inplace=True) # Removes rows with missing values

# Remember to replace 'data.csv' with the actual name of your CSV file.

Explanation and How to Run:


1. Create a CSV File (data.csv):

○ You'll need a CSV file to test this code. You can create a simple one like this:

Code snippet

Name,Age,City

Alice,25,New York

Bob,30,Los Angeles

Charlie,35,Chicago

David,28,Houston

○ Save this data as a file named data.csv in the same directory as your Python script.
2. Install Pandas:

pip install pandas


3. Save the Code:
Save the provided Python code as a .py file (e.g., csv_operations.py).
4. Run the Script:
Open a terminal or command prompt.
○ Navigate to the directory where you saved data.csv and csv_operations.py.
○ Run the command python csv_operations.py.

Key Concepts Demonstrated:

● Loading CSV: pd.read_csv() is used to load data from a CSV file into a Pandas
DataFrame.
● Basic DataFrame Inspection:
○ df.head() and df.tail(): Displaying the first and last rows.
○ df.info(): Getting DataFrame information.
○ df.describe(): Descriptive statistics.
○ df.shape: DataFrame dimensions.
○ df.dtypes: Column data types.
● Data Selection:
○ df['column']: Selecting a column.
○ df[['col1', 'col2']]: Selecting multiple columns.
○ df.loc[row_label, col_label]: Accessing values by label.
○ df.iloc[row_position, col_position]: Accessing values by integer position.
○ Boolean indexing: Filtering rows based on a condition.
● Data Manipulation:
○ Calculating the mean of a column.
○ Sorting the DataFrame.
○ Adding a new column.
● Error Handling:
○ A try...except block is used to handle the FileNotFoundError in case the CSV
file is not found.
○ KeyError exceptions are handled when trying to access or operate on columns that
might not exist in the CSV.
Tkinter is the standard Python interface to the Tk GUI toolkit shipped with Python. It's a cross-platform
toolkit, meaning you can create GUI applications that run on Windows, macOS, and Linux with the
same code.

Here's a detailed overview of Tkinter:

Key Features and Concepts:

● Standard Library: Tkinter is part of the Python standard library, so you don't need to install
any additional packages.

● Simple and Easy to Learn: Tkinter is relatively easy to learn and use, especially for
beginners.

● Cross-Platform: Applications created with Tkinter can run on different operating systems
without significant modifications.

● Widgets: Tkinter provides a wide range of GUI elements called widgets, such as buttons,
labels, text boxes, menus, and more.

● Event-Driven Programming: Tkinter applications are event-driven, meaning they respond to
user actions (events) like button clicks or keyboard input.

● Layout Managers: Tkinter provides layout managers (like pack, grid, and place) to control
the arrangement of widgets within the window.

Core Components:

● tkinter Module: The main module that provides access to all Tkinter classes and functions.
● Tk Class: The top-level window of the application.

● Widgets:
○ Label: Displays text or images.
○ Button: Creates clickable buttons.
○ Entry: Creates single-line text input fields.
○ Text: Creates multi-line text input fields.
○ Frame: Creates container widgets to group other widgets.
○ Canvas: Provides a drawing surface.
○ Checkbutton: Creates checkboxes.
○ Radiobutton: Creates radio buttons.
○ Listbox: Creates lists of items.
○ Menu: Creates menus.
○ Messagebox: displays pop up message boxes.
○ And many more...
● Layout Managers:
○ pack(): Arranges widgets in simple layouts.

○ grid(): Arranges widgets in a grid (rows and columns).

○ place(): Arranges widgets at specific coordinates.

● Events:
○ Tkinter uses an event-driven model.
○ Events are user actions or system events (e.g., button clicks, key presses, window
resizing).
○ You can bind event handlers (functions) to events to respond to them.

● Variables:
○ Tkinter provides variable classes (like StringVar, IntVar, BooleanVar, and
DoubleVar) to manage widget data.
○ These variables are linked to widget properties, so changes to the variables are
reflected in the widgets, and vice versa.

Basic Tkinter Program Structure:

Import tkinter:
Python
import tkinter as tk

Create the main window:


Python
window = tk.Tk()

Create widgets:
Python
label = tk.Label(window, text="Hello, Tkinter!")
button = tk.Button(window, text="Click Me")

Arrange widgets using layout managers:


Python
label.pack()
button.pack()

Start the event loop:


Python
window.mainloop()

Example (Adding a Button and Label):

Python
import tkinter as tk

window = tk.Tk()
window.title("Simple Tkinter Example")

label = tk.Label(window, text="Welcome to Tkinter!")


label.pack()

def button_click():
label.config(text="Button Clicked!")

button = tk.Button(window, text="Click Here", command=button_click)


button.pack()

window.mainloop()

Explanation of the Example:

● A label and a button are created.


● The command parameter of the button is set to the button_click function, which will be
called when the button is clicked.
● The label.config() method is used to change the text of the label.

Advantages of Tkinter:

● Part of the standard library.



● Easy to learn.
● Cross-platform.

Disadvantages of Tkinter:

● Can have a "dated" look and feel compared to more modern GUI toolkits.

● May not be as powerful or feature-rich as some other GUI libraries.

Tkinter is a great starting point for learning GUI programming in Python. As you become more
experienced, you might explore other GUI toolkits like PyQt, Kivy, or wxPython, which offer more
advanced features and customization options.

Python
import tkinter as tk

# Create a main window


window = tk.Tk()

# Set the title of the window


window.title("My Tkinter Window")
# Start the GUI event loop
window.mainloop()

Explanation:

1. import tkinter as tk:


This line imports the Tkinter library, which is the standard Python interface to the Tk
GUI toolkit.
○ as tk is an alias, making it easier to refer to Tkinter components (e.g., tk.Label
instead of tkinter.Label).
2. window = tk.Tk():

○ This creates the main application window.


○ tk.Tk() initializes the Tkinter interpreter and creates a top-level window.
○ The window variable now holds a reference to this window object.
3. window.title("My Tkinter Window"):

○ This sets the title of the window to "My Tkinter Window."


○ The title will appear in the window's title bar.
4. window.mainloop():

○ This starts the Tkinter event loop.


○ The event loop listens for events (like button clicks, keyboard input, etc.) and
processes them.
○ Without mainloop(), the window would appear briefly and then disappear.
○ The program will continue to run until the window is closed.

How to Run:

1. Save the code: Save the code as a .py file (e.g., tkinter_window.py).
2. Run the script: Open a terminal or command prompt, navigate to the directory where you
saved the file, and run python tkinter_window.py.

A simple window with the title "My Tkinter Window" will appear on your screen.

You might also like