Unit_1_2_Python
Unit_1_2_Python
2. Introduction to Python
Low-level language:
o Closer to machine language (what the computer understands directly).
o Involves very specific tasks like moving data bit by bit.
o Example: Assembly Language.
High-level language:
o Easier for humans to read and write.
o Uses abstract operations like "print on screen" or "show menu".
o Example: Python, Java, C++.
📝 High-level languages help us focus on solving problems instead of worrying about how the computer
works inside.
General-purpose:
o Can be used for building all kinds of software (games, apps, websites, etc.).
o Example: Python – you can build websites, games, software tools, and more.
Domain-specific:
o Built for one specific task or field.
o Example: Adobe Flash – designed mainly for animations and interactive web elements.
o Not suitable for tasks like financial analysis or data processing.
📝 Python is general-purpose. That’s why it’s so widely used.
📝 Python is interpreted, which is why it’s great for beginners – error messages are helpful and easier to
understand.
🔎 What is Python?
A general-purpose programming language.
Easy to read and write.
You can use it to build almost anything (except things that need very close control of the computer
hardware).
🔺 Limitations of Python:
📝 In simple words: Python is great for learning and building apps, but not the best for highly sensitive or
long-term projects.
✅ Advantages of Python
1. Simple and easy to learn – Good for beginners.
2. Interactive and interpreted – Helpful for understanding what your code is doing.
3. Huge community and tons of free libraries:
o You don’t need to build everything from scratch.
o Examples: NumPy (math), Flask (web), Pygame (games), etc.
📝 This book helps you focus on thinking like a programmer, not memorizing Python rules.
🔹 Major Versions
📝 This means older Python code may not work in newer versions.
📌 Final Thoughts
Python is just a tool to teach you how to think and solve problems using programming.
Once you learn that, switching to any other language is much easier.
2.1. The Basic Elements of Python (Simplified Notes)
A Python program (also called a script) is a set of instructions that you write to get the computer to do
something.
These instructions can include:
o Definitions (like defining variables or functions)
o Commands (also called statements) that tell Python what to do
✅ Tip: You can try code directly in the Python shell. It's great for practice and learning.
A command (also known as a statement) is a line of code that tells Python to perform an action.
For example:
This tells Python to print the message 'Yankees rule!' to the screen.
📝 In Python 2, the print statement doesn't require parentheses. In Python 3, you must use parentheses:
print('Yankees rule!')
✅ Output:
Yankees rule!
But not in Boston!
Yankees rule, but not in Boston!
🎯 What Happened?
Concept Description
Multiple values If passed to print, they're printed in the given order with spaces.
🧠 What is an Expression?
An expression is a combination of objects and operators that Python evaluates to produce a value.
💬 Examples:
3 + 2 # Result: 5 → type int
3.0 + 2.0 # Result: 5.0 → type float
3 != 2 # Result: True → type bool
Python’s built-in type() function tells you the type of any object.
+ Addition 2 + 3 5
- Subtraction 5 - 2 3
* Multiplication 3 * 4 12
** Exponent (Power) 2 ** 3 8
// Integer Division 6 // 4 1
% Modulus (Remainder) 7 % 4 3
🔁 Operator Precedence
Python follows a specific order when evaluating expressions.
🔍 Comparison Operators
== Equal 3 == 3 True
and True if both conditions are True True and False False
>>> 3 + 2
🎓 Quick Recap
Python manipulates objects, and every object has a type.
Scalar types: int, float, bool, and None.
Expressions combine values and operators to produce new values.
Use type() to check an object’s type.
Know the basic operators and how to use comparison and boolean logic.
Variables in Python (and programming in general) are like labels for data. They are used to store values that
you can use later in your program. Instead of writing a number or a string repeatedly, you can assign it to a
variable and then refer to that variable.
Example:
pi = 3
radius = 11
area = pi * (radius ** 2)
In this code:
The key here is that variables store objects. An object can be a number, a string, or even a more complex
data type like a list or a function.
Visualizing it: When we assign values to variables, we can think of variables as boxes that store values.
Here's a quick illustration:
o Initially:
pi -> 3
radius -> 11
You can rebind a variable to a new value later in the program. For example, changing the value of radius:
radius = 14
This doesn’t affect the value of area because area is still bound to the result of pi * (11 ** 2), not to
the new value of radius. Essentially, area remembers its original value.
In Python, a variable is just a name. It doesn’t "hold" or "contain" a value itself; rather, it is linked or
associated with the value. That’s why Python allows multiple variables to refer to the same value. You can
have many variables pointing to the same object, or even no variables pointing to an object.
Important: The value (or object) can exist in memory without having any variable pointing to it, or it can be
referred to by many variables.
In Python, variables are just names for objects, but good naming of variables plays a crucial role in making
the code understandable for humans.
Well-chosen variable names help programmers (and your future self) understand the code easily and
quickly.
Example:
a = 3.14159
pi = 3.14159
b = 11.2
diameter = 11.2
c = a * (b ** 2)
area = pi * (diameter ** 2)
o The code above calculates the area of a circle, but the second version uses more meaningful
variable names like pi and diameter. This makes it clearer what the variables represent, even
though both versions do the same thing.
o The first example uses a and b, which could make it hard to understand what the code is doing if
someone unfamiliar with it reads it.
Python Variable Rules
o and, or, not, if, else, while, for, class, try, except, etc.
and, as, assert, break, class, continue, def, del, elif, else, except, exec,
finally, for, from,global, if, import, in, is, lambda, not, or, pass, print,
raise, return, try, with, while, yield
Comments in Python
Comments are lines in the code that are ignored by Python. They are useful for adding explanations or notes
for humans reading the code.
Example:
o Anything after # is not processed by Python and is only there for the programmer.
Python allows you to assign values to multiple variables in a single line. This is called multiple assignment.
Example:
x, y = 2, 3
Python evaluates all expressions on the right-hand side before any of the assignments happen.
Swapping values: You can also swap the values of two variables using multiple assignment:
Example:
x, y = 2, 3
x, y = y, x # Swap the values
print('x =', x) # x = 3
print('y =', y) # y = 2
In this case, x becomes 3 and y becomes 2.
Summary
2.1.3 IDLE
What is IDLE?
IDLE is an integrated development environment (IDE) that comes pre-installed with Python.
It provides a user-friendly interface for writing, editing, and running Python programs, making it easier to
work with Python.
Typing Python programs directly into the shell (a command-line interface) is not convenient for larger
programs. An IDE like IDLE makes it much easier to write and manage Python code because it provides
various useful features like syntax highlighting, autocompletion, and smart indentation.
IDLE helps streamline the process of developing Python programs by combining a text editor and a Python
shell in one environment.
Features of IDLE
1. Text Editor:
o IDLE provides a built-in text editor that helps you write Python programs.
o The editor includes features like:
Syntax Highlighting: It highlights different parts of the code in different colors to make it
easier to read (e.g., keywords, variables, and functions).
Autocompletion: This feature suggests completions for code as you type, helping you to
write code faster and with fewer errors.
Smart Indentation: It automatically aligns your code properly to keep it neat and readable.
2. Shell:
o When you open IDLE, it also opens a Python shell window. This shell allows you to interactively type
and test Python commands one by one.
o The shell also has syntax highlighting to make your code easier to understand.
o You can execute single lines or small blocks of code in the shell to quickly test things.
3. Integrated Debugger:
o IDLE includes a debugger, which is a tool used to find and fix errors in your code. However, for now,
you can ignore the debugger and focus on writing and running your code.
How to Use IDLE
Starting IDLE:
o IDLE is just like any other application on your computer. You can start it by double-clicking on the
IDLE icon or by searching for "IDLE" in your computer’s start menu or applications list.
Main Features of the IDLE Interface:
o Shell Window: This is where you can type individual Python commands and see their output
immediately.
o File Menu: This menu allows you to work with Python files.
Create a new file: To start a new Python program, click on "File" → "New File", which opens
a new window where you can write your Python code.
Open an existing file: To edit an existing Python program, use "File" → "Open" to load a .py
file.
Save the current file: To save your work, click on "File" → "Save" and choose a location to
store your file (files are saved with the .py extension).
o Edit Menu: This menu provides typical text-editing commands like copy, paste, and find. It also
includes Python-specific commands:
Indent Region: Automatically indents selected lines of code.
Comment Out Region: Adds a # symbol before each line in the selected region, which makes
those lines comments (ignored by Python).
Conclusion
IDLE is a simple and powerful tool for writing Python programs. It provides a text editor and a shell for
writing and testing Python code.
It is beginner-friendly, with features like syntax highlighting, autocompletion, and smart indentation,
making coding in Python easier and more efficient.
Straight-line programs are simple and execute one statement after another, without making any decisions.
They are quite limited because they can only execute a fixed sequence of statements.
Branching programs, on the other hand, include conditional statements that allow the program to decide
which block of code to execute based on certain conditions. This makes them more flexible and interesting,
as the program can take different paths depending on inputs or other factors.
Conditional Statements
The simplest branching statement is a conditional statement (also known as an if-else statement).
if Boolean_expression:
# Block of code if condition is True
else:
# Block of code if condition is False
if x % 2 == 0:
print('Even')
else:
print('Odd')
Explanation:
o x % 2 == 0 checks if x is divisible by 2 (i.e., if it is even).
o If the condition is True, the program prints "Even". If it's False, it prints "Odd".
o The print('Done with conditional') line always runs after the conditional is evaluated.
Note on Indentation:
o In Python, indentation is crucial. It defines the blocks of code that belong to the if or else
statements.
o If you mistakenly change the indentation level, Python will raise an IndentationError.
o Python uses indentation instead of braces {} (like in C or Java), making the code visually organized
and easy to understand.
Nested Conditionals
You can also have nested conditionals, where one if statement appears inside another if or else block. This
allows you to check multiple conditions in a structured way.
Example:
Explanation:
o First, it checks if x is divisible by 2.
o If x is divisible by 2, it checks whether it is also divisible by 3.
o Depending on these conditions, the program prints the appropriate message.
The elif keyword stands for "else if". It's used when you want to test multiple conditions.
The structure looks like this:
if condition1:
# Block for condition1
elif condition2:
# Block for condition2
else:
# Block for all other cases
x = 5
y = 10
z = 2
Explanation:
o It compares x, y, and z to determine the smallest number.
o It uses the and operator to check two conditions in one line.
Constant-time programs: A program where the maximum running time does not depend on the size of the
input.
o For a branching program, the maximum time it takes to run is still bounded by the number of lines
of code.
o This means that a branching program with n lines of code cannot take more than n steps to execute,
even if it contains conditionals.
Finger Exercise
Problem: Write a program that examines three variables—x, y, and z—and prints the largest odd number among
them. If none of them are odd, it should print a message to that effect.
x = 3
y = 4
z = 5
Explanation:
o The program first checks if x, y, or z is odd by using the modulus operator %.
o Then, it checks if that odd number is the largest among the three.
o If none of them are odd, it prints that no odd numbers were found.
✅ Examples:
'abc' # A string using single quotes
"abc" # A string using double quotes
'123' # A string, NOT the number 123
🟡 Note: '123' is not the same as 123. One is a string of characters, the other is a number.
Expression 1:
>>> 'a'
✅ Output:
'a'
Expression 2:
>>> 3 * 4
✅ Output:
12
Expression 3:
>>> 3 * 'a'
✅ Output:
'aaa'
👉 This repeats the string 'a' 3 times. This is called string repetition.
Expression 4:
>>> 3 + 4
✅ Output:
Expression 5:
>>> 'a' + 'a'
✅ Output:
'aa'
👉 This is string concatenation. It joins two strings into one.
🔁 2 * 'John' → 'JohnJohn'
❌ Output:
🟡 Explanation:
Python thinks a is a variable name, not a string. Since you never defined a before, it throws an error.
✅ Fix:
>>> 'a'
Expression:
>>> 'a' * 'a'
❌ Output:
NameError
TypeError
This is better than letting your program run incorrectly with mysterious bugs.
✅ Output:
False
🟡 Explanation: Python says any number is considered less than any string when comparing.
But this is arbitrary and most other languages would show an error.
✅ Output:
📌 8. Indexing Strings
✅ Examples:
>>> 'abc'[0] # First character
'a'
❌ Error:
>>> 'abc'[3]
Output:
'a' → index 0
'b' → index 1
'c' → index 2
✂️ 9. Slicing Strings
Syntax:
s[start:end]
✅ Example:
>>> 'abc'[1:3]
'bc'
🔄 Default Values:
>>> 'abc'[:2]
'ab' # Same as 'abc'[0:2]
>>> 'abc'[1:]
'bc' # From index 1 to end
>>> 'abc'[:]
'abc' # Full string
✅ Summary Table
Expression Result Meaning
'a' + 'b' 'ab' Concatenate strings
3 * 'a' 'aaa' Repeat string 3 times
len('abc') 3 Number of characters
'abc'[0] 'a' First character
'abc'[-1] 'c' Last character
'abc'[1:3] 'bc' Substring from index 1 to 2
💡 Explanation:
🔷 1. What Is Input?
In programming, input means getting data from the user — usually via the keyboard. Python gives us built-
in functions to do this.
Then:
🔹 So: input() always returns a string, no matter what the user types.
🔸 Example:
name = input("Enter your name: ")
print("Welcome,", name)
Output:
Enter your name: Alice
Are you really Alice ?
Are you really Alice?
<class 'str'>
❌ So this:
print(n * 4)
Will print:
3333
Because:
You’re repeating the string "3" four times, not multiplying the number 3.
Example:
n = int(input("Enter an integer: "))
print(n * 4)
12
Another example:
f = float(input("Enter a decimal number: "))
print("You entered:", f)
print("As integer:", int(f)) # Truncates, doesn’t round
🔷 7. Summary Table
Concept Python 2.7 Python 3.x
Input function raw_input() / input() input()
raw_input() return type str Not available
input() return type evaluated result (unsafe) str (safe, default)
Convert to int int("3") Same
Convert to float float("3.5") Same
Print with multiple args Adds space automatically Same
Print with concatenation Must manually manage spaces Same
print("Hello", name + "! You will be", age + 5, "years old in 5 years.")
🔷 1. What is Iteration?
Iteration, also known as looping, is the process of repeating a set of instructions until a certain condition
is met.
Think of it like a washing machine cycle — it keeps spinning (executing) until the time runs out or the
clothes are clean (a condition becomes false).
Start
|
[Condition?] ---> No ---> Exit loop
|
Yes
|
[Do something]
|
[Go back to Condition]
🔍 Step-by-step Explanation:
Final Output:
3*3 = 9
x = -1
itersLeft = x
x = -3
ans = 0
itersLeft = abs(x)
This works for both positive and negative x, but the output will always be positive.
for i in range(10):
num = int(input("Enter integer #" + str(i+1) + ": "))
if largest_odd is None:
print("No odd number was entered.")
else:
print("The largest odd number entered is:", largest_odd)
🔍 Explanation:
In Python, scalar types are basic types of data that hold a single value. These include:
Scalar types are immutable, meaning once you assign a value to a scalar type, that value cannot be changed. For
example:
x = 10 # x is an int
x = 20 # Now x is 20, but you can't change the value of 10 directly
Structured Type: str
my_string = "hello"
print(my_string[0]) # Output: 'h'
my_string = "hello"
print(my_string[1:4]) # Output: 'ell'
Tuples
A tuple is similar to a string, but it can store multiple types of elements, such as integers, strings, or other tuples.
Tuple Syntax:
Accessing Tuple Elements: You can access elements in a tuple using indexing (just like strings).
print(my_tuple[0]) # Output: 1
Lists
A list is an ordered collection of elements, and it is mutable, meaning you can modify, add, or remove elements from
it.
List Syntax:
Accessing List Elements: Like tuples, you can access elements using indexing.
print(my_list[0]) # Output: 1
Mutability: Unlike tuples, lists are mutable, so you can change, add, or remove elements.
Dictionaries (dict)
A dictionary is an unordered collection of key-value pairs. Each key in the dictionary maps to a corresponding value.
Dictionary Syntax:
my_dict = {'name': 'Alice', 'age': 25, 'city': 'New York'}
Accessing Dictionary Values: You access values by referring to their associated key.
Adding or Modifying Entries: You can modify or add key-value pairs in a dictionary.
Mutability: Dictionaries are mutable, allowing you to add, modify, or delete key-value pairs.
3. Mutability in Python
Mutability refers to whether the contents of an object can be changed after its creation. Here’s the distinction
between mutable and immutable objects:
Immutable Objects: These cannot be modified after creation. Examples are int, float, str, and tuple.
x = 5
# x = 10 # This will reassign x, but 5 cannot be changed directly
Mutable Objects: These can be modified after creation. Examples include list, dict, and set.
my_list = [1, 2, 3]
my_list[0] = 10 # This modifies the list
print(my_list) # Output: [10, 2, 3]
This distinction is important because mutable objects allow for more flexibility but also carry risks of accidental
modification (e.g., if shared between functions).
4. Higher-Order Functions
In Python, functions are first-class objects, meaning they can be assigned to variables, passed as arguments, or
returned from other functions. This is the basis for higher-order functions.
map(): Applies a function to all items in an iterable (like a list) and returns an iterator of the results.
numbers = [1, 2, 3, 4]
squares = map(lambda x: x ** 2, numbers)
print(list(squares)) # Output: [1, 4, 9, 16]
filter(): Filters elements of an iterable by applying a function to each element and only including those for
which the function returns True.
numbers = [1, 2, 3, 4, 5]
even_numbers = filter(lambda x: x % 2 == 0, numbers)
print(list(even_numbers)) # Output: [2, 4]
reduce() (from functools module): Reduces an iterable to a single value by applying a function
cumulatively.
You can create your own higher-order function that accepts other functions as arguments:
# Example usage:
result = apply_function(lambda x: x ** 2, [1, 2, 3, 4])
print(result) # Output: [1, 4, 9, 16]
Summary
Scalar Types: Basic types like int, float, and str, which hold a single value and are immutable.
Structured Types: More complex types like tuple, list, and dict. Tuples are immutable, while lists and
dictionaries are mutable.
Mutability: Mutable objects can be changed after creation, while immutable objects cannot.
Higher-Order Functions: Functions that can accept other functions as arguments or return functions. These
provide flexibility and reusability.
This chapter sets the foundation for working with more advanced data structures in Python, including tuples, lists,
dictionaries, and higher-order functions. These concepts are crucial for writing clean and efficient code in Python.
5.1. Tuples
A tuple in Python is an ordered collection of elements (just like a list). However, the key difference between a tuple
and a list is that tuples are immutable. This means that once you create a tuple, you cannot modify it (no adding,
removing, or changing elements).
Ordered: The elements in a tuple have a specific order, which means the position of each element matters.
Heterogeneous: A tuple can store elements of different types (e.g., integers, strings, floats, etc.), unlike lists
which are usually homogeneous.
Immutable: Once a tuple is created, you cannot modify it (no changes allowed).
Creating a Tuple
You can create a tuple by writing a comma-separated list of elements inside parentheses ().
Example 1: Creating an Empty Tuple
t1 = () # This is an empty tuple.
print(t1) # Outputs: ()
In this case, the tuple t1 doesn't contain any elements, so it prints an empty tuple ().
An integer: 1
A string: 'two'
Another integer: 3
Single-Element Tuples
In Python, you might think that creating a tuple with one element would look like this:
singleton_tuple = (1)
However, this is not a tuple. It's just the integer 1 inside parentheses, which is not a tuple.
To create a tuple with a single element, you must include a comma after the element:
The comma tells Python that you're creating a tuple, not just grouping the value.
Tuple Operations
1. Concatenation: You can combine two tuples using the + operator, which concatenates them.
t1 = (1, 'two', 3)
t2 = (t1, 3.25)
print(t2) # Outputs: ((1, 'two', 3), 3.25)
You can also concatenate two tuples to make a new, longer tuple:
This combines the elements of t1 and t2, resulting in a new tuple with five elements.
2. Indexing: Just like lists, you can access individual elements of a tuple using their index. Remember, Python
uses zero-based indexing, so the first element has index 0, the second has index 1, and so on.
In this case, t1 + t2 creates a tuple with five elements, and the code [(t1 + t2)[3]] retrieves the 4th
element (which is (1, 'two', 3)).
3. Slicing: You can slice a tuple to extract a part of it. The slicing syntax is:
tuple[start:stop]
This code slices the concatenated tuple from index 2 to index 4 (index 5 is not included). The result is a sub-
tuple containing three elements.
You can iterate over the elements of a tuple using a for loop. This is useful when you want to perform an operation
on each element in the tuple.
In this example:
The function findDivisors finds the common divisors of n1 and n2 and returns them in a tuple.
A for loop is used to check if each number i divides both n1 and n2. If it does, i is added to the tuple
divisors.
The final result is printed as a tuple of common divisors: (1, 2, 4, 5, 10, 20).
total = 0
for d in divisors:
total += d # Add each divisor to total
print(total) # Outputs: 42
Tuples are like lists but immutable (you can't modify them after creation).
They are ordered collections of elements that can be of any data type.
To create a single-element tuple, always include a comma after the element.
Tuples can be concatenated, indexed, and sliced.
You can iterate over a tuple’s elements using a for loop.
Tuples can hold multiple types of elements, and you can even have a tuple inside another tuple (nested
tuples).
When you know the number of elements in a sequence, you can unpack it into separate variables using Python's
multiple assignment syntax. Here’s how it works with tuples and strings:
In this example:
This means that the values from the tuple (3, 4) are unpacked and assigned to x and y.
In this example:
Multiple assignment is very convenient when working with functions that return sequences. Instead of manually
accessing each element of the returned sequence, you can unpack them directly into variables in one line.
Here’s a practical example using a function that returns multiple values as a tuple:
Function: findExtremeDivisors
This function finds the smallest common divisor greater than 1 and the largest common divisor of two given
numbers (n1 and n2).
The function findExtremeDivisors(n1, n2) loops through integers starting from 2 and checks if they
are divisors of both n1 and n2.
For each common divisor, the function updates the smallest (minVal) and largest (maxVal) divisors found
so far.
It finally returns a tuple containing the smallest and largest common divisors.
You can use multiple assignment to unpack the result of this function into two variables:
The function call findExtremeDivisors(100, 200) returns a tuple (2, 100), where 2 is the smallest
common divisor and 100 is the largest common divisor.
The multiple assignment minDivisor, maxDivisor = findExtremeDivisors(100, 200) unpacks
this tuple:
o minDivisor gets the value 2
o maxDivisor gets the value 100
So, after executing this statement, the variables minDivisor and maxDivisor are assigned the respective values
from the tuple.
Convenience: Multiple assignment simplifies extracting values from tuples, lists, or strings.
Readability: Instead of accessing each element of a sequence separately, you can directly unpack them into
variables.
Efficient: This feature makes it easy to handle functions that return multiple values, especially when working
with fixed-size sequences like tuples.
Conclusion:
Multiple assignment is a simple but powerful feature in Python that allows you to directly unpack elements from
sequences like tuples or strings into multiple variables. It's particularly useful when working with functions that
return multiple values as a tuple, like the findExtremeDivisors function. This makes your code cleaner and more
readable.
This list L contains three elements: a string 'I did it all', an integer 4, and another string 'love'.
To access each element in the list, you can use an index. The index starts from 0 (zero) and increments by 1
for each subsequent element.
Example code:
I did it all
4
love
Indexing and Slicing Lists
Just like tuples, lists support indexing, where you can access individual elements by their position. Slicing also works
with lists, which allows you to access a range of elements. However, in the case of lists, because they are mutable,
you can modify the list after slicing.
For example:
# Example of slicing
result = [1, 2, 3, 4][1:3][1]
print(result) # Output: 3
The main difference between lists and tuples is that lists are mutable, meaning that you can modify them after
creation, while tuples and strings are immutable and cannot be altered once they are created.
For example:
Immutable Object: A tuple cannot be changed. If you try to modify its elements, Python will raise an error.
Mutable Object: A list, on the other hand, can be modified. You can change, add, or remove elements in a
list after it has been created.
Aliasing and Mutability
When a list is assigned to another variable, both variables refer to the same object in memory. This phenomenon is
called aliasing.
Example:
In this case, Univs is a list containing two lists: Techs and Ivys. However, when we modify Techs, we also modify
the first element of Univs since both Techs and the first element of Univs refer to the same list.
Techs.append('RPI')
print('Univs =', Univs)
As you can see, the change made to the list Techs is reflected in Univs, because both variables point to the same
list object.
To check whether two lists are pointing to the same object in memory, we can use the built-in id() function, which
returns the unique identifier of an object:
The id() function helps confirm whether two variables point to the same object. It is crucial for understanding how
mutability works in Python. For example, if we print the IDs of two lists:
print(Univs == Univs1) # True, because the lists have the same values
print(id(Univs) == id(Univs1)) # False, because they are two distinct objects
True
False
Although Univs and Univs1 have the same values (i.e., they contain the same lists), they are two distinct objects in
memory, as evidenced by their different id() values.
Mutating Lists
Because lists are mutable, you can perform operations that modify them directly. For example, the append()
method adds a new element to the end of a list:
Techs.append('RPI')
This directly changes the original Techs list. Unlike immutable types like tuples, you don't create a new object when
you modify a list; you change the existing one.
List Methods
Python provides a variety of methods to manipulate lists. Some of the most common ones include:
Many list methods, such as append(), extend(), and sort(), have side effects: they modify the list in place.
However, some operations like + and slice() return a new list without modifying the original.
Conclusion
Lists are mutable, allowing you to modify their elements after creation.
The mutability of lists can lead to aliasing, where multiple variables refer to the same object, which can lead
to unexpected changes in the list.
Python provides several methods for modifying and querying lists, but it's important to be mindful of the
side effects caused by mutating operations.
Mutating (changing) a list while iterating over it can cause unexpected results.
Then we run:
L1 = [1, 2, 3, 4]
L2 = [1, 2, 5, 6]
removeDups(L1, L2)
print('L1 =', L1)
🔴 Unexpected Output:
L1 = [2, 3, 4]
Wait, why? 😕
Start: L1 = [1, 2, 3, 4]
First element e1 = 1 → Found in L2 → Remove 1 → L1 = [2, 3, 4]
Python’s internal counter moves to next index (1), but now L1[1] is 3 (not the original 2!)
So 2 is skipped
Hence the mistake: mutating the list during iteration skips elements
Now it works! Because you’re looping over a copy of L1, not L1 itself.
L1[:] means: “Take all elements from start to end” → a new copy of the list
Changing L1 now doesn’t affect what you're iterating over
This just gives a new name for the same list — both L1 and newL1 refer to the same object. So if you
mutate L1, newL1 is also affected. It’s not a copy, just a different label.
If you used shallow copy here, both would change together — which is dangerous when working with
nested or mutable structures.
💡 Summary of 5.2.1
Concept Explanation
Mutating list during loop Can cause skipped elements or bugs
Concept Explanation
Safe way to loop Clone the list first using L1[:]
Why not use newL1 = L1? It’s not a clone, just a new name
Use copy.deepcopy() When the list has lists or mutable objects inside
🧠 Basic Syntax:
[expression for variable in iterable]
This will:
🔹 Example:
L = [x**2 for x in range(1, 7)]
print(L)
🔸 Output:
[1, 4, 9, 16, 25, 36]
L = []
for x in range(1, 7):
L.append(x**2)
🔸 Output:
[1, 4, 9]
🔍 Why?
Output:
[1, 2, 3, 4]
💡 Nested Comprehension:
Which gives:
[6, 8]
✅ Summary of 5.2.2
Topic Summary
List comprehension A short way to build lists
Syntax [expression for variable in iterable]
Add filters Use if to include only certain elements
Nested loops You can include more than one for, but keep it readable
These are sequence types because they hold items in ordered fashion and allow index-based access.
All three types (str, tuple, list) support the following operations:
All three sequence types behave similarly here, but differ in:
L = [1, 2, 3, 4, 5, 6]
evenElems = []
for e in L:
if e % 2 == 0:
evenElems.append(e)
print(evenElems) # Output: [2, 4, 6]
📌 Example:
Strings (str) have many powerful methods. But remember: strings are immutable — methods return new strings,
not modify originals.
s.replace(old, 'apple'.replace('p',
Replace substrings 'abble'
new) 'b')
Method Purpose Example Output
['a', 'b',
s.split(d) Split by delimiter d 'a b c'.split(' ')
'c']
📌 Special Note:
💡 Summary
Mutable? ❌ ❌ ✅
Ordered? ✅ ✅ ✅
Indexable? ✅ ✅ ✅
Use Case Text processing Fixed data (e.g., coordinates) Modifiable collections
🧠 Pro Tips
🔤 2. String (str)
📌 Definition:
✅ Features:
🔍 Example:
s = "Hello"
print(s[1]) # e
print(s + " World") # Hello World
print(s.upper()) # HELLO
❌ Immutable Example:
s = "Hello"
s[0] = "h" # ❌ Error! Strings cannot be modified.
🔗 3. Tuple (tuple)
📌 Definition:
✅ Features:
🔍 Example:
t = (1, "apple", 3.14)
print(t[0]) # 1
⚠️ Singleton Tuple:
single = (3,) # Must include comma to be a tuple
❌ Immutable Example:
t = (1, 2, 3)
t[0] = 10 # ❌ Error! Tuples are immutable.
✅ Use in Dictionaries:
location = (19.07, 72.87)
data = {location: "Mumbai"}
📋 4. List (list)
📌 Definition:
✅ Features:
🔍 Example:
fruits = ["apple", "banana", "cherry"]
print(fruits[1]) # banana
fruits[1] = "kiwi" # ✅ You can modify
print(fruits) # ['apple', 'kiwi', 'cherry']
📊 6. Comparison Table
Feature str tuple list
Mutable ❌ ❌ ✅
Can hold multiple types ❌ (only characters) ✅ ✅
Can be dictionary key ✅ ✅ ❌
Common use Text Fixed data Dynamic data
Has built-in methods ✅ (many) Few ✅ (many)
🧠 Conclusion
Use strings for text, where characters don’t change.
Use tuples when you need an unchangeable, fixed group of values.
Use lists when you need a dynamic, flexible container.
5.5. Dictionaries
🔑 What is a Dictionary in Python?
A dictionary (dict) in Python is a mutable, unordered collection of key-value pairs.
Think of it like a real-life dictionary: you look up a word (key), and get its meaning (value).
Output:
⚠️ Important Notes:
Dictionaries are unordered → You cannot access elements using an index like d[0].
Each key must be immutable (e.g., strings, numbers, or tuples).
Keys are unique, values can repeat.
Usage:
print(translate('I drink good red wine, and eat bread.', dicts, 'English to French'))
Output:
Functions
Functions in Python
A function is like a mini program within a program. It contains a group of statements designed to perform a
specific task. Functions are useful when you have multiple tasks to do in a program. Instead of writing the
same code again and again, you can create functions to handle those tasks.
In addition to these, you can also create your own functions, called user-defined functions.
Advantages of Functions:
1. Task Handling: Functions are great for processing data, making calculations, or performing any task
in software development.
2. Reusability: Once you write a function, you can use it multiple times, saving you from rewriting the
same code. This avoids code repetition.
3. Modularity: Functions break down a large task into smaller sub-tasks. Each sub-task can be handled
by a separate function, making the code more organized and manageable.
4. Easy Maintenance: If you need to add new features to a program, you can simply write a new
function. If a feature is no longer needed, you can remove the related function without affecting the
rest of the program.
5. Easier Debugging: If there’s an error, you can fix the issue in just the relevant function without
affecting the rest of the code.
6. Shorter Programs: Using functions helps reduce the overall length of the program, making it more
efficient.
Function:
def greet():
print("Hello, World!")
Method:
Examples:
class MyClass:
def greet(self):
print("Hello from the method!")
class MyClass:
@staticmethod
def greet():
print("Hello from the static method!")
Key Differences:
Summary:
When you create your own functions, you define them first and then call them by their name. When a
function is inside a class, it becomes a method, and you call it using either an object or the class name.
Defining a Function in Python:
In Python, you define a function using the def keyword, followed by the function name, parentheses (which may
include parameters), and a colon to indicate the start of the function's body.
Example:
o This is a docstring. It's a short description that explains what the function does.
o It is written between triple quotes (""" or '''), which allows for multi-line comments or
descriptions.
o Docstrings are optional, but it's a good habit to include them to describe what the function does.
3. Function Logic:
c = a + b
print(c)
o c = a + b: The function calculates the sum of the values passed to a and b, and stores the result in
c.
o print(c): This line displays the result of the sum to the screen.
To use this function, you need to call it with two numbers as arguments:
sum(3, 5)
Output:
Key Points:
1. Parameters: Variables like a and b inside the parentheses are called parameters. They are placeholders for
the actual values (arguments) passed to the function when it's called.
2. Function Body: The body of the function contains the logic (the code that gets executed) and should be
properly indented.
3. Docstrings: While optional, it's good practice to write docstrings at the start of the function to explain its
purpose.
4. Indentation: All statements inside the function should be indented. This is a fundamental rule in Python.
Summary:
Example:
sum(10, 15)
How It Works:
1. When you call sum(10, 15), Python "jumps" to the function definition and assigns a = 10 and b =
15.
2. The function body then executes, adding a and b together, storing the result in c, and printing it.
Output:
Sum= 25
Multiple Calls:
You can call the same function multiple times with different arguments:
Output:
Sum= 25
Sum= 12.25
Here, the first call uses integers, and the second uses floating-point numbers. Notice how the function
handles different types of data without any change to its definition.
Understanding Arguments:
Arguments are the values you pass to the function when you call it. In the example above, 10 and 15
are arguments.
Dynamic Typing: The parameters a and b don’t know what type of data they will receive until the
function is called. At runtime, Python automatically assigns the type of data based on the arguments
passed:
o Integer arguments: When calling sum(10, 15), both a and b will be integers.
o Float arguments: When calling sum(1.5, 10.75), a and b will be floats.
This feature is known as dynamic typing, meaning the type of a variable is determined at runtime, not at
compile-time. This is different from languages like C or Java, where you must define the type of variables
beforehand.
Key Points:
Summary:
The return statement is used to send a result back from the function to where the function was called. Once a
function hits a return statement, the function terminates, and the specified value is returned to the caller. This
value can then be assigned to a variable or used directly.
The function add takes two arguments x and y, adds them together, and returns the result. If you call
add(5, 3), it will return 8.
Returning multiple values: You can return multiple values from a function by separating them with commas.
When you call sum_and_diff(5, 3), it returns both the sum (8) and the difference (2). You can capture
these values using multiple variables:
Returning a list or other data types: Functions can return collections like lists, dictionaries, tuples, or even
more complex data structures.
In Program 2, the sum function is modified to return the sum of two numbers rather than printing it:
Output:
The sum is: 25
The sum is: 12.25
The function returns the sum of a and b and stores it in the variable x or y.
The return statement allows us to use the result of the function in other parts of the program, like printing
or storing it in a variable.
In Program 3, we define a function even_odd that checks whether a number is even or odd:
def even_odd(num):
""" to know num is even or odd """
if num % 2 == 0:
print(num, "is even")
else:
print(num, "is odd")
This function doesn't return any value. It simply prints whether the number is even or odd. This is an example of a
void function, meaning it performs an action but does not return anything.
In Program 4, the fact function calculates the factorial of a number by multiplying all the integers from n down to
1:
def fact(n):
""" to find factorial value """
prod = 1
while n >= 1:
prod *= n
n -= 1
return prod # return the calculated factorial
Output:
Factorial of 1 is 1
Factorial of 2 is 2
Factorial of 3 is 6
Factorial of 4 is 24
Factorial of 5 is 120
Factorial of 6 is 720
Factorial of 7 is 5040
Factorial of 8 is 40320
Factorial of 9 is 362880
Factorial of 10 is 3628800
The fact() function calculates the factorial of a given number n. For each number from 1 to 10, it returns the
factorial value.
In Program 5, we define a function prime that checks whether a given number is prime or not:
def prime(n):
""" to check if n is prime or not """
x = 1 # this will be 0 if not prime
for i in range(2, n):
if n % i == 0: # if divisible by any number
x = 0
break
return x # returns 1 for prime, 0 for not prime
The prime() function returns 1 if the number is prime and 0 if it's not. The if statement checks the result and
prints accordingly.
In Program 6, we use the prime() function to generate the first n prime numbers:
def prime(n):
""" to check if n is prime or not """
x = 1
for i in range(2, n):
if n % i == 0:
x = 0
break
return x
The loop generates prime numbers by calling the prime() function until it finds the required number of primes. The
prime numbers are printed as they are found.
Key Concepts:
1. return Statement:
o Allows the function to send a result back to the caller.
o The result can be any data type (integer, float, list, etc.).
2. Void Functions:
o Functions that do not return anything (they may only perform actions like printing).
3. Dynamic Return Types:
o Python functions can return different types of values based on the input, making it flexible.
By using the return statement, you can make functions reusable and modular, which is a key feature in functional
programming.
Key Concepts
1. Returning Multiple Values: To return multiple values from a function, you can list them after the return
keyword, separated by commas. Python will automatically pack these values into a tuple.
Example:
return a, b, c
2. Tuple Unpacking: When calling the function, you can unpack the returned tuple into separate variables.
Example:
x, y, z = function()
Here, the returned tuple will be unpacked into the variables x, y, and z.
To demonstrate this concept, we can create a function sum_sub() that calculates the addition and subtraction of
two numbers, and returns both results.
Code:
# Function that returns two results
def sum_sub(a, b):
"""This function returns results of addition and subtraction of a, b."""
c = a + b
d = a - b
return c, d
Let's expand on this concept by creating a function that returns four values: addition, subtraction, multiplication, and
division.
Code:
# Function that returns multiple results
def sum_sub_mul_div(a, b):
"""This function returns results of addition, subtraction, multiplication, and
division of a, b."""
c = a + b
d = a - b
e = a * b
f = a / b
return c, d, e, f
Key Points:
1. Tuple Packing: When multiple values are returned from a function, they are automatically packed into a
tuple.
Example: return a, b, c returns a tuple (a, b, c).
2. Tuple Unpacking: You can unpack the returned tuple into separate variables when calling the function. The
number of variables on the left side must match the number of values returned.
3. Multiple Values in Functions: You can return any number of values from a function. This feature makes it
easier to handle multiple results without the need for global variables or complex data structures.
4. Using Loops: When several values are returned, you can easily loop through the tuple to access each value
using a for loop or while loop.
Conclusion
In Python, functions can return multiple values, and these values are returned as a tuple. You can unpack the tuple
into separate variables when calling the function, which makes the code cleaner and more readable. This feature is
useful for functions that need to return multiple results, and Python's ability to return multiple values as tuples is
one of the language's strengths.
So, just like you can pass a number, string, or object, you can pass a function too!
x = display("Krishna")
print(x)
💡 Explanation:
🧠 Output:
Hai Krishna
🔁 Alternate idea:
def display(str):
return 'Hai ' + str
print(display("Krishna"))
💡 Explanation:
🧠 Output:
How are U? Krishna
def message():
return 'How are U? '
print(display(message()))
💡 Explanation:
🧠 Output:
Hai How are U?
def message():
return 'How are U? '
💡 In this version:
fun = display()
print(fun())
💡 Explanation:
🧠 Output:
How are U?
🧠 Summary Table
Concept Code Example Behavior
Assign function result x = display("Krishna") x gets the result (string) of the function
Assign function itself x = display x becomes another name for the function
1. Pass by Value: A copy of the value is passed to the function, so any changes made to it within the function
do not affect the original value outside the function.
2. Pass by Reference: A reference (or memory address) of the variable is passed to the function, so any
changes made inside the function will affect the original value outside the function.
In Python, however, neither of these concepts strictly applies. Instead, Python uses a model called Pass by Object
Reference or Pass by Assignment. Let's break this down step by step:
Objects in Python
In Python, everything is treated as an object. This includes not only primitive types like integers, floats, and strings,
but also data structures like lists, tuples, and dictionaries. All these objects are stored in memory, and each object is
assigned an identity (memory address), which can be accessed using the id() function.
Example:
x = 10
print(id(x)) # Prints the memory address of the object 10
When we assign a value to a variable in Python, we're actually assigning a reference to an object in memory, not the
value itself. In the case above, x is a reference (or label) to the object 10 in memory. So, instead of imagining
variables as boxes with values, it's better to think of them as names attached to objects in memory.
When we pass immutable objects to a function, Python passes a reference to the object. However, since immutable
objects cannot be modified (their value cannot be changed), any changes within the function will create a new
object.
x = 10 # Original object
print(x, id(x)) # Original value and memory address
modify(x) # Passing 'x' to the function
print(x, id(x)) # Value and memory address after calling the function
Explanation:
Output:
10 1617805016 # Original value and memory address
15 1617805096 # Modified value and new memory address inside the function
10 1617805016 # Original value and memory address after the function call
Since x is immutable (integers), any modification creates a new object, and the original x outside the function
remains unchanged.
Mutable objects, such as lists and dictionaries, behave differently. When passed to a function, changes made within
the function affect the same object because mutable objects can be modified in-place.
Explanation:
The list [1, 2, 3, 4] is an object in memory, and lst is a reference to this object.
When we call the function modify(lst), we're passing the reference to the list.
Inside the function, we modify the list by appending 9 to it. Since lists are mutable, the original list object is
modified, and the changes reflect outside the function as well.
The memory address (id(lst)) of the list remains the same before and after the function call because it's
the same object being modified.
Output:
[1, 2, 3, 4] 37762552 # Original list and memory address
[1, 2, 3, 4, 9] 37762552 # Modified list and memory address inside the function
[1, 2, 3, 4, 9] 37762552 # Modified list and memory address after the function call
Since the list is mutable, the modifications to the list inside the function are reflected outside the function as well.
If you create a new object inside the function (even for mutable types), that object is local to the function. The
original object outside the function will not be affected.
Output:
[1, 2, 3, 4] 29505016 # Original list and memory address
[10, 11, 12] 29505976 # New list and its memory address inside the function
[1, 2, 3, 4] 29505016 # Original list and memory address after the function call
Conclusion
In Python, when you pass an object to a function, you're passing a reference to that object. The behavior of the
object (whether it gets modified or not) depends on whether the object is mutable or immutable:
Immutable objects (e.g., integers, strings, tuples) cannot be modified. If you attempt to modify them,
Python creates a new object, and the changes are not reflected outside the function.
Mutable objects (e.g., lists, dictionaries) can be modified in-place, and the changes will reflect outside the
function.
1. Formal Arguments
Definition: Formal arguments are the variables listed in the function definition. They act as placeholders to
receive values when the function is called.
Example: In the function definition def sum(a, b):, a and b are formal arguments.
Role: They are used inside the function to perform operations or computations using the passed values.
Definition: Actual arguments are the values or variables passed to the function when it is called. These are
the real data or values that the function operates on.
Example: In the function call sum(10, 15), the values 10 and 15 are actual arguments.
Role: They are assigned to the formal arguments in the function definition when the function is executed.
The actual arguments used in a function call are of 4 types:
o Positional arguments
o Keyword arguments
o Default arguments
o Variable length arguments
x = 10
y = 15
sum(x, y) # x, y are actual arguments
Positional arguments
Positional arguments are values passed to a function in the correct order. When you define a function that expects a
certain number of arguments in a specific order, you need to provide them in the same order when you call the
function.
1. Function Definition:
def attach(s1, s2):
s3 = s1 + s2
print('Total string: ' + s3)
2. Function Call:
When calling the function, you need to pass two values (strings in this case) in the exact order that matches the
function definition.
attach('New', 'York')
So, inside the function, s3 will be 'New' + 'York', which results in 'NewYork'.
3. Output:
Total string: NewYork
What happens if you mix up the order or pass the wrong number of arguments?
attach('York', 'New')
If you pass more or fewer arguments than expected (for example, 3 strings instead of 2):
This will give an error because the function is defined to accept only 2 arguments, but you've passed 3.
Summary:
Positional arguments must be passed in the correct order and number, exactly as expected in the function definition.
Keyword arguments
Keyword arguments allow you to pass arguments to a function by explicitly naming the parameters, rather than
relying on their position. This makes the code more readable and flexible, as you can pass the arguments in any
order.
1. Function Definition:
def grocery(item, price):
print('Item = %s' % item)
print('Price = %.2f' % price)
You can call the function and specify which argument corresponds to which parameter by naming them:
grocery(item='Sugar', price=50.75)
Item = Sugar
Price = 50.75
With keyword arguments, the order doesn't matter. You can swap the positions of the arguments without causing
any issues:
grocery(price=88.00, item='Oil')
Even though price comes first and item comes second, the function still works correctly because the argument
names are used to match values to the correct parameters. The output will be:
Item = Oil
Price = 88.00
Summary:
Keyword arguments allow you to specify which value goes into which parameter, regardless of their order in
the function call.
This makes the function call more flexible and readable.
Default arguments
Default arguments allow you to set a default value for a function parameter, which is used
if the caller doesn't provide a value for that parameter. This can make your function calls
simpler by eliminating the need to always pass every argument.
1. Function Definition:
def grocery(item, price=40.00):
print('Item = %s' % item)
print('Price = %.2f' % price)
Item = Sugar
Price = 50.75
Item = Sugar
Price = 40.00
Summary:
Default arguments let you define a parameter with a default value. If the caller doesn't provide a value for
that parameter, the default value is used.
This is useful when you want to give the caller the option to skip providing certain values, and the function
will use sensible defaults in such cases.
Variable Length Arguments
In Python, you can write functions that can accept any number of arguments. This is useful when you don't know in
advance how many values will be passed to the function. Python provides two ways to handle this: Positional
Variable Length Arguments and Keyword Variable Length Arguments.
When you use *args in the function definition, you can pass any number of positional arguments to the function,
and Python will store them as a tuple.
Example:
def add(farg, *args):
""" to add given numbers """
print('Formal argument= ', farg)
sum = 0
for i in args: # args is a tuple containing all extra arguments
sum += i
print('Sum of all numbers= ', (farg + sum))
# Function calls
add(5, 10) # Passes 1 extra argument
add(5, 10, 20, 30) # Passes 3 extra arguments
Output:
Formal argument= 5
Sum of all numbers= 15
Formal argument= 5
Sum of all numbers= 65
In the first call, args will contain (10,), and the sum will be 10. The final result is 5 + 10 = 15.
In the second call, args will contain (10, 20, 30), and the sum will be 60. The final result is 5 + 60 =
65.
When you use **kwargs, you can pass any number of key-value pairs to the function, and Python will store them
as a dictionary.
Example:
def display(farg, **kwargs):
""" to display given values """
print('Formal argument= ', farg)
for x, y in kwargs.items(): # Iterating through the key-value pairs in kwargs
print('key = {}, value = {}'.format(x, y))
# Function calls
display(5, rno=10)
print()
display(5, rno=10, name='Prakash')
Output:
Formal argument= 5
key = rno, value = 10
Formal argument= 5
key = rno, value = 10
key = name, value = Prakash
Summary:
1. Positional Variable Length Arguments (*args): These are passed as a tuple. You can pass any number of
positional arguments.
o Example: *args collects all extra positional arguments.
2. Keyword Variable Length Arguments (**kwargs): These are passed as a dictionary. You can pass any
number of key-value pairs.
o Example: **kwargs collects all extra keyword arguments.
Both of these techniques allow you to write flexible functions that can handle varying numbers of inputs, making
your code more adaptable.
👉 Example:
def myfunction():
a = 1 # 'a' is created inside the function → Local variable
a += 1 # Increase 'a' by 1 → Now a = 2
print(a) # This will print 2
myfunction()
print(a) # ❌ This will cause an ERROR because 'a' is not available outside the
function
👉 Example:
a = 1 # 'a' is created outside → Global variable
def myfunction():
b = 2 # 'b' is a local variable inside the function
print('a =', a) # can access 'a' (global variable)
print('b =', b) # can access 'b' (local variable)
myfunction()
🧠 In Short:
Feature Local Variable Global Variable
Where declared Inside a function Outside any function
Where available Only inside that function Everywhere (after it's created)
Life Duration Only while the function runs Throughout the program
Local variable is like a student’s notebook ➔ only that student can use it inside their classroom
(function).
Global variable is like a notice board ➔ everyone in school (all functions) can see it and use it.
🌟 The global Keyword and How to Use Global
Variables Inside a Function
👉 Inside the function, Python will use the local a only, and ignore the global a.
Example:
a = 1 # global variable
def myfunction():
a = 2 # local variable (only inside function)
print('a =', a) # prints local 'a' (2)
myfunction()
print('a =', a) # prints global 'a' (1)
Output:
a = 2
a = 1
"Please, don't create a new local variable! I want to use the global one!"
Example:
a = 1 # global variable
def myfunction():
global a # tell Python: 'use the global a'
print('global a =', a) # prints 1
a = 2 # changes the global 'a' to 2
print('modified a =', a) # prints 2
myfunction()
print('global a =', a) # prints 2 (it was changed inside the function)
Output:
global a = 1
modified a = 2
global a = 2
✅ Using it, you can pick any global variable inside your function even if a local one with the same name
exists!
Example:
a = 1 # global variable
def myfunction():
a = 2 # local variable
x = globals()['a'] # get global 'a' and store it in 'x'
print('global var a =', x) # prints 1
print('local var a =', a) # prints 2
myfunction()
print('global var a =', a) # prints 1
a = 2 → local
globals()['a'] → global value of a, which is 1
Output:
global var a = 1
local var a = 2
global var a = 1
🧠 In Short:
Feature Meaning
Same name for local & global Local is preferred inside function
Want to use global inside function Write global a
Want both local and global Use globals()['a']
If you write something on your notebook, it stays with you (local variable).
If you want to see the school's notice board (global variable) while still using your notebook, you
use globals() to check the notice board without changing your notebook.
🎯 Final Tip:
Use global keyword when you want to modify the global variable.
Use globals()['varname'] when you want to read the global variable without losing the local
one.
👉 To pass many elements together, we can put them inside a list, and then send the list to the function.
The function can then work on this list — like finding total, average, printing the names, etc.
🧠 Program 24: Passing a Group of Numbers to a
Function
This line:
means:
x, y = calculate(lst)
calculate(lst) will return two things — the total (sum) and the average.
We store them into x and y.
🎯 Full Example:
# define function
def calculate(lst):
""" to find total and average """
n = len(lst)
sum=0
for i in lst:
sum+=i
avg = sum/n
return sum, avg
# take numbers
print('Enter numbers separated by space: ')
lst = [int(x) for x in input().split()]
# call function
x, y = calculate(lst)
# display result
print('Total: ', x)
print('Average: ', y)
✅ Sample Output:
display(lst)
🎯 Full Example:
# define function
def display(lst):
""" to display the strings """
for i in lst:
print(i)
# take strings
print('Enter strings separated by comma: ')
lst = [x for x in input().split(',')]
# call function
display(lst)
✅ Sample Output:
🧠 In Short:
Concept Numbers Program Strings Program
Input Example 10 20 30 40 Gaurav,Vinod,Visal
How to split Using space Using comma
Function Name calculate() display()
Function Work Finds total and average Prints each string
What is Passed List of numbers [10,20,30,40] List of strings ["Gaurav","Vinod","Visal"]
🌟 Recursive Functions
A recursive function is a function that calls itself again and again while solving a problem.
It keeps calling itself until a simple case (called base case) is reached, and then it stops.
Factorial of 3 = 3 × 2 × 1 = 6
Factorial of 5 = 5 × 4 × 3 × 2 × 1 = 120
factorial(3) = 3 × factorial(2)
factorial(2) = 2 × factorial(1)
factorial(1) = 1 × factorial(0)
Now, step-by-step:
factorial(0) = 1
factorial(1) = 1 × 1 = 1
factorial(2) = 2 × 1 = 2
factorial(3) = 3 × 2 = 6
Factorial of 1 is 1
Factorial of 2 is 2
Factorial of 3 is 6
Factorial of 4 is 24
Factorial of 5 is 120
Factorial of 6 is 720
Factorial of 7 is 5040
Factorial of 8 is 40320
Factorial of 9 is 362880
Factorial of 10 is 3628800
# take input
n = int(input('Enter number of disks: '))
towers(n, 'A', 'C', 'B')
🌟 In Short:
Concept Meaning
Recursive Function Function that calls itself
Base Case A simple condition where recursion stops
Factorial n × (n-1) × (n-2) × ... × 1
Concept Meaning
Towers of Hanoi Move disks from A to C following two rules using recursion
def square(x):
return x * x
But sometimes, we want to create a very small and quick function without giving it a name.
That’s called an anonymous function.
In Python, we make anonymous functions using a special word: lambda.
That's why anonymous functions are also called lambda functions.
Format:
Example:
lambda x: x*x
✅ Important Point:
Lambda functions are used only for small, quick tasks.
They can have only one line of work (one expression).
You need to assign it to a variable (like a name), otherwise you can't call it later.
Example:
🔵 Output:
Square of 5 = 25
f = lambda x, y: x + y
result = f(1.55, 10)
print('Sum =', result)
Output:
Sum = 11.55
If you type:
10, 25
Bigger number = 25
These are used to process lists, strings, numbers quickly and easily.
numbers = [1, 2, 3, 4]
squares = list(map(lambda x: x*x, numbers))
print(squares)
Output:
[1, 4, 9, 16]
Lambda functions are short, quick, unnamed functions you make using lambda keyword, mostly used for
small tasks and simple expressions.
Format:
filter(function, sequence)
✅ Only the elements for which the function returns True will be kept.
✅ Other elements will be ignored.
def is_even(x):
if x % 2 == 0:
return True
else:
return False
is_even(10) ➔ True
is_even(23) ➔ False
and so on...
print(lst1)
✅ Output:
10 → even → kept
23 → odd → ignored
45 → odd → ignored
46 → even → kept
70 → even → kept
99 → odd → ignored
print(lst1)
✅ Output:
🎯 In Short:
Concept Meaning
filter(function, list) Selects items where function returns True
Normal Function A named function like def is_even(x): ...
Lambda Function A small quick function like lambda x: x%2==0
Result A list of filtered items
Format:
map(function, sequence)
[1, 2, 3, 4, 5]
def squares(x):
return x * x
squares(2) ➔ 4
squares(3) ➔ 9
etc.
lst = [1, 2, 3, 4, 5]
print(lst1)
✅ Output:
1×1→1
2×2→4
3×3→9
4 × 4 → 16
5 × 5 → 25
lst = [1, 2, 3, 4, 5]
✅ Output:
Suppose:
lst1 = [1, 2, 3, 4, 5]
lst2 = [10, 20, 30, 40, 50]
1 × 10 = 10
2 × 20 = 40
3 × 30 = 90
etc.
We can do:
✅ Output:
🔵 Here:
✅ So:
1×10 = 10
2×20 = 40
and so on.
🎯 In Short:
Concept Meaning
Using map(), you can combine bread and filling one-by-one automatically!
reduce(function, sequence)
function: A function that takes two arguments, processes them, and returns a new value (this function is
repeatedly applied to the elements).
sequence: A list, tuple, or any iterable.
Let’s look at an example where we want to calculate the product of all the numbers in a list:
lst = [1, 2, 3, 4, 5]
The goal is to multiply all these numbers together:
First, we take the first two numbers: 1 and 2, and multiply them: 1 * 2 = 2
Then, we take the result (2) and multiply it with the next number 3: 2 * 3 = 6
Next, take 6 and multiply it with 4: 6 * 4 = 24
Finally, take 24 and multiply it with 5: 24 * 5 = 120
So, the final result is 120, which is the product of all the numbers in the list.
Instead of defining a function separately, we can use a lambda function inside reduce(). A lambda function is an
anonymous function that can be used for simple operations.
Let’s rewrite the same example using reduce() and a lambda function.
# List of numbers
lst = [1, 2, 3, 4, 5]
# Using reduce with lambda function to calculate the product of all numbers
result = reduce(lambda x, y: x * y, lst)
Explanation:
lambda x, y: x * y: This is a lambda function that takes two numbers, x and y, and multiplies them.
reduce(lambda x, y: x * y, lst): This applies the lambda function to the list lst. It will repeatedly
multiply the numbers in the list until only one result remains (the final product).
Output:
120
Let’s say you want to calculate the sum of numbers from 1 to 50. You can use reduce() like this:
# Using reduce with lambda function to calculate the sum of numbers from 1 to 50
sum_result = reduce(lambda a, b: a + b, range(1, 51))
Explanation:
lambda a, b: a + b: This is a lambda function that takes two numbers, a and b, and adds them
together.
range(1, 51): This creates a sequence of numbers from 1 to 50.
reduce(lambda a, b: a + b, range(1, 51)): This applies the lambda function to the sequence,
repeatedly adding the numbers to get the total sum.
Output:
1275
reduce() is useful when you want to combine a sequence of items (like numbers, strings, etc.) into a single
value.
It is particularly helpful when you want to perform operations like addition, multiplication, or concatenation
over a sequence, and reduce it to a single result.
Summary:
reduce() takes a function and a sequence and reduces the sequence to a single value.
It applies the function to the first two elements of the sequence, then uses the result with the next element,
and so on until the entire sequence is processed.
It is often used with a lambda function to make the code more compact and elegant.
Example tasks you can do with reduce():
o Calculate the product of a list of numbers.
o Calculate the sum of a range of numbers.
o Concatenate strings, and much more.
In Simple Words:
Imagine you have a list of things and you want to combine them into one result (like summing them up or
multiplying them). reduce() helps you do that by combining two things at a time until only one thing is left. 😊
Function Decorators
A decorator is a special function in Python that can be used to modify or add extra functionality to an existing
function without changing the original function's code.
You can think of it like adding extra features to an existing product. For example, if you have a basic car (your
function), a decorator would be like adding a sunroof, better seats, or air conditioning to it (extra functionality)
without changing the car's basic structure.
The first thing you do is define a function that will act as your decorator. This decorator function will take another
function as a parameter.
Example:
def decor(fun):
# fun is the function passed to the decorator
Here, fun is the function we want to modify using the decorator. It will be passed into the decor function.
Inside the decorator function, we define another function that actually modifies the behavior of the function passed
to the decorator. This inner function will do something to the result of the original function (e.g., modify or decorate
it).
Example:
def decor(fun):
def inner():
value = fun() # Call the function fun()
return value + 2 # Add 2 to the result of fun()
return inner # Return the inner function
inner() is the function that will modify the result returned by fun(). It calls fun(), gets its result, and
adds 2 to it.
Then, inner() is returned so it can be used in place of fun().
Now, you can apply this decorator to any function that you want to modify. You do this by calling the decorator and
passing the function to it.
For example:
def num():
return 10
num() returns 10, but after applying the decor() decorator, the inner() function modifies it by adding 2.
So, the result is 12.
How it Works:
Full Example:
# A decorator that adds 2 to the result of the function
def decor(fun):
def inner():
value = fun() # Call the original function
return value + 2 # Add 2 to the result
return inner # Return the modified function
Output:
12
Code Reusability: You can use the same decorator on multiple functions without modifying their original
code.
Separation of Concerns: You can keep the extra functionality (like adding 2, logging, or timing) separate
from the original logic of the function.
Instead of calling the decorator function manually, Python allows us to use the @ symbol to apply a decorator to a
function more neatly.
Example:
@decor
def num():
return 10
This program demonstrates decorators in Python, which are a way to modify or enhance the behavior of a function
without changing its original code.
Decorators are essentially functions that take another function as input and return a modified version of that
function.
Let's start with Program 37, which shows a simple decorator that adds 2 to the result of a function.
Step-by-Step Breakdown:
1. Creating the Decorator (decor): The decor function is a decorator. A decorator is a function that takes
another function (fun) as an argument and returns a new function (inner) that modifies the behavior of
fun.
def num():
return 10
3. Applying the Decorator: To apply the decorator, we call decor(num) and assign the result to result_fun.
This replaces num with the decorated version, which adds 2 to the result.
When result_fun() is called, it executes the inner() function inside the decorator, which calls num()
(which returns 10), adds 2, and returns 12.
Output:
12
Instead of manually calling the decorator function (decor(num)), we can use the @ symbol to apply the decorator
more neatly.
Step-by-Step Breakdown:
1. Using @ to Apply Decorators: In Program 38, we apply the decor decorator to the num function using the
@decor syntax.
def num():
return 10
num = decor(num) # Apply the decorator manually
2. Calling the Decorated Function: After applying the decorator with @decor, we can call num() directly. It will
automatically use the decorated version of the function.
print(num()) # Output will be 12
Output:
12
In Program 39, we introduce another decorator, decor1, which doubles the result of a function. Now we want to
apply both decor and decor1 to the num() function.
Step-by-Step Breakdown:
1. Creating the Second Decorator (decor1): The decor1 function is a second decorator that doubles the value
returned by the function.
def decor1(fun):
def inner():
value = fun() # Call the original function (e.g., num)
return value * 2 # Double the value
return inner
2. Applying Two Decorators: We apply decor1 first, then decor. This means the result will be modified twice:
o decor1 will double the result.
o decor will add 2 to the doubled value.
Output:
22
In Program 40, we use the @ symbol to apply both decorators (decor1 and decor) to the num() function. The
syntax is:
@decor
@decor1
def num():
return 10
def num():
return 10
When we call num(), it first gets modified by decor1 (doubling the value), and then by decor (adding 2).
22
Multiple Decorators: When you apply multiple decorators, they are applied from bottom to top:
o @decor is applied first.
o @decor1 is applied second.
def func():
pass
func = decor(decor1(func)) # Apply decorators in this order
Separation of Concerns: You can separate the core logic of a function from its extra features (like modifying
its output).
Code Reusability: You can reuse decorators for many functions without modifying them.
Cleaner Code: Using the @ symbol makes the code look cleaner and easier to read.
Generators
Generators are special functions in Python that allow you to generate a sequence of values one at a time. The key
thing that makes generators unique is that they don’t return all values at once. Instead, they yield values one by
one. This is more memory efficient compared to returning all values in a list at once.
In a regular function, you use the return statement to return a value, but in a generator function, you use the
yield statement. When a function uses yield, it produces a generator object that will produce values on demand
when asked for them.
Generators are defined like normal functions, but instead of return, they use yield. Let's take an example.
Output:
5 6 7 8 9 10
You can convert a generator into a list if you want to store all its values at once. This is done using the list()
function.
You can also manually retrieve values from a generator using the next() function. Each time you call next(), it
gives you the next value yielded by the generator.
Let's see another simple example where the generator returns characters.
def mygen():
yield 'A'
yield 'B'
yield 'C'
The first call to next(g) returns 'A', the second returns 'B', and the third returns 'C'.
After 'C' is yielded, calling next(g) will raise a StopIteration error because there are no more values in
the generator.
yield: The yield statement temporarily suspends the function's execution, saving its state. When you call
next() on the generator again, the function picks up from where it left off, right after the last yield.
This allows you to generate values on the fly, without storing them all in memory at once.
1. Memory Efficiency: Generators don't store all the values in memory at once. Instead, they generate values
as needed. This is great when dealing with large datasets.
2. Lazy Evaluation: A generator only computes the next value when it is needed (on demand). This is called lazy
evaluation.
3. Cleaner Code: Generators can be a cleaner alternative to writing functions that return large lists or arrays.
Generators are functions that use the yield statement to produce values one at a time, instead of
returning a whole list at once.
next() is used to retrieve values from a generator.
StopIteration is raised when the generator has no more values to yield.
You can convert a generator to a list using the list() function if you need to store all its values.
Advantages include memory efficiency and lazy evaluation, which can be particularly useful for handling
large data.
Structured Programming
Structured programming is a method used to solve complex problems by breaking them down into smaller,
manageable parts. These smaller parts are known as subtasks, and each subtask is solved using a function. This
approach makes the program easier to write, maintain, and understand.
The idea behind structured programming is to focus on dividing the main task into smaller functions, which can be
individually tested and reused. Once all the smaller functions are ready, you can combine them to solve the larger
problem.
Let’s say we need to calculate the net salary of an employee. The net salary is calculated by:
1. Gross Salary = Basic Salary + Dearness Allowance (DA) + House Rent Allowance (HRA)
2. Net Salary = Gross Salary - Provident Fund (PF) - Income Tax
Now, each of these components (DA, HRA, PF, and Income Tax) can be calculated using different formulas. Each of
these can be handled by a separate function. Once we have the values from these functions, we can easily calculate
the gross and net salary.
Steps of Structured Programming in This Example:
Each of the functions takes the basic salary as input and returns a value that contributes to the final salary.
Once we have the functions for DA, HRA, PF, and tax, we can use them to calculate the gross salary and net salary of
the employee.
Example Output
Clarity: By dividing a big problem into smaller tasks (functions), the code is easier to read and understand.
Reusability: Functions can be reused wherever necessary without rewriting the same code.
Maintainability: If you need to make changes, you only need to modify the relevant function without
affecting the whole program.
Debugging: It is easier to debug and test small, independent functions rather than a large, monolithic
program.
Summary
Structured Programming helps in breaking a large task into smaller, manageable pieces called functions.
Each function solves a subtask, and these functions are then used to solve the overall problem.
In this example, we divided the salary calculation problem into four tasks (DA, HRA, PF, and Income Tax),
each handled by its own function.
The program uses these functions to calculate the gross and net salary of an employee.
In Python, a module is a file that contains Python code, such as functions, variables, and classes, that can be used in
other Python programs. Modules are created to organize code into smaller, reusable parts. Instead of writing the
same code repeatedly in different programs, you can import a module and reuse the code it contains.
1. Code Reusability: You can write a function or class once and reuse it in multiple programs.
2. Organization: Modules help in organizing related functions and classes into one file, making the code more
manageable.
3. Collaboration: In a team project, different programmers can work on different modules. Once a module is
created, others can use it.
In Python, you can create your own module by simply writing the code you want to reuse in a file and giving it a
name (ending with .py). This file becomes your module.
For example, let’s create a module called employee.py that contains several functions to calculate different parts
of an employee's salary:
Here’s the code inside employee.py that calculates various salary-related components:
# Save this code as employee.py
Each of these functions takes either the basic salary or gross salary as input and returns the calculated value.
Once the module is created, you can use it in other Python programs by importing it.
You can import the employee module into your program using one of two ways:
import employee
employee.da(basic)
employee.hra(basic)
employee.pf(basic)
employee.itax(gross)
2. From-import (importing everything from the module so you don’t need to use the module name):
Now, let’s write a program that calculates the gross salary and net salary using the functions from the employee
module.
# Program using the employee module to calculate gross and net salaries
1. User Input: The program asks the user to enter the basic salary.
2. Gross Salary Calculation: It calculates the gross salary by adding:
o Basic salary
o DA (calculated using da(basic))
o HRA (calculated using hra(basic))
3. Net Salary Calculation: It calculates the net salary by subtracting:
o PF (calculated using pf(basic))
o Income Tax (calculated using itax(gross))
Example Output
If the user enters a basic salary of 15,000, the program will display:
1. Reusability: Once you create a module, you can reuse it in multiple programs without rewriting the same
code.
2. Organization: By grouping related functions into a module, the code becomes more organized and easier to
manage.
3. Collaboration: In a team, different members can work on different modules, and each person can easily
import and use the modules developed by others.
Summary
In Python, the special variable __name__ is automatically set by the Python interpreter when a program or module
is run. It helps us understand whether a Python script is being executed directly as the main program or if it's being
imported as a module into another program.
1. When you run a Python program directly (e.g., by typing python program.py in the terminal), Python
sets the value of __name__ to '__main__'.
2. When you import the Python file as a module into another program (e.g., import program), Python sets
the value of __name__ to the name of the module (i.e., the name of the Python file without the .py
extension).
By checking the value of __name__, we can determine how the Python code is being executed.
Let’s look at the first example where we write a Python program one.py that uses __name__:
def display():
print('Hello Python!')
if __name__ == '__main__':
display() # This will call the display function if run directly
print('This code is run as a program')
else:
print('This code is run as a module')
1. When you run one.py directly (using python one.py), the Python interpreter will set __name__ to
'__main__' because the file is being executed as the main program.
2. Inside the script, there is a check:
if __name__ == '__main__':
Since __name__ is '__main__', the code inside the if block will be executed, which includes:
Output:
Hello Python!
This code is run as a program
Now let’s look at another Python program two.py that imports one.py as a module:
1. When you run two.py (using python two.py), Python first imports the one module.
2. During the import, Python sets the value of __name__ in the one.py script to 'one' because one.py is
now being treated as a module named one, not as the main program.
3. The else block in one.py is executed because __name__ is no longer '__main__'. This means the code in
the else block will print:
o This code is run as a module.
4. After the import, two.py calls one.display(), which prints:
o Hello Python! (because display() is called from one.py).
Output:
This code is run as a module
Hello Python!
Key Points:
Direct Execution: When you run a Python file directly, __name__ is set to '__main__'.
Module Import: When you import a Python file as a module, __name__ is set to the module's name (the
filename without .py).
Conditional Code Execution: By using the condition if __name__ == '__main__', you can ensure that
certain parts of the code only run when the file is executed directly, not when it's imported as a module.
1. Code Reusability: By using __name__, you can write code that behaves differently depending on whether it
is being used directly or as part of another program.
2. Testing and Debugging: If you want to test some functions in a file without running them when importing
the file as a module, you can wrap that testing code inside if __name__ == '__main__'. This ensures
the test code runs only when the file is executed directly.
3. Modular Design: It helps you design your Python code in a modular way. The functions and classes can be
imported and used by other programs, while certain parts (e.g., testing code) can be kept separate and only
run when needed.
Summary:
The __name__ variable in Python helps you determine how a program is executed: directly or as a module.
When you run a script directly, __name__ is set to '__main__'. When you import it as a module,
__name__ is set to the name of the module.
Using if __name__ == '__main__', you can write code that only runs when the script is executed
directly, making your code modular and reusable.
3. Function Execution:
o A function is executed only when it is called using its name.
function_name(arguments)
o Actual Arguments: These are the values passed to the function when calling it.
7. Positional Arguments:
o These are arguments passed to the function in the correct position and number. The first argument
goes to the first parameter, the second to the second, and so on.
8. Keyword Arguments:
o These are arguments where you specify the parameter name along with the value.
9. Default Arguments:
o If an argument is not provided in the function call, it takes a default value as specified in the
function definition.
def sum_numbers(*args):
return sum(args)
print(sum_numbers(1, 2, 3)) # 6
def print_info(**kwargs):
for key, value in kwargs.items():
print(f"{key}: {value}")
print_info(name="Alice", age=25)
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n-1)
square = lambda x: x * x
print(square(5)) # Output: 25
numbers = [1, 2, 3, 4]
squared_numbers = map(lambda x: x ** 2, numbers)
print(list(squared_numbers)) # [1, 4, 9, 16]
import math
print(math.sqrt(16)) # Output: 4.0
16. Decorators:
o A decorator is a function that takes another function as an argument and returns a new function. It
is often used to modify the behavior of functions.
def decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper
@decorator
def greet():
print("Hello!")
greet() # Output: Before function call \n Hello! \n After function call
17. Generators:
o A generator is a special type of function that returns a sequence of values using the yield
statement. Unlike regular functions that return a single value, a generator returns an iterator that
produces values one at a time.
def count_up_to(max):
count = 1
while count <= max:
yield count
count += 1
counter = count_up_to(5)
for number in counter:
print(number) # Output: 1 2 3 4 5
Exceptions
Errors in a Python Program
In general, we can classify errors in a program into one of these three types:
Compile-time errors
Runtime errors
Logical errors
In this example, a colon is missing after the if statement, which causes a SyntaxError.
x = 1
if x == 1 # Missing colon here
print('Where is the colon?')
Output:
Explanation: The Python interpreter expects a colon after the condition x == 1 because it marks the start
of the code block that will execute if the condition is true. Since the colon is missing, Python cannot
understand the code, so it raises a SyntaxError.
Example 2: Indentation Error
In this example, the indentation is incorrect because the second print statement inside the if block is not aligned
properly.
x = 10
if x % 2 == 0:
print(x, 'is divisible by 2')
print(x, 'is even number') # Incorrect indentation
Output:
Explanation:
IndentationError: Python expects all statements within the same block to be indented equally. Here, the
second print statement is indented incorrectly, causing an IndentationError. All the statements inside the
if block should have the same number of spaces or tabs.
When Python encounters a syntax error or indentation error, it provides the following helpful information:
Line number: The error message will point to the line where the error occurred, making it easier to find and
fix.
Error description: Python will describe the type of error (like SyntaxError or IndentationError) and
sometimes provide hints (like "unexpected indent").
1. Check for missing colons: After if, for, while, def, and similar statements, ensure you add a colon (:) at
the end.
o Example:
if x == 1:
print('Where is the colon?')
2. Fix indentation: Make sure that all statements within the same block have the same indentation level
(usually 4 spaces). Avoid mixing spaces and tabs.
o Example:
if x % 2 == 0:
print(x, 'is divisible by 2')
print(x, 'is even number') # Correct indentation
Summary:
Compile-time errors happen due to syntax mistakes (like missing colons or incorrect indentation).
Python will give a helpful error message with the line number and a description of the error, so you can
easily find and fix the problem.
Always check for missing colons and ensure that your indentation is consistent to avoid these errors.
Runtime Errors in Python
Runtime errors occur when a program runs, but something goes wrong while the program is executing. Unlike
compile-time errors (which are detected before the program runs), runtime errors are only detected when the
program is actually running. These errors happen when the Python Virtual Machine (PVM) tries to execute the
program but encounters a problem.
Detected during execution: Python can't catch these errors when compiling the code, but it catches them
when the program is actually running.
Examples: Common runtime errors include trying to divide by zero, accessing a list element that doesn't
exist, or using incompatible data types in an operation.
In this example, the function concat tries to add a string and an integer together. This results in a TypeError.
Output:
Explanation:
The function concat is designed to add two values, but the way it adds depends on the types of values
passed:
o If both values are strings, they are concatenated (joined).
o If both values are numbers, they are added (summed).
But here, the function is called with a string ('Hai') and an integer (25). Python can't automatically decide
how to handle this because it doesn't know how to combine a string with a number. This results in a
TypeError.
Python doesn’t check the types during compilation. It only checks them when the program is running.
When Python tries to add a string and an integer, it realizes they are incompatible types and raises a
TypeError.
The error message helps by saying: "Can't convert 'int' object to str implicitly." This tells us that Python
can't automatically convert the integer to a string.
In this example, we try to access an index that doesn't exist in a list, which raises an IndexError.
animal = ['Dog', 'Cat', 'Horse', 'Donkey']
print(animal[4])
Output:
Explanation:
The Python interpreter detects that the index is invalid only when trying to access it at runtime.
The error message tells us: "IndexError: list index out of range". This means we tried to access an index that
doesn't exist in the list.
When a runtime error occurs, Python will display the line number and type of error, which can help you fix the
problem.
Common Solutions:
1. TypeError Solution: Ensure that the data types you are working with are compatible. For example, if you
want to concatenate a string and a number, you should convert the number to a string first:
concat('Hai', 25)
2. IndexError Solution: Check if the index is valid before trying to access the list element. You can use len() to
check the length of the list before accessing an index:
In some cases, runtime errors are due to conditions that can't be easily prevented (like running out of
memory or trying to access a resource that isn't available). In such cases, exception handling is used to
manage these errors gracefully.
Conclusion:
Runtime errors happen while the program is running, and they can be due to invalid operations (like adding
incompatible types or accessing non-existent list indices).
Python displays helpful error messages (with line numbers) to help you fix these errors.
You can prevent most runtime errors by ensuring that your data types and index values are valid and by
using exception handling when necessary.
These errors are not detected by the Python compiler or interpreter, meaning that the program will run without
any obvious signs of trouble, but the result will be wrong. It is the programmer's responsibility to find and fix these
logical errors.
Let’s look at a program where the programmer wants to increment an employee's salary by 15%. But due to a
logical error, the calculation is incorrect.
def increment(sal):
sal = sal * 15 / 100 # This is the wrong formula.
return sal
Output:
Explanation:
The goal is to increase the salary by 15%, but the formula used is sal = sal * 15 / 100. This only
calculates the increment (i.e., 15% of the salary) but does not add it to the original salary.
If you manually calculate 15% of 5000, you get 750. But the correct formula to increase the salary by 15%
should be:
Corrected Output:
So, this is a logical error because the logic behind the calculation was wrong, even though the program ran without
any errors.
Example 2: Runtime Errors and Exception Handling
Next, let's look at a program where we perform a division and write the result to a file. The program works fine
when there is no problem, but if the user enters zero for the denominator, it causes a runtime error called
ZeroDivisionError.
# an exception example
# Perform division
c = a / b
print('File closed')
Output:
Explanation:
In this case, when you divide 10 / 0, it raises a ZeroDivisionError because dividing by zero is
mathematically impossible.
When the error occurs, the program stops immediately, and the subsequent lines of code (like closing the
file) are not executed.
This is problematic because the file was never closed properly, which could lead to data loss or a corrupted
file.
1. Data Loss: In the above example, if the file isn't closed properly, any data that was written to the file might
be lost.
2. Corrupted Software: If errors are not caught and handled, the program may stop working completely.
3. User Experience: If the program abruptly crashes, users may lose trust in the software.
Here is how you can handle the ZeroDivisionError in the program using a try-except block:
try:
# Perform division
c = a / b
# Open the file and write the result
f = open("myfile", "w")
f.write("writing %d into myfile" % c)
except ZeroDivisionError:
print("Error: Division by zero is not allowed!")
finally:
# Ensure the file is closed whether there's an error or not
f.close()
print('File closed')
try: This block contains the code that might cause an error. In this case, it’s the division operation.
except: If an error occurs in the try block, Python will jump to the except block. In this case, it catches the
ZeroDivisionError and prints a message.
finally: This block will always execute, regardless of whether an error occurred or not. Here, it ensures
that the file is closed properly.
Conclusion
Logical errors are problems with the design or logic of your program. They do not cause the program to
crash, but they produce the wrong output. These are the programmer’s responsibility to find and fix.
Runtime errors happen during the program’s execution, such as dividing by zero or accessing invalid data.
These errors need to be handled using exception handling techniques like try-except blocks.
Exception handling ensures that the program doesn’t crash unexpectedly, which improves reliability and
helps preserve data integrity.
Exception
What is an Exception?
An exception is an error that happens while the program is running (during runtime). The difference between a
regular error and an exception is that an exception is an error that the programmer can handle or fix, while a regular
error is something that might be too difficult or impossible to fix within the program itself.
For example, when you're trying to divide a number by zero, that's an exception because the programmer can
handle this case and prevent the program from crashing. However, if you're trying to open a file that doesn't exist,
that's usually an error because the program can't do much to fix it automatically.
Python allows programmers to handle exceptions using exception handling mechanisms like try, except, and
finally blocks. When a potential exception occurs, the program can catch it using except and decide what to do
(e.g., show an error message, use default values, etc.). The program doesn't need to crash because of it.
Built-In Exceptions in Python
Python comes with a set of built-in exceptions, which are pre-defined errors that the Python language itself can
recognize. These exceptions help the programmer handle common errors without having to create new ones.
All exceptions in Python are organized in a hierarchy of classes. The base class for all exceptions is the
BaseException class, and from this, all other exceptions are derived (i.e., "built on top of").
1. BaseException: This is the topmost class in Python's exception hierarchy. All exceptions in Python are
subclasses of BaseException. However, you rarely deal directly with this class.
2. Exception: This is a subclass of BaseException and is the main class for most built-in exceptions. In fact, all
errors in Python are actually subclasses of this Exception class.
3. StandardError: This used to be a subclass of Exception and was the parent for most of the errors like
ValueError, IndexError, etc. However, in modern versions of Python, it’s considered somewhat
obsolete, as exceptions are now directly derived from Exception.
4. Warning: A warning is not really an error. It’s more like a caution or advisory message. A warning doesn’t
stop the program from running. It’s just a heads-up to the programmer that something may not be ideal, but
it’s not serious enough to crash the program.
User-Defined Exceptions
In addition to the built-in exceptions, Python also allows programmers to create their own custom exceptions. These
are called user-defined exceptions. If you have a specific error that you want to catch in your program that isn’t
already covered by Python’s built-in exceptions, you can create your own exception class.
To do this, you should inherit from the Exception class, not from BaseException. Here's an example of a user-
defined exception:
Output:
Explanation:
Here, we created a custom exception class called InvalidAgeError, which inherits from the Exception
class.
When we try to check the age and if it’s below 18, the InvalidAgeError exception is raised with a custom
message.
The try and except block is used to catch this exception and handle it appropriately (in this case, by
printing an error message).
Handling exceptions is important because it helps you manage errors gracefully without crashing the program.
Instead of the program stopping abruptly, the programmer can define what should happen when an exception
occurs, whether it’s displaying an error message, using a default value, or even trying the operation again.
For example, if you're dealing with file operations, and the file doesn't exist, you can handle this exception by telling
the user the file isn’t found instead of letting the program crash unexpectedly.
Warnings vs Errors
Warnings: These are not critical and do not stop the program. They’re just there to inform you that
something may not be ideal. For example, if you use a deprecated feature in a library, Python might give you
a warning, but your program will continue running.
Errors: These are critical problems that stop the program from running correctly. You must handle these
errors to prevent your program from crashing. For example, trying to divide by zero is an error that will stop
the program if not handled.
Summary
Exceptions are runtime errors that can be handled by the programmer to prevent the program from
crashing.
Built-in exceptions are provided by Python and include things like ZeroDivisionError, ValueError, etc.
Warnings are less severe and don't stop the program from running, but errors do.
User-defined exceptions allow the programmer to create their own exceptions when the built-in ones don't
cover their needs.
Exception Handling
What is Exception Handling?
Exception handling is a way to handle errors in a program so that the program doesn't crash or stop unexpectedly
when something goes wrong. Instead, when an error (or "exception") happens, the program can continue running
smoothly by managing the error in a controlled way.
Imagine you’re writing a program that performs some calculations. Sometimes, errors like dividing by zero, accessing
a file that doesn’t exist, or entering invalid data may occur. Without exception handling, the program would crash.
But by handling exceptions, you can catch the error, show a helpful message, and continue running the program.
Robustness: A program becomes "robust" (strong and reliable) when it can handle errors gracefully instead
of stopping abruptly. This is crucial in real-world applications because unexpected things often happen (like a
user entering wrong data, or the internet connection being lost).
Error Message to User: When an exception occurs, you can show the user a helpful message explaining the
error and how to avoid it in the future.
Preventing Data Loss: When an exception occurs, the finally block can be used to clean up, close files, and
release resources properly. This ensures that important data is not corrupted or lost.
1. try block: The code that might cause an exception goes here. It's like saying, "I'm going to try this, but if
something goes wrong, handle it."
2. except block: If an exception occurs, Python jumps to this block, and the programmer can specify what
should happen when that exception happens. This is the "handler" for the exception.
3. finally block: No matter what happens (whether there is an exception or not), the finally block will always
run. It’s mainly used for cleanup actions, like closing files or stopping processes.
The first thing the programmer does is identify the parts of the code that might cause an error. These sections are
placed inside the try block.
try:
# Statements that might raise an exception
For example, division by zero or trying to open a non-existent file might cause errors, so these go inside the try
block.
If an error happens in the try block, Python jumps to the except block. Here, the programmer can handle the error
by showing a message, fixing the problem, or taking action.
except exceptionname:
# Handle the error here
The programmer can specify what type of exception they want to handle (like ZeroDivisionError for division by
zero), and they can provide a message for the user explaining what went wrong and how to fix it.
The finally block contains code that should always run, no matter what. This is useful for cleaning up resources
like files, network connections, or processes.
finally:
# Always run this code (e.g., closing files)
Even if there was no exception, the finally block will run. If an exception occurred, it will still run, ensuring that
things like file handles are properly closed.
try:
# Try to open a file and perform division
f = open("myfile", "w")
a, b = [int(x) for x in input("Enter two numbers: ").split()]
c = a / b # This might cause a ZeroDivisionError
f.write("writing %d into myfile" % c)
except ZeroDivisionError:
print('Division by zero happened') # Handle division by zero error
print('Please do not enter 0 in input')
finally:
f.close() # Always close the file
print('File closed')
Output:
File closed
In both cases, the finally block ensures that the file is closed, regardless of whether there was an error or not.
try:
# Code that might cause exceptions
except Exception1:
# Handle Exception1
except Exception2:
# Handle Exception2
else:
# Code to run if no exception occurs
finally:
# Code that runs no matter what (cleanup)
Key Points:
Multiple except blocks: You can handle different exceptions separately with multiple except blocks. For
example, one for handling division errors and another for handling file-related errors.
else block: If no exception is raised, the code inside the else block runs. It's not mandatory but is useful
when you want to do something only if no error occurred.
finally block: This is always executed, whether an exception occurred or not. It's used for cleanup actions,
such as closing files or releasing resources.
Output Examples:
Result: 5.0
Execution complete.
Summary:
If an error happens, Python will stop the program unless we handle (manage) the error properly.
To handle exceptions, we use try, except, and sometimes else, finally blocks.
🎯 Types of Exceptions
There are two types of exceptions:
🏗️ Built-in Exceptions
Python already has many common exceptions ready for us.
Here’s a simple table with important ones:
Exception The base class for all exceptions General parent for all errors
✅ Program:
✅ Example Output:
Correct Input:
Wrong Input:
🎯 Simple Explanation:
✅ Program:
✅ Example Output:
File Exists:
Enter filename: ex.py
ex.py has 11 lines
🎯 Simple Explanation:
✅ Program:
try:
t, a = avg([1, 2, 3, 4, 5, 'a']) # Try also avg([]) for empty list
print('Total= {}, Average= {}'.format(t, a))
except TypeError:
print('Type Error, please provide numbers.')
except ZeroDivisionError:
print('ZeroDivisionError, please do not give empty list.')
✅ Example Output:
🎯 Simple Explanation:
Handling Errors Prevent program from crashing and give a good message
📝 Summary
✅ Python has many built-in exceptions.
✅ We can catch and handle exceptions using try and except.
✅ We can handle different types of errors differently.
✅ Programs become stronger and more professional when we handle exceptions.
✅ Format:
except ExceptionClass:
✅ Example:
try:
a = 5 / 0
except ZeroDivisionError:
print("You cannot divide by zero!")
✅ Format:
✅ Example:
try:
a = 5 / 0
except ZeroDivisionError as e:
print("Error occurred:", e)
🔵 Meaning:
✅ Format:
✅ Example:
try:
a = int("abc") # ValueError
b = 5 / 0 # ZeroDivisionError
except (ValueError, ZeroDivisionError):
print("Either ValueError or ZeroDivisionError occurred.")
🔵 Meaning: If either error happens, the same block will handle it.
✅ Format:
except:
✅ Example:
try:
a = 5 / 0
except:
print("Some error occurred.")
🔵 Meaning:
except TypeError:
print("Type Error occurred.")
except ZeroDivisionError:
print("Zero Division Error occurred.")
✨ Important Point
If you don't mention which error, it will catch any error.
But best practice: Always mention the exception names if possible, so you know what went wrong!
✅ Important Rule:
You enter: 5
1/5 = 0.2
Output will be:
You enter: 0
Use try + finally To clean up work (like closing files) even if error happens
Only use empty except: if really needed Otherwise it's hard to debug errors
# handling AssertionError
try:
x = int(input('Enter a number between 5 and 10: '))
assert x >= 5 and x <= 10
print('The number entered: ', x)
except AssertionError:
print('The condition is not fulfilled')
✅ Step-by-step explanation:
1. It asks the user to enter a number between 5 and 10.
2. assert x >= 5 and x <= 10 checks if the number is really between 5 and 10.
3. If you entered correctly, it prints the number.
4. If you entered wrong (like 12 or 3), it throws an error and the except block catches it, printing:
✅ Example Outputs:
If you enter 7 ➔ Output:
✅ Step-by-step explanation:
1. User enters a number.
2. assert checks if the number is between 5 and 10.
3. If not, it raises an error with your custom message: "Your input is not correct".
4. except block catches the error and prints the message.
✅ Example Outputs:
If you enter 9 ➔ Output:
✅ Python code:
Use assert when you are very sure that a condition must be true. Example: age limits, input validations
If assert fails and no one catches the error, the program will So you can use try-except to catch it if
crash. needed.
Later, we can open the file, read all the errors, find out where things went wrong, and fix them easily.
It helps in debugging (finding and solving mistakes in the program).
✅ By default, Python only shows messages from WARNING level and above (WARNING, ERROR, CRITICAL).
import logging
logging.basicConfig(filename='mylog.txt', level=logging.ERROR)
✅ Only the error and critical messages are stored because we set level to ERROR. ✅ warning, info, debug
will NOT be saved in the file.
import logging
logging.basicConfig(filename='mylog.txt', level=logging.ERROR)
What happens:
Other messages are ignored because they are below ERROR level.
🧠 Making it Easier
Instead of writing logging.error() every time, we can import everything:
basicConfig(filename='mylog.txt', level=ERROR)
error("There is an error in the program.")
critical("There is a problem in the design.")
logging.exception(e)
logging.basicConfig(filename='log.txt', level=logging.ERROR)
try:
a = int(input('Enter a number: '))
b = int(input('Enter another number: '))
c = a / b
except Exception as e:
logging.exception(e)
else:
print('The result of division:', c)
🚀 How It Works
Suppose you run this program:
ERROR:root:division by zero
Traceback (most recent call last):
File "ex.py", line 9, in <module>
c = a/b
ZeroDivisionError: division by zero
✅ Now the programmer can open log.txt and easily see what errors happened.
Points to Remember
✅ What is an Exception?
An exception is a problem that happens while the program is running, but we can handle it using
Python code.
If we cannot handle the problem, it becomes a normal error that can crash the program.
If a programmer wants to create their own special exception, they should create it by inheriting
(extending) the Exception class.
If we handle exceptions properly, the program becomes robust (strong and safe).
It will not crash, and it will work smoothly even if something unexpected happens.
✅ Special Behaviors:
If an exception occurs, Python immediately jumps from the try block into the except block.
If there is no exception, Python skips except and goes to the else block.
You can use many except blocks for different types of exceptions.
Or you can use one except block with multiple exception types inside a tuple (round brackets).
✅ What is Assert?
Built-in exceptions are already available in Python (like ZeroDivisionError, ValueError, etc.).
User-defined exceptions are created by the programmer for special cases.
✅ What is Logging?
Instead of just showing errors on screen, we can save them in a file called a log file.
Logging helps to keep a record of errors, making it easy to debug and fix the problems later.
FILES IN PYTHON
📂 Files
📂 What is a File?
1. Permanent Storage
o When you store something in a file, it stays there even if you turn off the computer.
o Files are stored on hard disks, pen drives, CDs, or clouds.
o Example: You write a story in a text file today, you can still read it after 5 years!
2. Can Update Data
o Files are flexible.
o You can add new data, remove unwanted data, or change old data.
o Example: Add a new student to the student list, or update a salary when someone gets a promotion.
3. Data Sharing Across Programs
o The same file can be used by many programs.
o Example:
One program uses employee data to calculate salaries.
Another program uses the same file to calculate income tax for employees.
o No need to type data again and again — it saves time and effort.
4. Handling Big Data
o Files can store a huge amount of data easily.
o Example:
Voter lists of an entire country.
Census data (how many people live in a city).
o Even millions of records can be kept safely in files.
1. Text Files
Name: Ganesh
Salary: 8900.75
In a text file:
o "Ganesh" will be stored as 6 characters: G, a, n, e, s, h.
o "8900.75" (salary) will be stored as 7 characters: 8, 9, 0, 0, ., 7, 5.
🔵 Key point:
Text files are good when you want to store readable information like names, addresses, or notes.
2. Binary Files
Binary files store data as bytes (small units made up of 8 bits: 1s and 0s).
Computers love working with bytes because that's their language (1s and 0s)!
In a binary file:
o A character or a number is stored as binary numbers (not readable by humans).
o Example: On a 64-bit computer, one number (like 100) may take up 8 bytes.
Binary files are used to store:
o Images (like .jpg, .png files)
o Audio files (like .mp3)
o Video files (like .mp4)
o Big databases
🔵 Key point:
Binary files are good when you want to store things that computers use directly, like pictures, songs, videos —
because they are made up of lots of tiny dots (pixels) or sounds — not letters!
open()
"file name" → The name of the file you want to open. Example: "data.txt".
"open mode" → What you want to do with the file:
o Read it?
o Write in it?
o Add new content at the end?
"buffering" → (Optional) How much memory you want to use temporarily when working with the file. (You
usually don’t need to worry about it.)
🎯 Example:
f = open("myfile.txt", "w")
w Write in the file. If the file already has some data, it will be deleted first. Save new employee data
r Read from the file. You cannot write. The file must already exist. Read a list of students
Mode What it does Example
a Add (append) new data at the end of the file without deleting existing data. Add new product to a list
r+ Read and write. Old data is kept, and you can change it. Update a marksheet
Add (append) and read the file. If the file doesn’t exist, it will create a new
a+ Add new voters
one.
x Create a new file. If the file already exists, it will give an error. Create a new log file
🧠 Small Notes:
Text Files → Normal files like .txt.
Binary Files → Special files like images (.jpg, .png), music (.mp3).
If you are working with binary files, just add 'b' after the mode:
🗂️ What is Buffering?
Buffer means a small temporary memory that helps when you work with files.
It helps read/write faster by taking data in chunks instead of little bits.
You can tell Python how much buffer memory you want (optional):
o 0 → No buffering (direct read/write)
o 1 → Line buffering (one line at a time for text files)
o Positive number like 500 → 500 bytes buffering
If you don't mention anything, Python will automatically use 4096 or 8192 bytes buffering.
Tip: As a beginner, you can just skip setting buffering — Python will handle it well.
👉 If you don't close a file, your program might behave badly or crash later.
f.close()
Here, f is the file object — the name you used when you opened the file.
Example:
✅ Important point:
If the file myfile.txt already had some old text, it will be erased. Only your new text will be saved.
Example:
f = open('myfile.txt', 'a')
This way, the new text will be added at the end of the old text.
Example:
str = f.read(4)
This will read only the first 4 characters from the file.
Example output:
This
If your file had "This is my first line.", only "This" will be read.
Suppose you want to save multiple lines (like sentences or strings) into a file.
You can't just use write() one time.
Instead, you need to take input again and again inside a loop.
Example Idea:
Important:
We add "\n" (new line) after every string so that each sentence comes on a new line in the file.
Example Run:
f.close()
1. Using f.readlines()
Example:
2. Using f.read().splitlines()
Example:
f = open('myfile.txt', 'a+')
# read everything
print('The file contents are:')
str = f.read()
print(str)
f.close()
f.seek(0, 0) moves the cursor back to the start of file after writing.
Then we read from the beginning and display the updated file.
✨ Example:
🎯 In Very Short:
Operation Meaning
import os
os.path.isfile(filename)
Example Program:
import os, sys
if os.path.isfile(fname):
f = open(fname, 'r')
else:
print(fname + ' does not exist')
sys.exit() # stops the program
f.close()
Example Output:
If file is present:
If file is missing:
Easy logic:
Example Program:
import os, sys
if os.path.isfile(fname):
f = open(fname, 'r')
else:
print(fname + ' does not exist')
sys.exit()
# counters
cl = cw = cc = 0
f.close()
Example Output:
No. of lines: 3
No. of words: 13
No. of characters: 61
Binary files are different from text files (which store normal letters, words, and sentences).
👉 Example:
Result:
Now you will have a copy of cat.jpg saved as new.jpg in the same folder!
Example:
f1 = open('C:\\rnr\\cat.jpg', 'rb')
Double backslashes \\ are used because in Python, a single backslash \ has a special meaning.
So, always use double \ in file paths.
8. Quick Diagram:
Original file: cat.jpg
|
| read() bytes
v
New file created: new.jpg (copy)
Simple Summary
Concept Meaning
f = open('sample.txt', 'w')
f.write('some text')
f.close()
👉 Format:
As soon as the code inside with block finishes, Python will close the file automatically, even if there is an error!
Code:
What happens:
What happens:
Output:
I am a learner
Python is attractive
You must call close() manually. Python closes the file automatically.
Error in program → file may stay open. Error happens → file still closed properly.
7. Simple Diagram:
Without 'with':
open file -> write/read -> close file manually
With 'with':
open file -> write/read -> Python closes file automatically!
Summary Table
Concept Meaning
Final Line
Using with while working with files is the best practice in Python!
It makes your code safe, short, and clean. ✅✨
Pickle in Python
What is Pickling in Python?
Pickling is the process of converting a Python object into a byte stream (a series of bytes). This allows you to store
the object in a file or send it over a network. Later, you can read this byte stream and reconstruct the original Python
object. This is useful because it lets you save Python objects (like lists, dictionaries, instances of a class, etc.) to a file
and retrieve them later, preserving their structure and data.
What is Unpickling?
Unpickling is the reverse of pickling. It is the process of converting a byte stream (stored in a file or received from
another source) back into the original Python object. This process allows you to reconstruct Python objects from the
data stored in files.
In simple terms:
Pickling = Converting a Python object into a stream of bytes (to store or transfer).
Unpickling = Converting the byte stream back into the original Python object (to use or display).
You want to load the saved object (that was pickled) back into memory so you can use it again.
Let’s take an example where we want to store employee data. We’ll create an Emp class with employee details like
id, name, and salary, and then pickle (serialize) this object into a binary file.
class Emp:
def __init__(self, id, name, sal):
self.id = id
self.name = name
self.sal = sal
def display(self):
# Print employee details in a formatted way
print("{:5d} {:20s} {:10.2f}".format(self.id, self.name, self.sal))
__init__(self, id, name, sal) is the constructor method, which initializes the employee’s ID, name,
and salary when an Emp object is created.
display(self) is a method to display the details of an employee in a structured format (ID, name, salary).
1. with open('emp.dat', 'wb') as f:: This line opens the emp.dat file in write-binary mode ('wb').
This is important because pickling works with binary files.
2. n = int(input('How many employees? ')): This asks the user how many employees they want to
enter.
3. for i in range(n):: This loop runs for each employee. It collects the employee’s id, name, and
salary.
4. e = Emp.Emp(id, name, sal): This creates an Emp object e with the data the user provided.
5. pickle.dump(e, f): This serializes the Emp object e and writes it into the file emp.dat.
After running this program, the employee details will be stored in the binary file emp.dat in the form of pickled
objects.
Once the employee objects have been pickled into the file, we can read (unpickle) the objects from the file at any
time.
Here’s the program that unpickles the data from the file and displays it:
1. with open('emp.dat', 'rb') as f:: This opens the emp.dat file in read-binary mode ('rb'), so we
can read the pickled objects.
2. while True:: This loop continuously reads objects from the file.
3. obj = pickle.load(f): This reads (unpickles) the next object from the file and stores it in obj. If there
are no more objects left to read, it throws an EOFError (End of File Error).
4. obj.display(): This calls the display() method on the unpickled object to print the employee’s details.
5. except EOFError:: If we reach the end of the file (no more objects), this catches the error and breaks the
loop.
The output will show each employee’s details that were saved earlier:
Employee details:
100 Sai Kumar 9000.00
101 Ravi Teja 8900.50
102 Harsh Deep 12000.75
End of file reached...
Convenience: Pickling makes it easy to save complex objects like class instances (with various data types)
and later retrieve them.
Data Persistence: Pickling allows you to store the state of an application or data across different program
runs.
Network or File Transfer: It’s useful for sending Python objects over a network or saving them to a file for
future use.
Security Warning:
Pickle is not secure: You should never unpickle data from untrusted sources, as it can execute arbitrary code
during unpickling. Always ensure the data is from a trusted source before unpickling it.
…Half…