Python Notes Units1-5
Python Notes Units1-5
num = -8
# print the data type
print(type(num))
Output:
<class 'int'>
Example 2: Performing arithmetic Operations on int type
a=5
b=6
c=a+b
print("Addition:",c)
c=a-b
print("Subtraction:",c)
c = a // b
print("Division:",c)
c=a*b
print("Multiplication:",c)
c=a%b
print("Modulus:",c)
c = a ** b
print("Exponent:",c)
Output:
Addition: 11
Subtraction: 3
Division: 4
Multiplication: 15
Modulus: 0
Exponent: 36
Python Float
This is a real number with a floating-point representation. It is specified by a decimal
point. Optionally, the character e or E followed by a positive or negative integer may
be appended to specify scientific notation.
Examples : 0.5 and -7.823457.
They can be created directly by entering a number with a decimal point, or by using
operations such as division on integers. Extra zeros present at the number’s end are
ignored automatically.
Example 1: Creating float and checking type
num = -8.7844216
# print the data type
print(type(num))
Output:
<class 'float'>
Example 2: Performing arithmetic Operations on the float type
a = 5.5
b = 3.2
c=a+b
print("Addition:", c)
c = a-b
print("Subtraction:", c)
c = a/b
print("Division:", c)
c = a*b
print("Multiplication:", c)
Output
Addition: 8.7
Subtraction: 2.3
Division: 1.71875
Multiplication: 17.6
Python Complex
A complex number is a number that consists of real and imaginary parts. For
example, 2 + 3j is a complex number where 2 is the real component, and 3 multiplied
by j is an imaginary part.
Example 1: Creating Complex and checking type
num = 6 + 9j
print(type(num))
Output:
<class 'complex'>
Example 2: Performing arithmetic operations on complex type
a = 1 + 5j
b = 2 + 3j
c=a+b
print("Addition:",c)
Output:
Addition: (3+8j)
Type Conversion in Python
We can convert one number into the other form by two methods:
i) Using Arithmetic Operations:
We can use operations like addition, and subtraction to change the type of number
implicitly(automatically), if one of the operands is float. This method is not working
for complex numbers.
Example: Type conversion using arithmetic operations
a = 1.6
b=5
c=a+b
print(c)
Output:
6.6
ii) Using built-in functions
We can also use built-in functions like int(), float() and complex() to convert into
different types explicitly.
Example: Type conversion using built-in functions
a=2
print(float(a))
b = 5.6
print(int(b))
Output:
2.0
5
__________________________________________________________________________
2. SET Operations
Introduction to Python Sets
A set is a collection of unique data, meaning that elements within a set cannot be
duplicated.
For instance, if we need to store information about student IDs, a set is suitable since
student IDs cannot have duplicates.
Create a Set in Python
● In Python, we create sets by placing all the elements inside curly braces {},
separated by commas.
● A set can have any number of items and they may be of different types
(integer, float, tuple, string, etc.).
Example,
student_id = {112, 114, 116, 118, 115}
print('Student ID:', student_id)
vowel_letters = {'a', 'e', 'i', 'o', 'u'}
print('Vowel Letters:', vowel_letters)
mixed_set = {'Hello', 101, -2, 'Bye'}
print('Set of mixed data types:', mixed_set)
Output
Student ID: {112, 114, 115, 116, 118}
Vowel Letters: {'u', 'a', 'e', 'i', 'o'}
Set of mixed data types: {'Hello', 'Bye', 101, -2}
Function Description
all() Returns True if all elements of the set are true (or if the set is empty).
any() Returns True if any element of the set is true. If the set is empty, returns
False.
We use the | operator or the union() method to perform the set union operation.
For example,
A = {1, 3, 5}
B = {0, 2, 4}
print('Union using |:', A | B)
print('Union using union():', A.union(B))
Output
Union using |: {0, 1, 2, 3, 4, 5}
Union using union(): {0, 1, 2, 3, 4, 5}
Set Intersection
The intersection of two sets A and B include the common elements between set A
and B.
In Python, we use the & operator or the intersection() method to perform the set
intersection operation.
For example,
A = {1, 3, 5}
B = {1, 2, 3}
print('Intersection using &:', A & B)
print('Intersection using intersection():', A.intersection(B))
Output
Intersection using &: {1, 3}
Intersection using intersection(): {1, 3}
We use the - operator or the difference() method to perform the difference between
two sets.
For example,
A = {2, 3, 5}
B = {1, 2, 6}
print('Difference using &:', A - B)
print('Difference using difference():', A.difference(B))
Output
Difference using &: {3, 5}
Difference using difference(): {3, 5}
3. Lists in Python
i) Introduction to Lists
ii) Creating a List in Python
iii) Accessing elements from the List
iv) Getting the size of Python list
v) Taking Input of a Python List
vi) Adding Elements to a Python List - append(), insert(), extend()
vii) Reversing a List - reverse(), reversed()
viii) Removing Elements from the List - remove(), pop()
ix) Slicing of a List
x) List Comprehension
xi) List Methods
i) Introduction to Lists
● Python Lists are just like dynamically sized arrays, declared in other languages.
● A list is a collection of things, enclosed in [ ] and separated by commas.
● The list is a sequence data type which is used to store the collection of data.
● Lists are the simplest containers that are an integral part of the Python
language.
● Lists are always heterogeneous. A single list may contain DataTypes like
Integers, Strings, as well as Objects.
● Lists are mutable, and hence, they can be altered even after their creation.
# Creating a List
List = []
print("Blank List: ")
print(List)
# Creating a List of numbers
List = [10, 20, 14]
print("\nList of numbers: ")
print(List)
# Creating a List of strings and accessing using index
List = ["Geeks", "For", "Geeks"]
print("\nList Items: ")
print(List[0])
print(List[2])
# Creating a List with mixed type of values Having numbers and
strings
List = [1, 2, 'Geeks', 4, 'For', 6, 'Geeks']
print("\nList with the use of Mixed Values: ")
print(List)
Output
Blank List: []
List of numbers: [10, 20, 14]
List Items:
Geeks
Geeks
List with the use of Mixed Values: [1, 2, 'Geeks', 4, 'For', 6, 'Geeks']
Output
Accessing a element from the list
Geeks
Geeks
Accessing a element from a Multi-Dimensional list
For
Geeks
Accessing element using negative indexing
Geeks
For
iv) Getting the size of Python list
● len() is used to get the length of the list.
Output:
Enter the size of list : 4
Enter the integer elements: 6 3 9 10
The list is: [6, 3, 9, 10]
Example
List = [1,2,3,4]
print("Initial List: ")
print(List)
List.insert(3, 12)
List.insert(0, 'Geeks')
print("\nList after performing Insert Operation: ")
print(List)
Output
Initial List:
[1, 2, 3, 4]
List after performing Insert Operation:
['Geeks', 1, 2, 3, 12, 4]
Reversing a List
Method 1: A list can be reversed by using the reverse() method in Python.
mylist = [1, 2, 3, 4, 5]
mylist.reverse()
print(mylist)
Output
[ 5, 4, 3, 2, 1]
Example 1:
List = [1, 2, 3, 4, 5, 6,7, 8, 9, 10, 11, 12]
print("Initial List: ")
print(List)
List.remove(5)
List.remove(6)
print("\nList after Removal of two elements: ")
print(List)
Output
Initial List:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
List after Removal of two elements:
[1, 2, 3, 4, 7, 8, 9, 10, 11, 12]
Slicing of a List
We can get substrings and sublists using a slice. To print a specific range of elements
from the list, we use the Slice operation. Slice operation is performed on Lists with
the use of a colon(:).
Example
List = ['G', 'E', 'E', 'K', 'S', 'F','O', 'R', 'G', 'E', 'E', 'K', 'S']
print("Initial List: ")
print(List)
Sliced_List = List[3:8]
print("\nSlicing elements in a range 3-8: ")
print(Sliced_List)
Sliced_List = List[5:]
print("\nElements sliced from 5th element till the end: ")
print(Sliced_List)
Output
Initial List:
['G', 'E', 'E', 'K', 'S', 'F', 'O', 'R', 'G', 'E', 'E', 'K', 'S']
Slicing elements in a range 3-8:
['K', 'S', 'F', 'O', 'R']
Elements sliced from 5th element till the end:
['F', 'O', 'R', 'G', 'E', 'E', 'K', 'S']
List Comprehension
Python List comprehensions are used for creating new lists from other iterables like
tuples, strings, arrays, lists, etc. A list comprehension consists of brackets containing
the expression, which is executed for each element along with the for loop to iterate
over each element.
Syntax:
newList = [ expression(element) for element in oldList if condition ]
Example:
# Below list contains square of all odd numbers from range 1 to 10
odd_square = [x ** 2 for x in range(1, 11) if x % 2 == 1]
print(odd_square)
Output
[1, 9, 25, 49, 81]
List Methods
Function Description
Append() Add an element to the end of the list
Extend() Add all elements of a list to another list
Insert() Insert an item at the defined index
Remove() Removes an item from the list
Clear() Removes all items from the list
Index() Returns the index of the first matched item
Count() Returns the count of the number of items passed as an argument
Sort() Sort items in a list in ascending order
Reverse() Reverse the order of items in the list
copy() Returns a copy of the list
pop() Removes and returns the item at the specified index.
_____________________________________________________________________
4. Tuples in Python
Introduction
Tuple is a collection of objects separated by commas.
Tuple is immutable.
Tuples are defined by enclosing values in parentheses () separated by commas.
Output:
('Geeks', 'for', 'Geeks')
Output:
Value in Var[0] = Geeks
Value in Var[1] = for
Value in Var[2] = Geeks
Output:
Value in Var[-1] = 3
Value in Var[-2] = 2
Value in Var[-3] = 1
Output:
(0, 1, 2, 3, 'python', 'geek')
Output :
((0, 1, 2, 3), ('python', 'geek'))
tuple3 = ('python',)*3
print(tuple3)
Output:
('python', 'python', 'python')
Output:
(1, 2, 3)
(3, 2, 1, 0)
(2, 3)
Output:
NameError: name 'tuple3' is not defined
Output:
2
Output :
('immutable', True, 23)
Output:
Tuples take a single parameter which may be a list, string, set, or even a
dictionary(only keys are taken as elements), and converts them to a tuple.
(0, 1, 2)
('p', 'y', 't', 'h', 'o', 'n')
Tuples in a Loop
We can also create a tuple with a single element in it using loops.
tup = ('geek',)
n=5
for i in range(int(n)):
tup = (tup,)
print(tup)
Output:
(('geek',),)
((('geek',),),)
(((('geek',),),),)
((((('geek',),),),),)
(((((('geek',),),),),),)
_____________________________________________________________________
5. Dictionaries in Python
A Python dictionary is a data structure that stores the value in key:value pairs.
Example:
Dict = {1: 'Geeks', 2: 'For', 3: 'Geeks'}
print(Dict)
Output:
{1: 'Geeks', 2: 'For', 3: 'Geeks'}
Example
Dict = {1: 'Geeks', 2: 'For', 3: 'Geeks'}
print("\nDictionary with the use of Integer Keys: ")
print(Dict)
Dict = {'Name': 'Geeks', 1: [1, 2, 3, 4]}
print("\nDictionary with the use of Mixed Keys: ")
print(Dict)
Dict = dict({1: 'Geeks', 2: 'For', 3: 'Geeks'})
print("\nDictionary with the use of dict(): ")
print(Dict)
Output
Dictionary with the use of Integer Keys:
{1: 'Geeks', 2: 'For', 3: 'Geeks'}
Dictionary with the use of Mixed Keys:
{'Name': 'Geeks', 1: [1, 2, 3, 4]}
Dictionary with the use of dict():
{1: 'Geeks', 2: 'For', 3: 'Geeks'}
Nested Dictionaries
Dict = {1: 'Geeks', 2: 'For',
3: {'A': 'Welcome', 'B': 'To', 'C': 'Geeks'}}
print(Dict)
Output:
{1: 'Geeks', 2: 'For', 3: {'A': 'Welcome', 'B': 'To', 'C': 'Geeks'}}
This example includes a top-level dictionary with keys 1, 2, and 3. The value
associated with key 3 is another dictionary with keys ‘A,’ ‘B,’ and ‘C.’ This showcases
how Python dictionaries can be nested to create hierarchical data structures.
Adding Elements to a Dictionary
The addition of elements can be done in multiple ways. One value at a time can be
added to a Dictionary by defining value along with the key e.g. Dict[Key] = ‘Value’.
Example
Dict = {}
print("Empty Dictionary: ")
print(Dict)
Dict[0] = 'Geeks'
Dict[2] = 'For'
Dict[3] = 1
print("\nDictionary after adding 3 elements: ")
print(Dict)
Output:
Empty Dictionary:
{}
Dictionary after adding 3 elements:
{0: 'Geeks', 2: 'For', 3: 1}
Output:
Accessing a element using key:
For
Accessing a element using key:
Geeks
Accessing a element using get:
Geeks
Output:
{1: 'Geeks'}
Geeks
For
Output
Dictionary ={1: 'Geeks', 'name': 'For', 3: 'Geeks'}
Data after deletion Dictionary={'name': 'For', 3: 'Geeks'}
Dictionary Methods
Method Description
dict.clear() Remove all the elements from the dictionary
dict.copy() Returns a copy of the dictionary
dict.get(key, default = “None”) Returns the value of specified key
dict.items() Returns a list containing a tuple for each key value pair
dict.keys() Returns a list containing dictionary’s keys
dict.update(dict2) Updates dictionary with specified key-value pairs
dict.values() Returns a list of all the values of dictionary
pop() Remove the element with specified key
popItem() Removes the last inserted key-value pair
dict.setdefault(key,default= “None”) set the key to the default value if the key is
not specified in the dictionary
dict.has_key(key) Returns true if the dictionary contains the specified key.
Example:
dict1 = {1: "Python", 2: "Java", 3: "Ruby", 4: "Scala"}
dict2 = dict1.copy()
print(dict2)
dict1.clear()
print(dict1)
print(dict2.get(1))
print(dict2.items())
print(dict2.keys())
dict2.pop(4)
print(dict2)
dict2.popitem()
print(dict2)
dict2.update({3: "Scala"})
print(dict2)
print(dict2.values())
Output:
{1: 'Python', 2: 'Java', 3: 'Ruby', 4: 'Scala'}
{}
Python
dict_items([(1, 'Python'), (2, 'Java'), (3, 'Ruby'), (4, 'Scala')])
dict_keys([1, 2, 3, 4])
{1: 'Python', 2: 'Java', 3: 'Ruby'}
{1: 'Python', 2: 'Java'}
{1: 'Python', 2: 'Java', 3: 'Scala'}
dict_values(['Python', 'Java', 'Scala'])
_____________________________________________________________________
UNIT 2
1. While Loop in Python
i) Introduction
ii) Python While Loop Syntax
iii) Using else statement with While Loop
iv) Infinite While Loop in Python
i) Introduction
In Python, a while loop is used to execute a block of statements repeatedly until a
given condition is satisfied. When the condition becomes false, the line immediately
after the loop in the program is executed.
Output
Hello
Hello
Hello
In Else Block
count = 0+U
while (count == 0):
print("Hello Geek")
_____________________________________________________________________
For loops are used for sequential traversal. For example: traversing a list or string or
array etc. In Python, there is a “for in” loop which is similar to the foreach loop in
other languages. Let us learn how to use for loops in Python for sequential traversals
with examples.
For Loop Syntax:
Example:
The code uses a Python for loop that iterates over the values from 0 to 3 (not
including 4), as specified by the range(0, n) construct. It will print the values of ‘i' in
each iteration of the loop.
n=4
for i in range(0, n):
print(i)
Output
0
1
2
3
Example with List, Tuple, String, and Dictionary Iteration Using for Loops in Python
print("List Iteration")
l = ["Red", "Green", "Blue"]
for i in l:
print(i)
print("\nTuple Iteration")
t = ("Red", "Green", "Blue")
for i in t:
print(i)
print("\nString Iteration")
s = "Hello"
for i in s:
print(i)
print("\nDictionary Iteration")
d = dict()
d['xyz'] = 123
d['abc'] = 345
for i in d:
print("%s %d" % (i, d[i]))
print("\nSet Iteration")
set1 = {1, 2, 3, 4, 5, 6}
for i in set1:
print(i)
Output
List Iteration
Red
Green
Blue
Tuple Iteration
Red
Green
Blue
String Iteration
H
e
l
l
o
Dictionary Iteration
xyz 123
abc 345
Set Iteration
1
2
3
4
5
6
Output
Red
Green
Blue
We can also combine the else statement with for loop like in while loop. But as there
is no condition in for loop based on which the execution will terminate so the else
block will be executed immediately after for block finishes execution.
Output
Red
Green
Blue
Inside Else Block
Python programming language allows one loop inside another loop which is called a
nested loop.
Nested Loops Syntax:
A final note on loop nesting is that we can put any type of loop inside of any other
type of loops in Python. For example, a for loop can be inside a while loop or vice
versa.
Example: This Python code uses nested ‘for' loops to create a triangular pattern of
numbers. It iterates from 1 to 4 and, in each iteration, prints the current number
multiple times based on the iteration number. The result is a pyramid-like pattern of
numbers.
By default, statements in the script are executed sequentially from the first to the
last. If the processing logic requires so, the sequential flow can be altered in two
ways:
Python uses the if keyword to implement decision control. Python's syntax for
executing a block conditionally is as below:
Syntax:
if [boolean expression]:
statement1
statement2
...
statementN
Any Boolean expression evaluating to True or False appears after the if keyword. Use
the : symbol and press Enter after the expression to start a block with an increased
indent. One or more statements written with the same level of indent will be
executed if the Boolean expression evaluates to True.
To end the block, decrease the indentation. Subsequent statements after the block
will be executed out of the if condition. The following example demonstrates the if
condition.
Example: if Condition
price = 50
if price < 100:
print("price is less than 100")
Output
price is less than 100
In the above example, the expression price < 100 evaluates to True, so it will execute
the block. The if block starts from the new line after : and all the statements under
the if condition starts with an increased indentation, either space or tab. Above, the
if block contains only one statement. The following example has multiple statements
in the if condition.
Output
price*quantity is less than 500
price = 50
quantity = 5
Above, the if condition contains multiple statements with the same indentation. If all
the statements are not in the same indentation, either space or a tab then it will
raise an IdentationError.
Example: Invalid Indentation in the Block
price = 50
quantity = 5
if price*quantity < 500:
print("price is less than 500")
print("price = ", price)
print("quantity = ", quantity)
Output
print("quantity = ", quantity)
^
IdentationError: unexpected indent
The statements with the same indentation level as if condition will not consider in
the if block. They will consider out of the if condition.
else Condition
Along with the if statement, the else condition can be optionally used to define an
alternate block of statements to be executed if the boolean expression in the if
condition evaluates to False.
Syntax:
if [boolean expression]:
statement1
statement2
...
statementN
else:
statement1
statement2
...
statementN
As mentioned before, the indented block starts after the : symbol, after the boolean
expression. It will get executed when the condition is True. We have another block
that should be executed when the if condition is False. First, complete the if block by
a backspace and write else, put add the : symbol in front of the new block to begin it,
and add the required statements in the block.
Example: else Condition
price = 50
if price >= 100:
print("price is greater than 100")
else:
print("price is less than 100")
Output
price is less than 100
In the above example, the if condition price >= 100 is False, so the else block will be
executed. The else block can also contain multiple statements with the same
indentation; otherwise, it will raise the IndentationError.
Note that We cannot have multiple else blocks, and it must be the last block.
elif Condition
Use the elif condition is used to include multiple conditional expressions after the if
condition or between the if and else conditions.
Syntax:
if [boolean expression]:
[statements]
elif [boolean expresion]:
[statements]
elif [boolean expresion]:
[statements]
else:
[statements]
The elif block is executed if the specified condition evaluates to True.
In the above example, the elif conditions are applied after the if condition. Python
will evalute the if condition and if it evaluates to False then it will evalute the elif
blocks and execute the elif block whose expression evaluates to True. If multiple elif
conditions become True, then the first elif block will be executed.
All the if, elif, and else conditions must start from the same indentation level,
otherwise it will raise the IndentationError.
Output
elif price == 100:
^
IdentationError: unindent does not match any outer indentation level
Output
Amount is between 200 and 500
____________________________________________________________________
4. Functions in Python
Python Functions
Python Functions is a block of statements that return the specific task. The idea is to
put some commonly or repeatedly done tasks together and make a function so that
instead of writing the same code again and again for different inputs, we can do the
function calls to reuse code contained in it over and over again.
Output:
Welcome to GFG
Example
# Driver code
num1, num2 = 5, 15
ans = add(num1, num2)
print(f"The addition of {num1} and {num2} results {ans}.")
Output:
i) Default argument
ii) Keyword arguments (named arguments)
iii) Positional arguments
iv) Arbitrary arguments (variable-length arguments *args and **kwargs)
Default Arguments
A default argument is a parameter that assumes a default value if a value is not
provided in the function call for that argument. The following example illustrates
Default arguments to write functions in Python.
Output:
x: 10
y: 50
Keyword Arguments
The idea is to allow the caller to specify the argument name with values so that the
caller does not need to remember the order of parameters.
# Keyword arguments
student(firstname='Arun', lastname='Kumar')
student(lastname='Kumar', firstname='Arun')
Output:
Arun Kumar
Arun Kumar
Positional Arguments
We used the Position argument during the function call so that the first argument (or
value) is assigned to name and the second argument (or value) is assigned to age. By
changing the position, or if We forget the order of the positions, the values can be
used in the wrong places, as shown in the Case-2 example below, where 27 is
assigned to the name and Suraj is assigned to the age.
# Driver code
print("Case-1:")
nameAge("Suraj", 27)
print("\nCase-2:")
nameAge(27, "Suraj")
Output:
Case-1:
Hi, I am Suraj
My age is 27
Case-2:
Hi, I am 27
My age is Suraj
Output:
Hello
Welcome
to
Our Home
Output:
first == Geeks
mid == for
last == Geeks
Nested Functions
A function that is defined inside another function is known as the inner function or
nested function. Nested functions can access variables of the enclosing scope. Inner
functions are used so that they can be protected from everything happening outside
the function.
# Python program to demonstrate nested functions
def f1():
s = 'Welcome’'
def f2():
print(s)
f2()
# Driver's code
f1()
Output:
Welcome
print(cube(7))
print(cube_v2(7))
Output:
343
343
print(factorial(4))
Output
24
_____________________________________________________________________
5. Exception Handling
Exception Handling
Introduction
In Python, there are several built-in Python exceptions that can be raised when an
error occurs during the execution of a program. Here are some of the most common
types of exceptions in Python:
● SyntaxError: This exception is raised when the interpreter encounters a syntax
error such as a misspelled keyword, a missing colon, or an unbalanced
parenthesis.
● TypeError: This exception is raised when an operation is applied to an object
of the wrong type, such as adding a string to an integer.
● NameError: This exception is raised when a variable or function name is not
found in the current scope.
● IndexError: This exception is raised when an index is out of range.
● KeyError: This exception is raised when a key is not found in a dictionary.
● ValueError: This exception is raised when a function is called with an invalid
argument or input, such as trying to convert a string to an integer.
● IOError: This exception is raised when an I/O operation, such as reading or
writing a file fails.
● ZeroDivisionError: This exception is raised when an attempt is made to divide
a number by zero.
● ImportError: This exception is raised when an import statement fails to find or
load a module.
In the above example, the ZeroDivisionError will be raised as we are trying to divide a
number by 0.
Example: Here we are trying to access the array element whose index is out of
bound and handle the corresponding exception.
a = [1, 2, 3]
try:
print ("Second element = "+a[1])
print ("Fourth element = "+a[3])
except:
print ("An error occurred")
Output
Second element = 2
An error occurred
In the above example, the statements that can cause the error are placed inside the
try statement (second print statement in our case). The second print statement tries
to access the fourth element of the list which is not there and this throws an
exception. This exception is then caught by the except statement.
try:
# statement(s)
except IndexError:
# statement(s)
except ValueError:
# statement(s)
def fun(a):
if a < 4:
b = a/(a-3)
print("Value of b = ", b)
try:
fun(3)
fun(5)
except ZeroDivisionError:
print("ZeroDivisionError Occurred and Handled")
except NameError:
print("NameError Occurred and Handled")
Output
ZeroDivisionError Occurred and Handled
If We comment on the line fun(3), the output will be
NameError Occurred and Handled
The output above is so because as soon as python tries to access the value of b,
NameError occurs.
Example
def AbyB(a , b):
try:
c = ((a+b) / (a-b))
except ZeroDivisionError:
print ("a/b result in 0")
else:
print (c)
AbyB(2.0, 3.0)
AbyB(3.0, 3.0)
Output:
-5.0
a/b result in 0
Finally Keyword in Python
Python provides a keyword finally, which is always executed after the try and except
blocks. The final block always executes after the normal termination of the try block
or after the try block terminates due to some exception.
Syntax:
try:
# Some Code....
except:
# optional block
# Handling of exception (if required)
else:
# execute if no exception
finally:
# Some code .....(always executed)
Example:
try:
k = 5//0
print(k)
except ZeroDivisionError:
print("Can't divide by zero")
finally:
print('This is always executed')
Output:
Can't divide by zero
This is always executed
Raising Exception
The raise statement allows the programmer to force a specific exception to occur.
The sole argument in raise indicates the exception to be raised. This must be either
an exception instance or an exception class (a class that derives from Exception).
try:
raise NameError("Hi there")
except NameError:
print ("An exception")
raise
The output of the above code will simply line printed as “An exception” but a
Runtime error will also occur in the last due to the raise statement in the last line. So,
the output on Wer command line will look like
Traceback (most recent call last):
File "/home/d6ec14ca595b97bff8d8034bbf212a9f.py", line 5, in <module>
raise NameError("Hi there") # Raise Error
NameError: Hi there
Name and age are the two properties of the Person class. Additionally, it has a
function called greet that prints a greeting.
Objects in Python:
An object is a particular instance of a class with unique characteristics and functions.
After a class has been established, We may make objects based on it. By using the
class constructor, We may create an object of a class in Python. The object's
attributes are initialised in the constructor, which is a special procedure with the
name __init__.
Syntax:
# Declare an object of a class
object_name = Class_Name(arguments)
Example:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def greet(self):
print("Hello, my name is " + self.name)
The self-parameter
The self-parameter refers to the current instance of the class and accesses the class
variables. We can use anything instead of self, but it must be the first parameter of
any function which belongs to the class.
_ _init_ _ method
In order to make an instance of a class in Python, a specific function called __init__ is
called. Although it is used to set the object's attributes, it is often referred to as a
constructor.
The self-argument is the only one required by the __init__ method. This argument
refers to the newly generated instance of the class. To initialise the values of each
attribute associated with the objects, We can declare extra arguments in the __init__
method.
Output:
2
Whereas, instance variables are specific to each instance of a class. They are
specified using the self-argument in the __init__ method.
Here's an illustration:
class Person:
def __init__(self, name, age):
self.name = name # This is an instance variable
self.age = age
person1 = Person("Ayan", 25)
person2 = Person("Bobby", 30)
print(person1.name)
print(person2.age)
Output:
Ayan
30
Class variables are created separately from any class methods and are shared by all
class copies. Every instance of a class has its own instance variables, which are
specified in the __init__ method utilising the self-argument.
_____________________________________________________________________
2. Super() Function .
Introduction:
The super() function in Python is a built-in function that allows We to call methods
from a parent (or superclass) in a child (or subclass) context. This function is primarily used
in object-oriented programming to ensure that inherited methods or constructors (such as
__init__()) from a parent class are called in a subclass, allowing We to extend or customize
the behavior of parent classes without losing their functionality.
In simpler terms, super() is used to give access to methods and properties of a
parent class from within a child class. It helps in situations where multiple inheritance or
method overriding is involved, allowing We to avoid repetitive code.
Benefits:
❖ Inheritance and Code Reusability: By using super(), We can reuse the methods and
properties of a parent class in a child class, reducing the need for redundant code.
❖ Avoids Direct Parent Class Reference: The super() function allows We to refer to the
parent class without explicitly naming it, making the code more flexible, especially
when dealing with multiple inheritance.
❖ Supports Multiple Inheritance: When working with multiple inheritance, super()
helps resolve the "diamond problem" by following the method resolution order
(MRO) and ensuring the correct class is called in the inheritance chain.
❖ Ensures Consistent Initialization: In the case of overriding the __init__() method,
super() helps ensure that the initialization of the parent class happens, preventing
errors caused by skipping necessary initializations.
❖ Increases Maintainability: Using super() simplifies modifications to a class hierarchy.
If We rename or refactor parent classes, We don’t need to change subclass code
since super() dynamically refers to the parent class.
Output:
Method in class B
Method in class A
Example:
Let's take a more detailed example involving the use of super() with the __init__() method
in a simple inheritance scenario.
Example: Using super() in Class Initialization
# Base or Parent class
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound."
# Subclass or Child class
class Dog(Animal):
def __init__(self, name, breed):
# Calling the parent class's __init__ method using super()
super().__init__(name)
self.breed = breed
def speak(self):
return f"{self.name}, the {self.breed}, barks."
# Accessing methods
print(dog.speak()) # Output: Buddy, the Golden Retriever, barks.
print(cat.speak()) # Output: Whiskers meows.
Explanation:
Animal Class: This is the parent class with an __init__() method that initializes the name
attribute. The speak() method provides a general message.
Dog Class: This subclass of Animal has its own __init__() method to initialize both the name
and the breed attributes. It uses super() to call the __init__() method of the Animal class. It
also overrides the speak() method to provide a custom message for dogs.
Cat Class: This subclass overrides the speak() method but doesn't need to override __init__()
since it only relies on the name attribute inherited from Animal.
class A:
def method(self):
print("A's method")
class B(A):
def method(self):
print("B's method")
super().method()
class C(A):
def method(self):
print("C's method")
super().method()
obj = D()
obj.method()
Output:
D's method
B's method
C's method
A's method
In this example:
The class D inherits from both B and C.
The super() function ensures that methods are called in the proper order, adhering
to the method resolution order (MRO).
We can check the MRO by calling D.mro() to see the order in which classes are
looked at.
_____________________________________________________________________
3. inheritance Single inheritance and Multiple inheritance
Inheritance in Python
Introduction
● One of the core concepts in object-oriented programming (OOP) languages is
inheritance.
● It is a mechanism that allows We to create a hierarchy of classes that share a
set of properties and methods by deriving a class from another class.
● Inheritance is the capability of one class to derive or inherit the properties
from another class.
● Inheritance allows We to inherit the properties of a class, i.e., base class to
another, i.e., derived class.
Output:
Satyam 102
class Emp(Person):
def Print(self):
print("Emp class called")
Emp_details = Emp("Mayank", 103)
# calling parent class function
Emp_details.Display()
# Calling child class function
Emp_details.Print()
Output:
Mayank 103
Emp class called
Output:
Manoj False
Anand True
Single inheritance: When a child class inherits from only one parent class, it is called
single inheritance.
Multiple inheritance: When a child class inherits from multiple parent classes, it is
called multiple inheritances.
Multilevel inheritance: When we have a child and grandchild relationship. This
means that a child class will inherit from its parent class, which in turn is inheriting
from its parent class.
Hierarchical inheritance: More than one derived class can be created from a single
base.
Hybrid inheritance: This form combines more than one form of inheritance.
Basically, it is a blend of more than one type of inheritance.
# Python example to show the working of multiple inheritance
class Base1(object):
def __init__(self):
self.str1 = "Class1"
print("Base1")
class Base2(object):
def __init__(self):
self.str2 = "Class2"
print("Base2")
ob = Derived()
ob.printStrs()
Output:
Base1
Base2
Derived
Class1 Class2
class Base(object):
# Constructor
def __init__(self, name):
self.name = name
# To get name
def getName(self):
return self.name
# Driver code
g = GrandChild("Geek1", 23, "Noida")
print(g.getName(), g.getAge(), g.getAddress())
Output:
Geek1 23 Noida
# parent class
class Person():
def __init__(self, name, age):
self.name = name
self.age = age
def display(self):
print(self.name, self.age)
# child class
class Student(Person):
def __init__(self, name, age):
self.sName = name
self.sAge = age
# inheriting the properties of parent class
super().__init__("Rahul", age)
def displayInfo(self):
print(self.sName, self.sAge)
Output:
Rahul 23
Mayank 23
Adding Properties
One of the features that inheritance provides is inheriting the properties of the
parent class as well as adding new properties of our own to the child class. Let us see
this with an example:
# parent class
class Person():
def __init__(self, name, age):
self.name = name
self.age = age
def display(self):
print(self.name, self.age)
# child class
class Student(Person):
def __init__(self, name, age, dob):
self.sName = name
self.sAge = age
self.dob = dob
# inheriting the properties of parent class
super().__init__("Rahul", age)
def displayInfo(self):
print(self.sName, self.sAge, self.dob)
The arguments that are given after the name of the program in the command line
shell of the operating system are known as Command Line Arguments. Python
provides various ways of dealing with these types of arguments. The three most
common are:
Using sys.argv
Using getopt module
Using argparse module
Using sys.argv
The sys module provides functions and variables used to manipulate different parts
of the Python runtime environment. This module provides access to some variables
used or maintained by the interpreter and to functions that interact strongly with the
interpreter.
One such variable is sys.argv which is a simple list structure. It’s main purpose are:
Example: Let’s suppose there is a Python script for adding two numbers and the
numbers are passed as command-line arguments.
# Addition of numbers
Sum = 0
# Using argparse module
for i in range(1, n):
Sum += int(sys.argv[i])
print("\n\nResult:", Sum)
Output:
python-command-line-arguments
Python getopt module is similar to the getopt() function of C. Unlike sys module
getopt module extends the separation of the input string by parameter validation. It
allows both short, and long options including a value assignment. However, this
module requires the use of the sys module to process input data properly. To use
getopt module, it is required to remove the first element from the list of command-
line arguments.
Example:
# Python program to demonstrate
# command line arguments
import getopt, sys
# Remove 1st argument from the
# list of command line arguments
argumentList = sys.argv[1:]
# Options
options = "hmo:"
# Long options
long_options = ["Help", "My_file", "Output="]
try:
# Parsing argument
arguments, values = getopt.getopt(argumentList, options, long_options)
# checking each argument
for currentArgument, currentValue in arguments:
if currentArgument in ("-h", "--Help"):
print ("Displaying Help")
Using argparse module is a better option than the above two options as it provides a
lot of options such as positional arguments, default value for arguments, help
message, specifying data type of argument etc.
Note: As a default optional argument, it includes -h, along with its long version –help.
python-command-line-arguments
Example 2: Adding description to the help message.
Output:
python-command-line-arguments
Output:
python-command-line-arguments
_____________________________________________________________________
Introduction :
In Python, modules are files containing Python code (functions, classes, variables,
etc.) that can be imported and reused across different programs or parts of a program.
Python has a rich standard library of built-in modules, and We can also create custom
modules. Importing modules is an essential feature that allows We to structure Wer code,
avoid duplication, and use a vast array of pre-written functionalities.
Modules enable modular programming, where the code is divided into separate,
manageable pieces. A module is simply a Python file (.py) that can contain functions,
variables, or classes.
Now, we can import this custom module into another Python script.
# main_program.py
import custom_module
# Using the functions from the custom module
print(custom_module.greet("Alice"))
print(f"2 + 3 = {custom_module.add(2, 3)}")
Output:
Hello, Alice!
2+3=5
In addition, We can use standard library modules. Here’s an example of using the math
and datetime modules:
import math
from datetime import datetime
# Using math module
number = 25
print(f"The square root of {number} is {math.sqrt(number)}")
# Using datetime module to get current time
current_time = datetime.now()
print(f"Current date and time is: {current_time}")
Output:
The square root of 25 is 5.0
Current date and time is: 2024-09-26 12:34:56.789123
_____________________________________________________________________
Unit 4
1. Steps Used in Reading Binary Files, Benefits of Using Binary Files in Python
Introduction
Binary files are files that contain data in a format that is not human-readable, as opposed to
text files which store data as readable characters. Binary files can include images, audio,
video, and various data formats used in applications. Reading binary files in Python allows
We to work with complex data structures efficiently and with more precision than text files.
Benefits of Using Binary Files
● Efficiency: Binary files consume less space compared to text files as they store data
in raw bytes without encoding or extra formatting.
● Faster Access: Binary data can be written and read faster since there is no need for
encoding or decoding between text and byte representations.
● Data Integrity: Binary files preserve the original data without losing any detail or
precision (especially useful for images, audio, videos, and floating-point numbers).
● Non-Text Data Support: Binary files allow storage of non-textual data such as
images, audio, videos, and serialised objects.
● Portability: Binary data is easier to transfer between systems without conversions,
avoiding issues such as encoding errors.
Steps:
Open the Binary File:
Use open() function with the 'rb' (read binary) mode to open a binary file.with
open('file.bin', 'rb') as file:
Read the Data:
Use methods like read(), readline(), or readlines() to read data from the binary file.
The data read will be in bytes.
binary_data = file.read()
Example Program
Explanation of Example:
The image file 'example_image.jpg' is opened in binary mode ('rb'). We read the
entire content of the file into the binary_data variable as raw bytes. We can manipulate or
process these bytes as needed, such as displaying them, decoding them, or writing them to
another file. In the example, we create a small chunk of binary data (some_binary_data) and
write it to 'output.bin'. The binary data is written to the file without any transformation,
ensuring efficiency and integrity.
def get_stock_price(stock_symbol):
response = client.service.GetStockPrice(symbol=stock_symbol)
return response
# Periodic checks (using schedule library)
import schedule
import time
def job():
price = get_stock_price('AAPL')
print(f"Apple Stock Price: {price}")
schedule.every(10).minutes.do(job)
while True:
schedule.run_pending()
time.sleep(1)
RESTful Web Services
What is REST?
REST (Representational State Transfer) is an architectural style where web services
are designed around resources, represented as URLs (Uniform Resource Identifiers).
It is a stateless protocol that uses standard HTTP methods (GET, POST, PUT, DELETE).
Features:
○ Lightweight: REST uses lightweight JSON or XML for data interchange,
reducing overhead compared to SOAP.
○ Stateless: Each request from the client to the server must contain all
information needed to understand and process the request.
○ Scalable and Cacheable: REST services support scalability and caching,
enhancing performance.
○ Widely Used: Most modern APIs, such as those from Google, Twitter, or
GitHub, use REST.
REST in Python
Python’s requests library is widely used for interacting with RESTful services.
Performing HTTP Methods
RESTful APIs use HTTP methods to interact with resources:
● GET: Retrieve information from the server.
● POST: Create a new resource.
● PUT: Update an existing resource.
● DELETE: Remove a resource.
Example: Performing REST API Requests
GET Request (Retrieve Data):
import requests
response = requests.get('https://api.example.com/data')
POST Request (Submit Data):
data = {'key': 'value'}
response = requests.post('https://api.example.com/data', json=data)
PUT Request (Update Resource):
update_data = {'key': 'new_value'}
response = requests.put('https://api.example.com/data/1', json=update_data)
DELETE Request (Delete Resource):
response = requests.delete('https://api.example.com/data/1')
Handling JSON Responses
Most REST APIs return JSON-formatted data. The requests library makes it easy to parse
JSON responses:
Example:
response = requests.get('https://api.example.com/data')
json_data = response.json() # Converts JSON to Python dictionary
print(json_data['key'])
Authentication in REST APIs
Many REST APIs require authentication:
from requests.auth import HTTPBasicAuth
Response = requests.get('https://api.example.com/data',
auth=HTTPBasicAuth('username', 'password'))
Error Handling in REST APIs
Check HTTP status codes and handle errors:
response = requests.get('https://api.example.com/data')
if response.status_code == 200:
print("Success")
elif response.status_code == 404:
print("Resource not found")
else:
print(f"Failed with status code: {response.status_code}")
Automating REST API Requests
We can automate periodic REST API requests using the schedule library:
Example: Automating Weather Data Retrieval:
import requests
import schedule
import time
def get_weather():
Response = requests.get('https://api.weatherapi.com/v1/current.json?
key=WeR_API_KEY&q=London')
weather_data = response.json()
print(f"Temperature: {weather_data['current']['temp_c']}°C")
schedule.every(10).minutes.do(get_weather)
while True:
schedule.run_pending()
time.sleep(1)
SOAP vs. REST for Automation
SOAP Advantages:
● Built-in Security: SOAP provides more robust security features like WS-Security.
● Standardized Protocol: It is highly standardized, which makes it suitable for
enterprise-level applications.
● Reliability: SOAP has better built-in error handling and is preferred in environments
where reliability is critical.
REST Advantages:
● Lightweight: REST uses less bandwidth and resources as it supports JSON, making it
faster for web services with mobile applications.
● Scalability: REST is stateless, which makes it more scalable for cloud-based
applications.
● Ease of Use: REST APIs
___________________________________________________________________________
5. NoSQL Databases: Introduction & Characteristics
Introduction to NoSQL Databases:
● NoSQL (Not Only SQL) databases are designed to handle large volumes of
unstructured or semi-structured data that traditional relational databases struggle to
manage.
● Unlike traditional SQL databases, NoSQL databases don't rely on fixed schemas or
complex join operations.
● Common use cases include big data applications, real-time web apps, IoT systems,
and content management systems.
● Examples of NoSQL databases include MongoDB, Cassandra, Redis, CouchDB, and
Amazon DynamoDB.
Characteristics of NoSQL Databases:
1. Scalability:
○ Horizontally scalable (scale-out by adding more servers) unlike relational
databases that scale vertically (scale-up by adding more resources to a single
server).
2. Schema Flexibility:
○ No predefined schema, allowing storage of different kinds of data in the same
database.
○ Ideal for unstructured or semi-structured data (e.g., JSON, XML, key-value
pairs).
3. Distributed Architecture:
○ NoSQL databases are often designed to work across distributed systems,
ensuring data is replicated and available even in the event of server failures.
4. High Availability:
○ Fault-tolerant with automatic recovery mechanisms. Data is often replicated
across multiple nodes to ensure availability.
5. Efficient Handling of Large Datasets:
○ Optimized for handling high volumes of data and transactions, making them
suitable for Big Data applications.
6. Eventual Consistency:
○ Many NoSQL systems prefer eventual consistency over strong consistency,
which means that updates to the database will eventually propagate to all
nodes.
7. Data Models:
○ NoSQL databases can have various data models, including:
■ Document-based (e.g., MongoDB): Data stored in JSON-like
documents.
■ Key-value (e.g., Redis): Data stored as key-value pairs.
■ Column-family (e.g., Cassandra): Data stored in column families
rather than rows.
■ Graph-based (e.g., Neo4j): Designed for handling data that can be
represented as graphs.
Steps Used in Managing Unstructured Data Within NoSQL Databases
1. Data Modeling:
○ Identify the structure: NoSQL allows for dynamic schema, so understanding
the data model (documents, key-value, graph, etc.) is key.
○ Organize unstructured data: Unstructured data such as text, images, and
videos are usually stored in a flexible format (e.g., JSON or BSON).
2. Data Ingestion:
○ Extract and load data: Use APIs or connectors to ingest data from various
sources like social media, IoT devices, or log files.
○ Handling large data: Utilize NoSQL features like sharding (partitioning large
datasets) for efficient ingestion and processing.
3. Data Storage:
○ Choose the appropriate database type: Depending on the nature of the
unstructured data, select a NoSQL model:
■ Document store (e.g., MongoDB) for JSON-like documents.
■ Key-value store (e.g., Redis) for simple, fast access.
■ Column-family store (e.g., Cassandra) for large datasets with dynamic
attributes.
■ Graph store (e.g., Neo4j) for relationship-driven data.
○ Data replication: Configure replication to ensure data availability and fault
tolerance.
4. Data Indexing:
○ Create efficient indexes: Create appropriate indexes on frequently queried
fields to improve performance.
○ Custom indexing strategies: Use indexing strategies suitable for unstructured
data (like text indexing for search engines).
5. Data Querying:
○ Use flexible query languages: NoSQL databases provide query languages
suited to their data models. For example, MongoDB uses queries resembling
JSON syntax.
○ Aggregation and filtering: Use built-in capabilities to filter and aggregate
unstructured data efficiently (e.g., MongoDB's aggregation pipeline).
6. Data Replication & Sharding:
○ Sharding: NoSQL databases support horizontal scaling via sharding,
distributing large datasets across multiple nodes.
○ Replication: Ensure data redundancy and fault tolerance by replicating data
across multiple nodes.
7. Data Backup & Recovery:
○ Regular backups: Since NoSQL databases handle large datasets, backups are
critical to avoid data loss.
○ Automated recovery: Configure automatic recovery systems to handle server
or node failures.
8. Data Security:
○ Implement access control: Define role-based access and permissions for
users.
○ Data encryption: Encrypt sensitive data, especially when dealing with
unstructured data like personal information.
Example: Managing Unstructured Data with MongoDB
1. Ingesting Data:
○ Unstructured data like log files or social media posts are loaded into
MongoDB using a client library.
2. Data Storage:
○ The data is stored in BSON (binary JSON) format, allowing flexible fields and
structure. Different documents in the same collection can have varied fields.
3. Data Indexing:
○ Indexes are created on frequently queried fields like timestamps or user IDs
to speed up retrieval.
4. Data Querying:
○ MongoDB’s query language is used to find, filter, or aggregate data. Queries
are structured similarly to JSON objects.
5. Replication & Sharding:
○ The dataset is split across multiple nodes (sharding), and MongoDB ensures
that copies (replicas) of the data exist to ensure high availability.
This method ensures efficient storage, retrieval, and management of unstructured data at a
large scale using NoSQL databases.
__________________________________________________________________________
Unit 5
1. Handling Files, Read and Write Files On Disk in Python
Python provides a simple and efficient way to handle files on disk. This is done using built-in
functions to read from and write to files. These operations are typically performed with the
open() function, which returns a file object that allows various file-handling methods.
i) Opening a File:
● To work with files, We first need to open them. Python’s open() function is used for
this.
Syntax:
file_object = open("file_name", "mode")
○ file_name: Name of the file We want to open.
○ mode: Specifies what We want to do with the file. The common modes are:
■ 'r': Open for reading (default mode).
■ 'w': Open for writing (will overwrite the file if it exists, or create a new
file).
■ 'a': Open for appending (data will be added to the end of the file).
■ 'b': Binary mode (used with 'rb', 'wb' for reading and writing binary
data).
■ 'x': Create a new file and open it for writing (fails if the file already
exists).
ii) Reading from a File
After opening a file, We can use various methods to read data from the file.
Example:
file = open("example.txt", "r")
content = file.read()
print(content)
file.close()
Reading Line by Line:
with open("example.txt", "r") as file:
for line in file:
print(line.strip())
# strip() removes the extra newline characters
Methods for Reading:
● read(size): Reads the entire file or the specified number of characters.
● readline(): Reads one line at a time.
● readlines(): Reads all lines in the file and returns them as a list.
iii) Writing to a File
To write data to a file, We can open it in 'w', 'a', or 'x' mode. If the file does not exist, it will
be created.
Example:
# Writing to a file
with open("example.txt", "w") as file:
file.write("This is a new line.\n")
file.write("Another line of text.\n")
Appending to a File:
# Appending to a file
with open("example.txt", "a") as file:
file.write("Appending a new line.\n")
Methods for Writing:
● write(string): Writes a single string to the file.
● writelines(list): Writes a list of strings to the file (We need to include the newline
characters \n explicitly).
iv) Closing a File
It's important to close a file after We are done with it to free up system resources and
ensure the file is saved correctly.
Closing a file manually:
import multiprocessing
def square(x):
return x * x
if __name__ == "__main__":
# Create a pool of worker processes
with multiprocessing.Pool(4) as pool:
# Map a function to a list of inputs
results = pool.map(square, [1, 2, 3, 4, 5])
print(results) # Output: [1, 4, 9, 16, 25]
In this example:
● The Pool(4) creates a pool of 4 processes.
● pool.map() distributes the task of squaring each number across the available
processes.
vii) Differences Between Threads and Processes
● Threads share the same memory space and are lightweight, but Python's Global
Interpreter Lock (GIL) prevents true parallel execution of threads.
● Processes have separate memory spaces and can run in parallel, making them
suitable for CPU-bound tasks.
For CPU-bound tasks, multiprocessing is preferable because it avoids the limitations of
Python’s GIL by using separate processes.
viii) CPU-Bound vs. I/O-Bound Tasks
CPU-Bound Task (using multiprocessing):
For tasks that require heavy computation, multiprocessing can distribute the workload
across multiple CPU cores.
import multiprocessing
def calculate_square(x):
return x * x
if __name__ == "__main__":
with multiprocessing.Pool() as pool:
result = pool.map(calculate_square, range(10**6))
print(result[:5]) # First five results
I/O-Bound Task (using threading):
For tasks like reading or writing to files or making network requests, threading can be more
efficient.
import threading
def download_data(url):
print(f"Downloading data from {url}")
# Create threads for downloading from multiple URLs
threads = [threading.Thread(target=download_data, args=(url,)) for url in ["url1",
"url2", "url3"]]
# Start the threads
for thread in threads:
thread.start()
# Wait for all threads to finish
for thread in threads:
thread.join()
___________________________________________________________________________
3. Handling Directories in Python
Python provides several built-in modules to work with directories (folders) and file paths,
allowing We to create, delete, list, or change directories programmatically. The most
commonly used modules for handling directories are:
● os module: Contains basic functions for interacting with the operating system,
including directory manipulation.
● os.path module: Provides functions for manipulating file paths in a platform-
independent way.
● pathlib module: Introduced in Python 3.4, this module provides an object-oriented
approach to working with files and directories.
i). Creating and Deleting Directories
We can create a directory using the mkdir() function from the os or pathlib module.
Example Using os:
import os
os.mkdir("my_directory")
To delete an empty directory, use rmdir().
Example Using os:
import os
os.rmdir("my_directory")
Note: Both os.rmdir() will throw an error if the directory is not empty. We need to delete
the contents first or use a more robust method like shutil.rmtree().
ii) Listing Files and Directories
Using os.listdir()
To get a list of all files and directories in a directory, use os.listdir().
Example:
import os
items = os.listdir(".")
print(items)
Using os.scandir() (More Efficient)
os.scandir() returns an iterator that yields DirEntry objects with more detailed information
about each file or directory.
Example:
import os
with os.scandir(".") as entries:
for entry in entries:
print(entry.name, entry.is_file(), entry.is_dir())
iii) Checking If a Directory Exists
Using os.path.exists()
To check whether a directory exists, use os.path.exists().
Example:
import os
if os.path.exists("my_directory"):
print("Directory exists")
else:
print("Directory does not exist")
iv) Changing the Current Working Directory
The working directory is the directory in which Wer Python script is currently operating. To
change the working directory, use os.chdir().
Example:
import os
current_directory = os.getcwd()
print("Current Directory:", current_directory)
os.chdir("my_directory")
print("New Current Directory:", os.getcwd())
v) Renaming Directories
To rename a directory, use the os.rename() or pathlib.Path.rename() method.
Example: import os
os.rename("old_directory", "new_directory")
vi) Deleting Directories with Contents
For directories that are not empty, We can delete them using shutil.rmtree(). This will
remove the directory and all its contents recursively.
Example: import shutil
shutil.rmtree("parent_dir")
vii) Getting the File/Directory Name and Parent Directory
Using os.path.basename() and os.path.dirname()
● os.path.basename(): Returns the name of the file or directory.
● os.path.dirname(): Returns the directory name of the path.
Example:
import os
path = "/home/user/documents/file.txt"
print("File Name:", os.path.basename(path)) print("Directory:",
os.path.dirname(path))
___________________________________________________________________________
4. Thread Synchronization in Concurrent Programming
import threading
lock = threading.RLock()
def task():
with lock:
print("Acquired lock")
with lock: # Acquiring the same lock again
print("Acquired lock again")
thread = threading.Thread(target=task)
thread.start()
thread.join()
Semaphores: A semaphore is a counter that limits the number of threads that can access a
resource simultaneously. Unlike a lock, which allows only one thread to access a resource, a
semaphore can allow a fixed number of threads to access a resource concurrently.
Example Using threading.Semaphore:
import threading
semaphore = threading.Semaphore(2) # Allow up to 2 threads to access
def task(thread_id):
with semaphore:
print(f"Thread {thread_id} is working")
# Simulate a task by sleeping
import time
time.sleep(2)
print(f"Thread {thread_id} has finished")
threads = [threading.Thread(target=task, args=(i,)) for i in range(5)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
___________________________________________________________________________
5. Publish-Subscribe Model With Examples
Introduction:
The Publish-Subscribe (Pub-Sub) model is a messaging pattern used in distributed
systems where the sender (publisher) of a message is decoupled from the receiver
(subscriber). In this model, publishers and subscribers do not communicate directly with
each other. Instead, they communicate via an intermediary, often referred to as a message
broker or event bus.
This model is widely used in systems where multiple parties need to consume the
same data, or where data producers and consumers should remain independent of each
other. It is commonly employed in event-driven architectures, real-time applications, and
cloud-based solutions like messaging queues.
Benefits:
Scalability: The model allows for easy scaling because publishers and subscribers can be
added independently. A publisher can have multiple subscribers without any change in its
code.
Decoupling of Components: Publishers and subscribers don’t need to be aware of each
other. This allows components to evolve independently, fostering modularity and reducing
dependencies.
Efficiency: Multiple subscribers can receive messages from the same publisher, making it an
efficient way to deliver messages to different consumers. The Pub-Sub model is also ideal
for real-time data delivery.
Flexibility: New subscribers can be added dynamically without modifying the existing
system. Similarly, publishers can produce messages without knowing how many subscribers
there are, providing flexibility in system design.
Load Distribution: The message broker can manage how messages are delivered, distribute
load, and handle issues like message queuing or buffering, making the system more robust.
Real-Time Updates: This model is particularly well-suited for systems where real-time
updates are critical, such as stock market applications, chat systems, IoT applications, and
more.
Components of MapReduce
Mapper: Takes the input data and transforms it into intermediate key-value pairs.
Combiner (Optional): Acts as a mini-reducer that performs a local reduce operation on the
output of the mapper before sending the data to the reducer, reducing data transfer across
the network.
Reducer: Processes intermediate key-value pairs, groups by key, and produces the final
output.
Shuffling and Sorting: Intermediate key-value pairs are shuffled and sorted to ensure that all
values for the same key are sent to the same reducer.
Input Data: The input data is split into smaller chunks and fed to the mappers.
Map Phase: Mappers process these chunks independently, producing intermediate key-
value pairs.
Shuffle and Sort: The intermediate data is shuffled and sorted by key so that all the values
associated with a specific key are grouped together.
Reduce Phase: Reducers aggregate the values for each key to generate the final output.
Output: The final output is written to a distributed storage system or returned to the user.