Environment Setup · Data Types · Control Flow · Strings · Collections
Functions · Recursion · OOP · File Handling · Regular Expressions
Read each section once for understanding, then re-read only the formula boxes, tables and exam tips before the exam. Every code snippet is exam-ready — type it out at least once in a Python interpreter; muscle memory beats re-reading. The practice questions at the end are graded Easy Medium Hard and every one has a full worked solution.
Python is a high-level, interpreted, general-purpose, dynamically typed programming language created by Guido van Rossum and first released in 1991. It emphasises code readability through significant whitespace (indentation) and a clean, minimal syntax.
Interpreter: a program that reads source code line by line, translates it into an intermediate form, and executes it immediately — no separate compilation-to-machine-code step is performed by the programmer.
| Feature | Meaning / Benefit |
|---|---|
| Interpreted | No explicit compilation; easy debugging and rapid prototyping. |
| Dynamically typed | Variable types are inferred at runtime; no type declaration needed. |
| High-level | Memory management (garbage collection) is automatic. |
| Object-oriented | Supports classes, objects, inheritance, polymorphism, encapsulation. |
| Free & open source | Downloadable from python.org; large community support. |
| Extensible & portable | Runs on Windows, Linux, macOS; can call C/C++ libraries. |
| Rich standard library | Modules for files, regex, maths, networking, data science, etc. |
Two major families exist: Python 2.x (legacy, end-of-life since January 2020) and Python 3.x (current). All new development uses Python 3. Important differences:
| Aspect | Python 2 | Python 3 |
|---|---|---|
print | Statement: print "Hi" | Function: print("Hi") |
| Integer division | 5/2 = 2 | 5/2 = 2.5, 5//2 = 2 |
| Strings | ASCII by default | Unicode by default |
| Iteration | range() returns list | range() returns lazy object |
https://www.python.org/downloads and download the latest Python 3 installer (.exe).python --version
pip --version
python is not recognised, add the installation folder (e.g. C:\Python312\) and its Scripts subfolder to the Path environment variable manually.Forgetting to add Python to PATH is the single most common installation error. It causes 'python' is not recognized as an internal or external command on Windows.
Anaconda is a free distribution that bundles Python together with 250+ scientific packages (NumPy, pandas, matplotlib, scikit-learn) and the conda package manager, plus Jupyter Notebook and Spyder. It is preferred for data-science work because dependency conflicts are resolved automatically.
https://anaconda.org/anaconda/python or anaconda.com.conda create -n myenv python=3.12| Mode | How to start | Characteristics |
|---|---|---|
| Interactive (REPL) | Type python in the terminal | Prompt >>>; each statement executes immediately; state is lost on exit. Ideal for quick testing. |
| Script mode | Save code in file.py, run python file.py | Whole program is executed top to bottom; persistent and reusable. Used for all real programs. |
| IDE / Notebook | IDLE, PyCharm, VS Code, Jupyter, CodeTantra | Provides editor, debugger, syntax highlighting, autocompletion. |
# hello.py
# This is a comment - the interpreter ignores it.
print("Hello, World!")
print("Welcome to INT108 - Python Programming")
Run it with python hello.py. Output:
Hello, World!
Welcome to INT108 - Python Programming
# Program: greet the user and show the Python version
import sys
name = input("Enter your name: ")
print("Hello,", name)
print("You are running Python", sys.version.split()[0])
Sample run: Enter your name: Aarav → Hello, Aarav followed by the version string.
# and extends to the end of the line.""" ... """ used as documentation.IndentationError.# Correct indentation
if 10 > 5:
print("Ten is greater") # 4 spaces - part of the if-block
print("Done") # back to column 0 - outside the block
Mixing tabs and spaces produces TabError. Always configure your editor to insert 4 spaces per tab.
When you run python file.py:
.pyc files inside __pycache__).Python dominates in Data Science, Machine Learning, Automation/DevOps, Web Development (Django, Flask), Cybersecurity scripting, and Scientific Computing — a key reason INT108 is tagged under Employability & Skill Development.
A variable is a name that refers to a value stored in memory. In Python, assignment creates the variable automatically — no declaration keyword is required.
Variable: a symbolic name bound to an object in memory. Assignment statement: name = expression — the expression on the right is evaluated first, then bound to the name on the left.
x = 10 # x refers to the integer object 10
message = "Hello" # message refers to a string object
pi = 3.14159 # pi refers to a float object
is_valid = True # is_valid refers to a bool object
| Rule | Valid | Invalid |
|---|---|---|
| Must begin with a letter or underscore | _count, total | 2total |
| May contain letters, digits, underscores | student_1 | student-1 |
| No spaces or special symbols | roll_no | roll no, rate% |
| Cannot be a Python keyword | marks | class, if, for |
| Case-sensitive | Age and age are different | — |
False None True and as assert async await
break class continue def del elif else except
finally for from global if import in is
lambda nonlocal not or pass raise return try
while with yield
NameError: name 'x' is not defined occurs when you use a variable before assigning it, or when you misspell a name. Python is case-sensitive: Total and total are two different variables. Always initialise a variable before its first use.
# Broken version
print(score) # NameError: name 'score' is not defined
# Corrected version
score = 0
print(score) # 0
A value is a fundamental unit of data such as a number or a string. Every value belongs to a type (class). Use the built-in type() function to inspect it.
| Type | Python name | Examples | Mutable? |
|---|---|---|---|
| Integer | int | 0, 42, -7, 10**20 | No |
| Floating point | float | 3.14, -0.5, 2.0e-3 | No |
| Complex | complex | 2+3j, 1j | No |
| Boolean | bool | True, False | No |
| String | str | "hello", 'a', "123" | No |
| List | list | [1, 2, 3] | Yes |
| Tuple | tuple | (1, 2, 3) | No |
| Dictionary | dict | {"a": 1} | Yes |
| Set | set | {1, 2, 3} | Yes |
| None | NoneType | None | No |
print(type(10)) # <class 'int'>
print(type(3.14)) # <class 'float'>
print(type("hi")) # <class 'str'>
print(type(True)) # <class 'bool'>
print(type([1,2])) # <class 'list'>
bool is a subclass of int: True == 1 and False == 0. Therefore True + True evaluates to 2.
3 + 4, x * y, len("abc").x = 5, print(x), if x > 0:.a + b, a and b are operands.length = 12
breadth = 8
area = length * breadth # expression evaluated, result stored
print("Area =", area) # Area = 96
Python performs implicit conversion (automatic type promotion) and supports explicit conversion (type casting) through constructor functions.
| Function | Purpose | Example | Result |
|---|---|---|---|
int(x) | Convert to integer (truncates floats) | int(3.9) | 3 |
float(x) | Convert to float | float("2.5") | 2.5 |
str(x) | Convert to string | str(45) | "45" |
bool(x) | Convert to boolean | bool(0), bool("a") | False, True |
list(x) | Convert iterable to list | list("abc") | ['a','b','c'] |
tuple(x) | Convert iterable to tuple | tuple([1,2]) | (1, 2) |
set(x) | Convert iterable to set | set([1,1,2]) | {1, 2} |
\(\text{int} \rightarrow \text{float} \rightarrow \text{complex}\) — Python promotes the narrower type to the wider one automatically.
print(3 + 4.5) # 7.5 (int promoted to float)
print(2 + 3j) # (2+3j) (int promoted to complex)
print(int("10") + 5) # 15 (explicit conversion of string)
int("hello") raises ValueError: invalid literal for int() with base 10: 'hello'. Always validate user input before converting.
input() always returns a string. Convert it explicitly when numeric input is needed.
name = input("Enter your name: ") # string
age = int(input("Enter your age: ")) # convert to int
cgpa = float(input("Enter your CGPA: ")) # convert to float
print("Name:", name, "Age:", age, "CGPA:", cgpa)
x = 12.34567
print(f"Value rounded to 2 decimals: {x:.2f}") # 12.35
print(f"{'Python':>10}") # Python
print(f"{255:b} {255:o} {255:x}") # 11111111 377 ff
a, b, c = 1, 2, 3 # multiple assignment
x = y = z = 0 # chained assignment
p, q = q, p # swap without a temporary variable
a = int(input("a = "))
b = int(input("b = "))
a, b = b, a # tuple packing / unpacking
print("After swap: a =", a, "b =", b)
Input: a = 5, b = 9 → Output: a = 9, b = 5.
import math
r = float(input("Radius: "))
area = math.pi * r ** 2
circumference = 2 * math.pi * r
print(f"Area = {area:.2f}")
print(f"Circumference = {circumference:.2f}")
For r = 7: Area = 153.94, Circumference = 43.98.
Questions that say “write a program to enter two numbers” almost always require int(input(...)) — writing only input() loses marks because the values remain strings and + would concatenate instead of add.
An operator is a symbol that performs a computation on one or more operands. Python classifies operators into seven families.
| Operator | Name | Example | Result |
|---|---|---|---|
+ | Addition | 7 + 3 | 10 |
- | Subtraction | 7 - 3 | 4 |
* | Multiplication | 7 * 3 | 21 |
/ | True division (float) | 7 / 2 | 3.5 |
// | Floor division | 7 // 2 | 3 |
% | Modulus (remainder) | 7 % 2 | 1 |
** | Exponentiation | 2 ** 5 | 32 |
Floor division rounds toward negative infinity: -7 // 2 = -4, and the remainder takes the sign of the divisor: -7 % 2 = 1. This differs from C/C++ truncation.
They return a Boolean value True or False.
| Operator | Meaning | Example | Result |
|---|---|---|---|
== | Equal to | 5 == 5 | True |
!= | Not equal to | 5 != 3 | True |
> | Greater than | 5 > 8 | False |
< | Less than | 5 < 8 | True |
>= | Greater than or equal | 5 >= 5 | True |
<= | Less than or equal | 5 <= 4 | False |
Python also supports chained comparisons: 0 < x < 100 is equivalent to 0 < x and x < 100.
| Operator | Description | Example | Result |
|---|---|---|---|
and | True if both operands are True | True and False | False |
or | True if at least one is True | True or False | True |
not | Negation | not True | False |
| A | B | A and B | A or B | not A |
|---|---|---|---|---|
| True | True | True | True | False |
| True | False | False | True | False |
| False | True | False | True | True |
| False | False | False | False | True |
and stops as soon as it finds a falsy operand; or stops as soon as it finds a truthy operand. Hence 0 and 1/0 returns 0 without raising ZeroDivisionError.
| Operator | Equivalent to | Example (x = 10) | New x |
|---|---|---|---|
= | Simple assignment | x = 5 | 5 |
+= | x = x + 2 | x += 2 | 12 |
-= | x = x - 2 | x -= 2 | 8 |
*= | x = x * 2 | x *= 2 | 20 |
/= | x = x / 2 | x /= 2 | 5.0 |
//= | x = x // 3 | x //= 3 | 3 |
%= | x = x % 3 | x %= 3 | 1 |
**= | x = x ** 2 | x **= 2 | 100 |
Operate on the binary representation of integers.
| Operator | Name | Example (a=12=1100, b=10=1010) | Result |
|---|---|---|---|
& | AND | a & b | 8 (1000) |
| | OR | a | b | 14 (1110) |
^ | XOR | a ^ b | 6 (0110) |
~ | NOT (complement) | ~a | -13 |
<< | Left shift | a << 1 | 24 |
>> | Right shift | a >> 1 | 6 |
\(a \ll n = a \times 2^{n}\) • \(a \gg n = \lfloor a / 2^{n} \rfloor\) • \(\sim a = -(a+1)\)
| Operator | Purpose | Example | Result |
|---|---|---|---|
in | Tests membership in a sequence | 3 in [1,2,3] | True |
not in | Tests absence | "z" not in "python" | True |
is | Tests identity (same object) | a is b | True if same object |
is not | Tests non-identity | a is not b | True if different objects |
== compares values; is compares memory identity. [1,2] == [1,2] is True, but [1,2] is [1,2] is False because they are two distinct list objects.
Precedence decides which operator binds tighter. Higher rows bind first.
| Level | Operators | Associativity |
|---|---|---|
| 1 (highest) | (), [], {}, function call | Left → Right |
| 2 | ** | Right → Left |
| 3 | +x, -x, ~x (unary) | Right → Left |
| 4 | *, /, //, % | Left → Right |
| 5 | +, - | Left → Right |
| 6 | <<, >> | Left → Right |
| 7 | & | Left → Right |
| 8 | ^ | Left → Right |
| 9 | | | Left → Right |
| 10 | ==, !=, <, >, <=, >=, is, in | Left → Right |
| 11 | not | Right → Left |
| 12 | and | Left → Right |
| 13 | or | Left → Right |
| 14 (lowest) | =, +=, -=, … | Right → Left |
PEMDAS — Parentheses, Exponentiation, Multiplication/Division, Addition/Subtraction — then comparisons, then not, and, or. When in doubt, use parentheses; they cost nothing and remove ambiguity.
result = 2 + 3 * 4 ** 2 // 8 - 1
print(result)
Step-by-step: 4**2 = 16 → 3*16 = 48 → 48//8 = 6 → 2+6 = 8 → 8-1 = 7. Output: 7.
n = int(input("Enter an integer: "))
print("Even" if n % 2 == 0 else "Odd")
print("Positive" if n > 0 else ("Negative" if n < 0 else "Zero"))
n = int(input("n = "))
is_power_of_two = (n > 0) and (n & (n - 1)) == 0
print(is_power_of_two)
For n = 16: 16 & 15 = 0 → True. For n = 12: 12 & 11 = 8 → False.
a, b, c = 4, 9, 2
print(a + b * c ** 2 % 5)
# 2**2 = 4 ; 9*4 = 36 ; 36%5 = 1 ; 4+1 = 5
print((a < b) and (b > c) or (a == c))
# (True) and (True) or (False) => True
Conditional (selection) statements let a program choose between alternative paths of execution based on a Boolean condition. Python provides if, if-else, if-elif-else, nested if, and the conditional (ternary) expression.
if Statementif condition:
statement_block
The block executes only when condition evaluates to True. A colon : and indentation are mandatory.
marks = int(input("Enter marks: "))
if marks >= 40:
print("Pass")
print("Result declared") # always executes
if-else Statementif condition:
block_A # executed when condition is True
else:
block_B # executed when condition is False
n = int(input("Enter a number: "))
if n % 2 == 0:
print(n, "is even")
else:
print(n, "is odd")
if-elif-else LadderUsed when there are more than two mutually exclusive alternatives. Conditions are tested top to bottom; the first true branch executes and the rest are skipped.
if condition1:
block1
elif condition2:
block2
elif condition3:
block3
else:
default_block
marks = float(input("Enter marks (0-100): "))
if marks >= 90:
grade = "A+"
elif marks >= 80:
grade = "A"
elif marks >= 70:
grade = "B"
elif marks >= 60:
grade = "C"
elif marks >= 40:
grade = "D"
else:
grade = "F (Fail)"
print("Grade:", grade)
For marks = 76 the first true condition is marks >= 70 → grade B.
In an elif ladder, always place the most restrictive condition first. If you wrote marks >= 40 before marks >= 90, every mark above 40 would be graded D.
if StatementsAn if inside another if — used when a second decision depends on the outcome of the first.
a = int(input("a = "))
b = int(input("b = "))
c = int(input("c = "))
if a > b:
if a > c:
print("Largest is", a)
else:
print("Largest is", c)
else:
if b > c:
print("Largest is", b)
else:
print("Largest is", c)
Equivalent single-level version:
if a >= b and a >= c:
print("Largest is", a)
elif b >= a and b >= c:
print("Largest is", b)
else:
print("Largest is", c)
value_if_true if condition else value_if_false
n = 7
result = "Even" if n % 2 == 0 else "Odd"
print(result) # Odd
# Nested ternary
x = 0
label = "Positive" if x > 0 else ("Negative" if x < 0 else "Zero")
print(label) # Zero
Any object can be used as a condition. The following are falsy; everything else is truthy.
| Falsy values | Truthy values |
|---|---|
False, None | True, non-zero numbers |
0, 0.0, 0j | "0", "False", any non-empty string |
"" (empty string) | [1], (0,), {1}, {"a":1} |
[], (), {}, set() | Any object whose __bool__ returns True |
items = []
if items:
print("Non-empty")
else:
print("Empty list") # prints this
Rule: a year is a leap year if it is divisible by 4 but not by 100, or it is divisible by 400.
year = int(input("Enter a year: "))
if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):
print(year, "is a leap year")
else:
print(year, "is not a leap year")
Test cases: 2024 → leap; 1900 → not leap; 2000 → leap.
a = float(input("First number: "))
op = input("Operator (+,-,*,/): ")
b = float(input("Second number: "))
if op == "+":
print("Result =", a + b)
elif op == "-":
print("Result =", a - b)
elif op == "*":
print("Result =", a * b)
elif op == "/":
if b == 0:
print("Error: division by zero")
else:
print("Result =", a / b)
else:
print("Invalid operator")
When a question says “using if-elif-else”, do not use a ternary expression or a dictionary lookup — examiners award marks for the exact construct requested. Also remember the trailing colon and consistent indentation; missing them is the most common loss of marks.
An iterative statement (loop) repeats a block of statements. Python offers two loops — while (condition-controlled) and for (collection-controlled) — plus the break, continue and else control statements.
while Loopwhile condition:
body # repeats while condition is True
update # must eventually make condition False
If the condition never becomes False, the loop never terminates. Always update the loop variable inside the body. Press Ctrl + C to interrupt a runaway loop.
n = int(input("n = "))
i, total = 1, 0
while i <= n:
total += i
i += 1
print("Sum =", total)
For n = 5 the loop adds 1+2+3+4+5 = 15.
for LoopIterates over the items of any iterable — string, list, tuple, set, dictionary, range, file, etc.
for variable in iterable:
body
range() Functionrange(stop) → 0 … stop−1 • range(start, stop) → start … stop−1 • range(start, stop, step)
list(range(5)) # [0, 1, 2, 3, 4]
list(range(2, 8)) # [2, 3, 4, 5, 6, 7]
list(range(1, 10, 2)) # [1, 3, 5, 7, 9]
list(range(10, 0, -2)) # [10, 8, 6, 4, 2]
range() in Python 3 is a lazy sequence object, not a list. Wrap it with list() to see its elements. The stop value is always excluded.
n = int(input("Table of: "))
for i in range(1, 11):
print(f"{n} x {i} = {n * i}")
for and while| Aspect | for | while |
|---|---|---|
| Use when | Number of iterations is known or you iterate a collection | Number of iterations is unknown; depends on a runtime condition |
| Termination | Automatic when iterable is exhausted | Programmer must update the condition |
| Typical use | Traversing a list, string, range | Menu loops, sentinel-controlled input, guessing games |
| Risk | Low | Infinite loop if update is forgotten |
A loop inside another loop. For an outer loop of \(m\) iterations and an inner loop of \(n\) iterations, the inner body executes \(m \times n\) times.
rows = 5
for i in range(1, rows + 1):
for j in range(i):
print("*", end="")
print()
Output:
*
**
***
****
*****
for n in range(2, 6):
print(f"--- Table of {n} ---")
for i in range(1, 11):
print(f"{n} x {i} = {n * i}")
print()
rows = 4
num = 1
i = 1
while i <= rows:
j = 1
while j <= i:
print(num, end=" ")
num += 1
j += 1
print()
i += 1
1
2 3
4 5 6
7 8 9 10
break, continue, else| Statement | Effect |
|---|---|
break | Exits the innermost loop immediately. |
continue | Skips the remaining body and jumps to the next iteration. |
pass | Does nothing — a syntactic placeholder. |
else on a loop | Executes only if the loop finished without hitting break. |
n = int(input("Enter a number: "))
if n < 2:
print("Not prime")
else:
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
print(n, "is not prime")
break
else:
print(n, "is prime")
The else clause runs only when no divisor was found, i.e. when break never fired.
for i in range(1, 11):
if i % 2 == 0:
continue
print(i, end=" ")
# Output: 1 3 5 7 9
The random module generates pseudo-random numbers, essential for simulations, games and testing.
| Function | Returns | Example |
|---|---|---|
random.random() | Float in [0.0, 1.0) | 0.7231... |
random.randint(a, b) | Integer in [a, b] inclusive | randint(1, 6) → 1…6 |
random.randrange(a, b, s) | Random element from range(a,b,s) | randrange(0, 10, 2) → 0,2,4,6,8 |
random.choice(seq) | Random element of a sequence | choice(['a','b','c']) |
random.shuffle(lst) | Shuffles a list in place | shuffle(cards) |
random.sample(pop, k) | List of k unique items | sample(range(1,50), 6) |
random.seed(x) | Fixes the sequence for reproducibility | seed(42) |
import random
random.seed(1) # reproducible results
counts = [0] * 7
for _ in range(1000):
roll = random.randint(1, 6) # simulate one dice throw
counts[roll] += 1
for face in range(1, 7):
print(f"Face {face}: {counts[face]} times")
import random
secret = random.randint(1, 50)
attempts = 0
while True:
guess = int(input("Guess a number (1-50): "))
attempts += 1
if guess < secret:
print("Too low!")
elif guess > secret:
print("Too high!")
else:
print(f"Correct! You took {attempts} attempts.")
break
These are two fundamental program-design techniques introduced with loops and reused throughout the course.
Encapsulation: wrapping a piece of repeated logic inside a function so that it can be called by name instead of copied. It hides the implementation details.
Generalization: replacing hard-coded literal values with parameters so the same function works for many inputs — e.g. turning a “print the 5× table” program into “print the table of n”.
# Specific: prints only the table of 5, only 10 rows
for i in range(1, 11):
print(5 * i)
# Generalized: any number, any number of rows, reusable
def print_table(n, upto=10):
"""Print the multiplication table of n up to 'upto'."""
for i in range(1, upto + 1):
print(f"{n} x {i} = {n * i}")
print_table(5)
print_table(7, 5)
Encapsulation happened when the loop was wrapped in print_table; generalization happened when 5 and 10 became parameters.
n = int(input("How many terms? "))
a, b = 0, 1
for _ in range(n):
print(a, end=" ")
a, b = b, a + b
print()
For n = 8: 0 1 1 2 3 5 8 13
n = int(input("Enter a number: "))
total = 0
for i in range(1, n):
if n % i == 0:
total += i
if total == n:
print(n, "is a perfect number")
else:
print(n, "is not a perfect number")
28 → divisors 1+2+4+7+14 = 28 → perfect.
n = int(input("Enter a number: "))
digits = len(str(n))
total = 0
temp = n
while temp > 0:
digit = temp % 10
total += digit ** digits
temp //= 10
print(n, "is Armstrong" if total == n else "is not Armstrong")
153 → \(1^3+5^3+3^3 = 1+125+27 = 153\) → Armstrong.
For nested-loop pattern questions, always trace the first three rows by hand. Examiners frequently award marks for the printed pattern even if the loop bounds are slightly wrong — but only if the logic is visible.
A string is a sequence of characters enclosed in single, double or triple quotes. It is a compound data type — built from smaller pieces (characters) — and it is immutable: once created, its characters cannot be changed in place.
s1 = 'hello'
s2 = "Python"
s3 = """A multi-line
string literal"""
s4 = str(1234) # '1234'
The built-in len() function returns the number of characters. Indexing accesses a single character; indices start at 0 and negative indices count from the end.
For a string of length \(n\): valid indices are \(0 \ldots n-1\) and \(-1 \ldots -n\).
s = "PYTHON"
# index : 0 1 2 3 4 5
# char : P Y T H O N
# neg : -6 -5 -4 -3 -2 -1
print(len(s)) # 6
print(s[0]) # P
print(s[5]) # N
print(s[-1]) # N
print(s[-6]) # P
# print(s[6]) # IndexError: string index out of range
Traversal means visiting each character in turn, using either a while loop with an index or a for loop directly.
s = "Python"
# Using while + index
i = 0
while i < len(s):
print(s[i], end="-")
i += 1
print()
# Using for (Pythonic)
for ch in s:
print(ch, end="-")
print()
# Using enumerate to get index and character
for idx, ch in enumerate(s):
print(idx, ch)
s[start : stop : step] — returns characters from start up to but not including stop, taking every step-th character.
| Slice | Result for s = "PYTHON" | Explanation |
|---|---|---|
s[0:3] | 'PYT' | Indices 0,1,2 |
s[:3] | 'PYT' | Start defaults to 0 |
s[3:] | 'HON' | Stop defaults to len |
s[:] | 'PYTHON' | Full copy |
s[-3:] | 'HON' | Last three |
s[::2] | 'PTO' | Every second character |
s[::-1] | 'NOHTYP' | Reversed string |
s[1:5:3] | 'YH' | Index 1 then 4 |
Out-of-range slice bounds are silently clamped. "abc"[1:100] returns 'bc', whereas "abc"[100] raises IndexError.
s = "hello"
# s[0] = 'H' # TypeError: 'str' object does not support item assignment
s = 'H' + s[1:] # build a NEW string instead
print(s) # Hello
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ | Concatenation | "Py" + "thon" | 'Python' |
* | Repetition | "ab" * 3 | 'ababab' |
in | Membership | "th" in "python" | True |
not in | Non-membership | "z" not in "python" | True |
==, != | Equality | "abc" == "abc" | True |
<, > | Lexicographic comparison | "apple" < "banana" | True |
Comparison uses lexicographic (dictionary) order based on the Unicode code point of each character. Uppercase letters have smaller code points than lowercase letters.
print("apple" < "banana") # True ('a' < 'b')
print("Apple" < "apple") # True ('A'=65 < 'a'=97)
print("abc" == "ABC") # False
print("abc" < "abd") # True (first difference at index 2)
print("ab" < "abc") # True (shorter prefix is smaller)
Use s1.lower() == s2.lower() or s1.casefold() == s2.casefold().
find() Function and Searching| Method | Returns | Example | Result |
|---|---|---|---|
find(sub) | Lowest index of sub, or −1 | "banana".find("na") | 2 |
rfind(sub) | Highest index, or −1 | "banana".rfind("na") | 4 |
index(sub) | Like find but raises ValueError | "banana".index("na") | 2 |
count(sub) | Number of non-overlapping occurrences | "banana".count("na") | 2 |
startswith(p) | True if string begins with p | "python".startswith("py") | True |
endswith(p) | True if string ends with p | "file.py".endswith(".py") | True |
find()def count_occurrences(text, sub):
"""Count how many times 'sub' appears in 'text' (non-overlapping)."""
count = 0
start = 0
while True:
pos = text.find(sub, start)
if pos == -1:
break
count += 1
start = pos + len(sub)
return count
print(count_occurrences("banana", "na")) # 2
print(count_occurrences("aaaa", "aa")) # 2
Encapsulation: the search loop is wrapped in a function. Generalization: text and sub are parameters, so the same function works for any string.
| Method | Purpose | Example → Result |
|---|---|---|
upper() / lower() | Change case | "Py".upper() → 'PY' |
capitalize() | First char upper, rest lower | "hello world".capitalize() → 'Hello world' |
title() | First letter of each word upper | "hello world".title() → 'Hello World' |
strip() | Remove leading/trailing whitespace | " hi ".strip() → 'hi' |
replace(a, b) | Substitute substring | "a-b".replace("-","+") → 'a+b' |
split(sep) | Break into a list | "a,b,c".split(",") → ['a','b','c'] |
join(iterable) | Concatenate with separator | "-".join(['a','b']) → 'a-b' |
isdigit() | All characters are digits | "123".isdigit() → True |
isalpha() | All characters are letters | "abc".isalpha() → True |
isalnum() | Letters or digits only | "ab12".isalnum() → True |
isspace() | All whitespace | " \t".isspace() → True |
zfill(n) | Pad with leading zeros | "7".zfill(3) → '007' |
| Style | Syntax | Example |
|---|---|---|
| %-formatting (legacy) | "%s is %d" % (name, age) | "Aarav is 19" |
str.format() | "{} is {}".format(name, age) | "Aarav is 19" |
| f-string (Python 3.6+) | f"{name} is {age}" | "Aarav is 19" |
price = 1234.5678
print(f"Price: {price:.2f}") # Price: 1234.57
print(f"{'Item':<10}{'Qty':>5}") # Item Qty
print(f"{0.75:.1%}") # 75.0%
s = input("Enter a string: ").lower().replace(" ", "")
rev = ""
for ch in s: # build the reversed string with a loop
rev = ch + rev
if s == rev:
print("Palindrome")
else:
print("Not a palindrome")
Input Madam → cleaned madam → reversed madam → Palindrome.
text = input("Enter a line of text: ")
vowels = consonants = upper = lower = digits = 0
for ch in text:
if ch.isupper():
upper += 1
if ch.islower():
lower += 1
if ch.isdigit():
digits += 1
if ch.isalpha():
if ch.lower() in "aeiou":
vowels += 1
else:
consonants += 1
print("Vowels :", vowels)
print("Consonants :", consonants)
print("Uppercase :", upper)
print("Lowercase :", lower)
print("Digits :", digits)
text = "python programming is fun"
words = text.split()
result = " ".join(w[0].upper() + w[1:] for w in words)
print(result) # Python Programming Is Fun
s = "programming"
seen = ""
for ch in s:
if ch not in seen:
print(ch, ":", s.count(ch))
seen += ch
Remember that len(s) is a function while s.upper() is a method. Writing s.len() is a guaranteed mark loss. Also, string methods never modify the original string — they return a new one.
A list is an ordered, mutable collection of items enclosed in square brackets. Items may be of mixed types, and lists may be nested.
numbers = [10, 20, 30, 40]
mixed = [1, "two", 3.0, True, None]
nested = [[1, 2], [3, 4], [5, 6]]
empty = []
from_range = list(range(5)) # [0, 1, 2, 3, 4]
chars = list("abc") # ['a', 'b', 'c']
Lists behave exactly like strings for indexing and slicing — but they are mutable, so slice assignment and item assignment are allowed.
L = [10, 20, 30, 40, 50]
print(L[0], L[-1]) # 10 50
print(L[1:4]) # [20, 30, 40]
print(L[::-1]) # [50, 40, 30, 20, 10]
print(len(L)) # 5
L[0] = 99 # item assignment - allowed!
print(L) # [99, 20, 30, 40, 50]
L[1:3] = [200, 300, 400] # slice assignment - length may change
print(L) # [99, 200, 300, 400, 40, 50]
matrix = [[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]
print(matrix[1][2]) # 6 (row 1, column 2)
for row in matrix:
for value in row:
print(value, end=" ")
print()
fruits = ["apple", "banana", "cherry"]
for fruit in fruits: # direct traversal
print(fruit)
for i in range(len(fruits)): # index-based traversal
print(i, fruits[i])
print("banana" in fruits) # True
print("grape" not in fruits) # True
| Operation | Example | Result |
|---|---|---|
Concatenation + | [1,2] + [3,4] | [1,2,3,4] |
Repetition * | [0] * 3 | [0,0,0] |
Membership in | 2 in [1,2,3] | True |
| Length | len([1,2,3]) | 3 |
| Maximum / Minimum | max([3,1,2]) | 3 |
| Sum | sum([1,2,3]) | 6 |
| Sorting (new list) | sorted([3,1,2]) | [1,2,3] |
| Method | Purpose | Example (L = [1,2,3]) | Result |
|---|---|---|---|
append(x) | Add one item at the end | L.append(4) | [1,2,3,4] |
extend(iter) | Append all items of an iterable | L.extend([5,6]) | [1,2,3,5,6] |
insert(i, x) | Insert x at index i | L.insert(1, 9) | [1,9,2,3] |
remove(x) | Delete first occurrence of value x | L.remove(2) | [1,3] |
pop(i) | Remove and return item at index i (default last) | L.pop() | returns 3, L = [1,2] |
del L[i] | Delete item / slice | del L[0] | [2,3] |
clear() | Remove all items | L.clear() | [] |
index(x) | First index of value x | [1,2,3].index(2) | 1 |
count(x) | Number of occurrences | [1,1,2].count(1) | 2 |
sort() | Sort in place | L.sort(reverse=True) | descending |
reverse() | Reverse in place | L.reverse() | reversed order |
copy() | Shallow copy | M = L.copy() | new list |
L = [10, 20, 30, 40, 50]
L.append(60) # insertion at end
L.insert(0, 5) # insertion at index 0
print(L) # [5, 10, 20, 30, 40, 50, 60]
L[2] = 200 # substitution
print(L) # [5, 10, 200, 30, 40, 50, 60]
L.remove(200) # deletion by value
del L[0] # deletion by index
popped = L.pop() # delete and return last
print(L, "| popped:", popped)
B = A does not copy a list — it makes B refer to the same object. Modifying B also modifies A.
A = [1, 2, 3]
B = A # ALIAS - same object
B.append(4)
print(A) # [1, 2, 3, 4] (A changed too!)
C = A.copy() # SHALLOW COPY - new object
C.append(5)
print(A) # [1, 2, 3, 4] (unchanged)
print(C) # [1, 2, 3, 4, 5]
D = A[:] # another way to copy
E = list(A) # another way to copy
For nested lists, a shallow copy still shares the inner lists. Use copy.deepcopy() for a fully independent copy.
import copy
deep = copy.deepcopy(nested)
[expression for item in iterable if condition]
squares = [x ** 2 for x in range(1, 6)]
print(squares) # [1, 4, 9, 16, 25]
evens = [x for x in range(20) if x % 2 == 0]
print(evens) # [0, 2, 4, ..., 18]
words = ["hello", "world"]
upper = [w.upper() for w in words] # ['HELLO', 'WORLD']
matrix = [[1,2],[3,4]]
flat = [n for row in matrix for n in row] # [1, 2, 3, 4]
nums = [45, 12, 89, 33, 67, 89, 5]
largest = max(nums)
smallest = min(nums)
unique = sorted(set(nums), reverse=True)
second = unique[1] if len(unique) > 1 else None
print("Largest :", largest)
print("Smallest:", smallest)
print("Second largest:", second)
Output: Largest 89, Smallest 5, Second largest 67.
data = [1, 3, 2, 3, 5, 1, 4]
result = []
for x in data:
if x not in result:
result.append(x)
print(result) # [1, 3, 2, 5, 4]
A = [[1, 2], [3, 4]]
B = [[5, 6], [7, 8]]
C = []
for i in range(len(A)):
row = []
for j in range(len(A[0])):
row.append(A[i][j] + B[i][j])
C.append(row)
print(C) # [[6, 8], [10, 12]]
stack = []
def push(item):
stack.append(item)
print(f"Pushed {item}")
def pop_item():
if not stack:
print("Stack underflow")
return None
return stack.pop()
def peek():
return stack[-1] if stack else None
push(10); push(20); push(30)
print("Top:", peek())
print("Popped:", pop_item())
print("Stack now:", stack)
marks = [78, 92, 65, 88, 54]
total = sum(marks)
average = total / len(marks)
print(f"Total = {total}, Average = {average:.2f}")
A tuple is an ordered, immutable sequence enclosed in parentheses. Once created, its elements cannot be added, removed or replaced. Tuples are faster and safer than lists when the data must not change.
point = (3, 5)
rgb = (255, 128, 0)
mixed = (1, "two", 3.0)
nested = ((1, 2), (3, 4))
empty = ()
single = (42,) # NOTE the trailing comma!
not_a_tuple = (42) # this is just the integer 42
from_list = tuple([1, 2, 3])
(42) is an integer, not a tuple. You must write (42,) with a trailing comma. Check with type((42,)) → <class 'tuple'>.
t = (10, 20, 30, 40, 50)
print(t[0]) # 10
print(t[-1]) # 50
print(t[1:4]) # (20, 30, 40)
print(t[::-1]) # (50, 40, 30, 20, 10)
print(len(t)) # 5
print(20 in t) # True
# t[0] = 99 # TypeError: 'tuple' object does not support item assignment
t = a, b, c (packing) • a, b, c = t (unpacking)
# Packing
person = "Aarav", 19, "CSE"
# Unpacking
name, age, branch = person
print(name, age, branch) # Aarav 19 CSE
# Swapping via tuple packing
a, b = 1, 2
a, b = b, a
print(a, b) # 2 1
# Starred unpacking
first, *middle, last = (1, 2, 3, 4, 5)
print(first, middle, last) # 1 [2, 3, 4] 5
A function can return several values at once by returning a tuple.
def min_max(numbers):
return min(numbers), max(numbers)
low, high = min_max([4, 9, 1, 7])
print("Min:", low, "Max:", high) # Min: 1 Max: 9
| Operation | Example | Result |
|---|---|---|
count(x) | (1,2,2,3).count(2) | 2 |
index(x) | ('a','b','c').index('b') | 1 |
| Concatenation | (1,2) + (3,4) | (1,2,3,4) |
| Repetition | (0,) * 3 | (0,0,0) |
| Membership | 3 in (1,2,3) | True |
len(), max(), min(), sum() | sum((1,2,3)) | 6 |
| Conversion | list((1,2)) | [1, 2] |
| Sorting (new list) | sorted((3,1,2)) | [1, 2, 3] |
| Feature | Tuple | List |
|---|---|---|
| Syntax | (1, 2, 3) | [1, 2, 3] |
| Mutability | Immutable | Mutable |
| Methods available | Only count(), index() | Many (append, sort, …) |
| Performance | Faster, less memory | Slower, more memory |
| Usable as dict key | Yes (if hashable elements) | No |
| Typical use | Fixed records, coordinates, function returns | Collections that change during execution |
locations = {
(28.61, 77.21): "Delhi",
(19.07, 72.87): "Mumbai",
(12.97, 77.59): "Bengaluru"
}
print(locations[(28.61, 77.21)]) # Delhi
votes = ("A", "B", "A", "C", "A", "B")
for candidate in set(votes):
print(candidate, "->", votes.count(candidate), "votes")
If a question asks “why are tuples used when lists exist?”, answer: immutability guarantees data integrity, allows use as dictionary keys, and provides better performance and memory efficiency.
A dictionary is an unordered (insertion-ordered since Python 3.7), mutable collection of key–value pairs. Keys must be unique and hashable (immutable types); values may be of any type.
student = {"name": "Aarav", "age": 19, "cgpa": 8.7}
empty = {}
from_pairs = dict([("a", 1), ("b", 2)])
squares = {x: x ** 2 for x in range(1, 5)} # {1:1, 2:4, 3:9, 4:16}
| Operation | Syntax | Behaviour |
|---|---|---|
| Access by key | d["name"] | Raises KeyError if the key is missing |
| Safe access | d.get("name") | Returns None (or a default) if missing |
| Default access | d.get("x", 0) | Returns 0 if "x" is absent |
| Insert / update | d["city"] = "Delhi" | Adds or overwrites |
| Delete | del d["age"] | Removes the pair |
| Pop | d.pop("age") | Removes and returns the value |
| Clear | d.clear() | Empties the dictionary |
| Membership | "name" in d | Tests keys, not values |
| Length | len(d) | Number of key–value pairs |
student = {"name": "Aarav", "age": 19}
student["cgpa"] = 8.7 # add a new key
student["age"] = 20 # update an existing key
print(student.get("name")) # Aarav
print(student.get("city", "N/A")) # N/A (no KeyError)
print(len(student)) # 3
del student["age"]
print(student) # {'name': 'Aarav', 'cgpa': 8.7}
| Method | Returns | Example |
|---|---|---|
keys() | View of all keys | dict_keys(['name','cgpa']) |
values() | View of all values | dict_values(['Aarav', 8.7]) |
items() | View of (key, value) tuples | dict_items([('name','Aarav'), ...]) |
update(other) | Merge another dict | d.update({"x": 1}) |
setdefault(k, v) | Get or insert a default | d.setdefault("z", 0) |
popitem() | Remove last inserted pair | d.popitem() |
marks = {"Maths": 92, "Physics": 85, "Python": 98}
for subject in marks.keys():
print(subject)
for score in marks.values():
print(score)
for subject, score in marks.items(): # most common pattern
print(f"{subject}: {score}")
students = {
"S101": {"name": "Aarav", "marks": [88, 92, 79]},
"S102": {"name": "Diya", "marks": [95, 81, 90]}
}
for roll, info in students.items():
avg = sum(info["marks"]) / len(info["marks"])
print(roll, info["name"], f"Average = {avg:.2f}")
text = "programming"
freq = {}
for ch in text:
freq[ch] = freq.get(ch, 0) + 1
print(freq)
# {'p': 1, 'r': 2, 'o': 1, 'g': 2, 'a': 1, 'm': 2, 'i': 1, 'n': 1}
The idiom freq.get(ch, 0) + 1 avoids a KeyError for first-time characters.
sentence = "the quick brown fox jumps over the lazy dog the fox"
words = sentence.split()
counts = {}
for w in words:
counts[w] = counts.get(w, 0) + 1
for word, n in sorted(counts.items(), key=lambda kv: (-kv[1], kv[0])):
print(f"{word:<8} {n}")
Top output: the 3, fox 2, then the remaining words once each.
marks = {"Aarav": 88, "Diya": 95, "Kabir": 72, "Meera": 60}
topper = max(marks, key=marks.get)
average = sum(marks.values()) / len(marks)
print("Topper :", topper, marks[topper])
print(f"Average: {average:.2f}")
print("Passed :", [n for n, m in marks.items() if m >= 40])
d1 = {"a": 1, "b": 2}
d2 = {"b": 20, "c": 30}
merged = {**d1, **d2} # d2 overwrites duplicate keys
print(merged) # {'a': 1, 'b': 20, 'c': 30}
d1.update(d2) # in-place merge
print(d1) # {'a': 1, 'b': 20, 'c': 30}
Questions that require counting occurrences, mapping names to values, or looking up a value by an identifier are almost always dictionary questions. Prefer get(key, default) over direct indexing to avoid KeyError.
A set is an unordered, mutable collection of unique, hashable elements. Duplicate values are automatically discarded. Sets are implemented with hash tables, giving average \(O(1)\) membership testing.
s1 = {1, 2, 3, 4}
s2 = set([3, 4, 5, 6])
empty = set() # NOTE: {} creates an empty DICT, not a set
chars = set("banana") # {'b', 'a', 'n'}
print({1, 2, 2, 3, 3, 3}) # {1, 2, 3} - duplicates removed
{} creates an empty dictionary. To create an empty set you must write set().
| Operation | Operator | Method | Example (A={1,2,3}, B={3,4,5}) | Result |
|---|---|---|---|---|
| Union | | | A.union(B) | A | B | {1,2,3,4,5} |
| Intersection | & | A.intersection(B) | A & B | {3} |
| Difference | - | A.difference(B) | A - B | {1,2} |
| Symmetric difference | ^ | A.symmetric_difference(B) | A ^ B | {1,2,4,5} |
| Subset | <= | A.issubset(B) | {1,2} <= A | True |
| Superset | >= | A.issuperset(B) | A >= {1,2} | True |
| Disjoint | — | A.isdisjoint(B) | {1} vs {2} | True |
| Method | Purpose | Example |
|---|---|---|
add(x) | Add a single element | s.add(9) |
update(iter) | Add multiple elements | s.update([7, 8]) |
remove(x) | Remove x; raises KeyError if absent | s.remove(3) |
discard(x) | Remove x; silent if absent | s.discard(99) |
pop() | Remove and return an arbitrary element | s.pop() |
clear() | Remove all elements | s.clear() |
copy() | Shallow copy | t = s.copy() |
An immutable version of a set. Because it is hashable, it can be used as a dictionary key or as an element of another set.
fs = frozenset([1, 2, 3])
# fs.add(4) # AttributeError: 'frozenset' object has no attribute 'add'
permissions = {frozenset({"read", "write"}): "editor"}
data = [1, 2, 2, 3, 4, 4, 5, 1]
unique = list(set(data))
print(unique) # order not guaranteed, e.g. [1, 2, 3, 4, 5]
To preserve the original order while removing duplicates, use the list-and-check technique from Example 7.3.
aarav = {"Maths", "Physics", "Python", "Chemistry"}
diya = {"Python", "Chemistry", "Biology", "English"}
print("Common :", aarav & diya)
print("All :", aarav | diya)
print("Only Aarav:", aarav - diya)
print("Not common :", aarav ^ diya)
Common : {'Python', 'Chemistry'}
All : {'Maths', 'Physics', 'Python', 'Chemistry', 'Biology', 'English'}
Only Aarav: {'Maths', 'Physics'}
Not common : {'Maths', 'Physics', 'Biology', 'English'}
vowels = set("aeiou")
word = "programming"
found = {ch for ch in word if ch in vowels}
print(found) # {'o', 'a', 'i'}
all_rolls = set(range(1, 11))
present = {1, 2, 4, 5, 7, 10}
absent = all_rolls - present
print("Absent:", sorted(absent)) # [3, 6, 8, 9]
A function is a named block of reusable code that performs a specific task. Functions support code reuse, modularity, readability and easier debugging.
def function_name(parameters): """docstring""" body return value
def greet(name):
"""Return a greeting message for the given name."""
return f"Hello, {name}!"
message = greet("Aarav")
print(message) # Hello, Aarav!
Parameter: the variable listed in the function definition (a placeholder).
Argument: the actual value supplied when the function is called.
def add(a, b): # a, b are PARAMETERS
return a + b
print(add(3, 5)) # 3, 5 are ARGUMENTS
| Type | Description | Example |
|---|---|---|
| Positional | Matched by order | def f(a,b): ... → f(1,2) |
| Keyword | Matched by name | f(b=2, a=1) |
| Default | Used when the argument is omitted | def f(a, b=10): |
Variable-length (*args) | Extra positional args as a tuple | def f(*nums): |
Variable-length (**kwargs) | Extra keyword args as a dict | def f(**opts): |
In a definition, non-default parameters must come before default parameters. def f(a=1, b) is a SyntaxError.
def power(base, exponent=2):
return base ** exponent
print(power(5)) # 25 (default exponent)
print(power(2, 10)) # 1024
print(power(exponent=3, base=4)) # 64 (keyword arguments)
def total(*numbers):
return sum(numbers)
print(total(1, 2, 3, 4)) # 10
def profile(**info):
for k, v in info.items():
print(f"{k}: {v}")
profile(name="Aarav", age=19, branch="CSE")
return immediately exits the function and passes a value back.return (or with bare return) returns None.def divide(a, b):
if b == 0:
return None # explicit "no result"
return a / b
print(divide(10, 2)) # 5.0
print(divide(10, 0)) # None
def statistics(nums):
return min(nums), max(nums), sum(nums) / len(nums)
lo, hi, avg = statistics([4, 8, 15, 16, 23, 42])
print(lo, hi, avg)
Python resolves names in this order: Local → Enclosing → Global → Built-in.
| Scope | Where defined | Accessible |
|---|---|---|
| Local | Inside the current function | Only within that function |
| Enclosing | In an outer function (closures) | Inner functions |
| Global | At module level | Everywhere in the module |
| Built-in | Python's built-in namespace | Everywhere (len, print, …) |
counter = 0 # global
def increment():
global counter # declare intent to modify the global
counter += 1
increment(); increment()
print(counter) # 2
def outer():
x = 10
def inner():
nonlocal x # refers to outer's x, not a new local
x += 5
inner()
return x
print(outer()) # 15
Assigning to a name inside a function makes it local for the whole function. Reading it before assignment raises UnboundLocalError. Use global or nonlocal when you intend to modify an outer variable.
A lambda is a small anonymous function written as a single expression.
lambda parameters : expression
square = lambda x: x * x
print(square(6)) # 36
add = lambda a, b: a + b
print(add(3, 4)) # 7
# Commonly used with sorted(), map(), filter()
nums = [5, 2, 9, 1]
print(sorted(nums, key=lambda n: -n)) # [9, 5, 2, 1]
print(list(map(lambda x: x ** 2, nums))) # [25, 4, 81, 1]
print(list(filter(lambda x: x > 3, nums)))# [5, 9]
Recursion is a technique in which a function calls itself to solve a smaller instance of the same problem. Every recursive function must have:
def factorial(n):
if n <= 1: # base case
return 1
return n * factorial(n - 1) # recursive case
print(factorial(5)) # 120
print(factorial(0)) # 1
Trace for n = 4: \(4 \times factorial(3) \to 4 \times 3 \times factorial(2) \to 4 \times 3 \times 2 \times factorial(1) \to 4 \times 3 \times 2 \times 1 = 24\).
def fib(n):
if n <= 1: # base cases: fib(0)=0, fib(1)=1
return n
return fib(n - 1) + fib(n - 2)
for i in range(8):
print(fib(i), end=" ") # 0 1 1 2 3 5 8 13
Note: naive recursion is exponential, \(O(2^n)\). Use iteration or memoization for large n.
def digit_sum(n):
if n == 0:
return 0
return n % 10 + digit_sum(n // 10)
print(digit_sum(12345)) # 15
def binary_search(arr, low, high, target):
if low > high:
return -1
mid = (low + high) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
return binary_search(arr, mid + 1, high, target)
else:
return binary_search(arr, low, mid - 1, target)
data = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]
print(binary_search(data, 0, len(data) - 1, 23)) # 5
| Aspect | Recursion | Iteration |
|---|---|---|
| Definition | Function calls itself | Loop repeats a block |
| Termination | Base case | Loop condition |
| Memory | Uses call stack — more memory | Constant memory |
| Speed | Slower (function-call overhead) | Faster |
| Readability | Elegant for tree/divide-and-conquer problems | Straightforward for linear problems |
| Risk | RecursionError for deep recursion | Infinite loop if condition never fails |
Python's default recursion depth limit is 1000. Exceeding it raises RecursionError: maximum recursion depth exceeded. Check with sys.getrecursionlimit() and change with sys.setrecursionlimit(n) — but prefer iteration for deep problems.
Object-Oriented Programming (OOP) organises a program around objects — self-contained entities that combine data (attributes) and behaviour (methods). Python supports all four pillars: encapsulation, abstraction, inheritance and polymorphism.
Class: a blueprint or template that defines attributes and methods.
Object (instance): a concrete entity created from a class, with its own copy of instance attributes.
self: the first parameter of every instance method; it refers to the object on which the method was called.
class ClassName: def __init__(self, ...): self.attribute = value def method(self, ...):
class Student:
"""Represents a student with a name and marks."""
def __init__(self, name, marks): # constructor
self.name = name # instance attribute
self.marks = marks
def average(self): # instance method
return sum(self.marks) / len(self.marks)
def display(self):
print(f"{self.name}: average = {self.average():.2f}")
# Creating objects
s1 = Student("Aarav", [88, 92, 79])
s2 = Student("Diya", [95, 81, 90])
s1.display() # Aarav: average = 86.33
s2.display() # Diya: average = 88.67
print(s1.name) # accessing an attribute -> Aarav
__init__ Constructor__init__ is a special (dunder) method that runs automatically when an object is created. It initialises instance attributes. It does not return a value.
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def distance_from_origin(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
def __str__(self): # string representation
return f"Point({self.x}, {self.y})"
p = Point(3, 4)
print(p) # Point(3, 4)
print(p.distance_from_origin()) # 5.0
| Aspect | Instance variable | Class variable |
|---|---|---|
| Defined | Inside __init__ with self.x | Inside the class body, outside any method |
| Scope | Unique to each object | Shared by all objects |
| Access | obj.x | ClassName.x or obj.x |
| Use | Per-object state (name, roll no) | Common constants or counters |
class Employee:
company = "LPU Tech" # class variable (shared)
def __init__(self, name):
self.name = name # instance variable (per object)
e1 = Employee("Aarav")
e2 = Employee("Diya")
print(e1.company, e2.company) # LPU Tech LPU Tech
Employee.company = "LPU Innovations"
print(e1.company) # LPU Innovations (both changed)
| Type | Decorator | First parameter | Purpose |
|---|---|---|---|
| Instance method | none | self | Operates on a specific object |
| Class method | @classmethod | cls | Operates on the class itself (alternate constructors) |
| Static method | @staticmethod | none | Utility function logically belonging to the class |
class Circle:
pi = 3.14159
def __init__(self, radius):
self.radius = radius
def area(self): # instance method
return Circle.pi * self.radius ** 2
@classmethod
def from_diameter(cls, diameter): # alternate constructor
return cls(diameter / 2)
@staticmethod
def is_valid_radius(r): # utility
return r > 0
c = Circle.from_diameter(10)
print(c.radius) # 5.0
print(Circle.is_valid_radius(3)) # True
Encapsulation bundles data and the methods that operate on it, and restricts direct access from outside. Python uses naming conventions rather than strict keywords.
| Convention | Meaning | Access |
|---|---|---|
name | Public | Freely accessible |
_name | Protected (by convention) | Accessible, but “internal use only” |
__name | Private | Name-mangled to _ClassName__name |
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance # private attribute
def deposit(self, amount):
if amount > 0:
self.__balance += amount
return True
return False
def withdraw(self, amount):
if 0 < amount <= self.__balance:
self.__balance -= amount
return True
return False
def get_balance(self): # controlled accessor
return self.__balance
acc = BankAccount("Aarav", 5000)
acc.deposit(2500)
acc.withdraw(1000)
print(acc.get_balance()) # 6500
# print(acc.__balance) # AttributeError
print(acc._BankAccount__balance) # 6500 - name mangling still allows access
Key idea: data hiding prevents accidental corruption; access is only through validated methods.
Inheritance lets a class (child/derived) acquire the attributes and methods of another class (parent/base), promoting code reuse and an “is-a” relationship.
class Child(Parent): … • super().__init__(...) calls the parent constructor
| Type | Structure | Example |
|---|---|---|
| Single | A → B | class Dog(Animal) |
| Multilevel | A → B → C | Vehicle → Car → ElectricCar |
| Hierarchical | One parent, many children | Animal → Dog, Cat, Cow |
| Multiple | Two or more parents | class C(A, B) |
| Hybrid | Combination of the above | Diamond-shaped hierarchies |
super()class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "Some generic sound"
def info(self):
print(f"I am {self.name}")
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # call parent constructor
self.breed = breed
def speak(self): # override
return "Woof!"
d = Dog("Bruno", "Labrador")
d.info() # I am Bruno
print(d.speak()) # Woof!
print(isinstance(d, Animal)) # True
class Vehicle:
def __init__(self, brand):
self.brand = brand
def describe(self):
print(f"Brand: {self.brand}")
class Car(Vehicle):
def __init__(self, brand, seats):
super().__init__(brand)
self.seats = seats
def describe(self):
super().describe()
print(f"Seats: {self.seats}")
class ElectricCar(Car):
def __init__(self, brand, seats, range_km):
super().__init__(brand, seats)
self.range_km = range_km
def describe(self):
super().describe()
print(f"Range: {self.range_km} km")
e = ElectricCar("Tesla", 5, 500)
e.describe()
Brand: Tesla
Seats: 5
Range: 500 km
class Flyer:
def move(self):
return "flies"
class Swimmer:
def move(self):
return "swims"
class Duck(Flyer, Swimmer):
pass
d = Duck()
print(d.move()) # flies (Flyer comes first in MRO)
print(Duck.__mro__) # (Duck, Flyer, Swimmer, object)
The MRO (Method Resolution Order) decides which parent's method is used when several define the same name. It is computed by the C3 linearisation algorithm.
Polymorphism means “many forms” — the same interface behaves differently for different types.
class Shape:
def area(self):
raise NotImplementedError
class Circle(Shape):
def __init__(self, r): self.r = r
def area(self): return 3.14159 * self.r ** 2
class Rectangle(Shape):
def __init__(self, l, b): self.l, self.b = l, b
def area(self): return self.l * self.b
for shape in (Circle(5), Rectangle(4, 6)):
print(f"{shape.__class__.__name__}: {shape.area():.2f}")
Circle: 78.54
Rectangle: 24.00
Dunder methods let user-defined objects respond to built-in operators.
| Operator | Dunder method |
|---|---|
+ | __add__(self, other) |
- | __sub__(self, other) |
* | __mul__(self, other) |
== | __eq__(self, other) |
< | __lt__(self, other) |
len() | __len__(self) |
print() | __str__(self) |
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __str__(self):
return f"Vector({self.x}, {self.y})"
v = Vector(1, 2) + Vector(3, 4)
print(v) # Vector(4, 6)
from abc import ABC, abstractmethod
class Payment(ABC):
@abstractmethod
def pay(self, amount):
pass
class CreditCard(Payment):
def pay(self, amount):
print(f"Paid Rs.{amount} by credit card")
class UPI(Payment):
def pay(self, amount):
print(f"Paid Rs.{amount} via UPI")
for method in (CreditCard(), UPI()):
method.pay(1500)
An abstract class cannot be instantiated; it only declares the interface that subclasses must implement.
Four pillars — Encapsulation (bundling + hiding), Abstraction (hiding complexity), Inheritance (reuse via “is-a”), Polymorphism (same call, different behaviour). Be ready to define each and give one code example; this is a very common 5-mark question.
Files provide persistent storage: data survives after the program terminates. Python's built-in open() function returns a file object used for reading and writing.
file_object = open("filename", "mode") … file_object.close()
| Mode | Meaning | File exists | File missing | Position |
|---|---|---|---|---|
'r' | Read only (default) | Reads from start | FileNotFoundError | Beginning |
'w' | Write only | Truncates to empty | Creates new file | Beginning |
'a' | Append | Adds at end | Creates new file | End |
'r+' | Read and write | Yes | FileNotFoundError | Beginning |
'w+' | Write and read | Truncates | Creates new file | Beginning |
'a+' | Append and read | Yes | Creates new file | End |
'rb' | Read binary | Yes | FileNotFoundError | Beginning |
'wb' | Write binary | Truncates | Creates new file | Beginning |
'ab' | Append binary | Adds at end | Creates new file | End |
Opening an existing file in 'w' mode erases all its contents immediately, even before you write anything. Use 'a' when you want to preserve existing data.
| Method | Returns |
|---|---|
read() | Entire file contents as one string |
read(n) | Next n characters |
readline() | One line (including the trailing \n) |
readlines() | List of all lines |
# Method 1: read everything
f = open("data.txt", "r")
content = f.read()
print(content)
f.close()
# Method 2: line by line (memory-efficient) - recommended
with open("data.txt", "r") as f:
for line in f:
print(line.rstrip()) # rstrip() removes the newline
# Method 3: readlines()
with open("data.txt", "r") as f:
lines = f.readlines()
print(len(lines), "lines")
# Write (overwrites)
with open("output.txt", "w") as f:
f.write("First line\n")
f.write("Second line\n")
f.writelines(["Third\n", "Fourth\n"])
# Append
with open("output.txt", "a") as f:
f.write("Fifth line (appended)\n")
with
The with statement (context manager) closes the file automatically, even if an exception occurs. It is the recommended style and saves marks in exams.
tell() and seek()with open("data.txt", "r") as f:
print(f.tell()) # 0 - at the start
first = f.read(5)
print(f.tell()) # 5 - after reading 5 characters
f.seek(0) # move back to the beginning
print(f.read(5)) # reads the same first 5 characters
pickle ModuleBinary files store data in bytes. Python's pickle module serialises (converts) Python objects into a byte stream and back.
| Function | Purpose |
|---|---|
pickle.dump(obj, file) | Write a Python object to a binary file |
pickle.load(file) | Read a Python object from a binary file |
import pickle
# --- Writing records ---
records = [("Aarav", 101), ("Diya", 102), ("Kabir", 103)]
with open("students.dat", "wb") as f:
pickle.dump(records, f)
# --- Searching for a roll number ---
roll_to_find = int(input("Enter roll number to search: "))
with open("students.dat", "rb") as f:
data = pickle.load(f)
found = False
for name, roll in data:
if roll == roll_to_find:
print("Name:", name)
found = True
break
if not found:
print("Roll number not found")
with open("sample.txt", "r") as f:
for line_no, line in enumerate(f, start=1):
print(f"{line_no}: {line.rstrip()}")
vowels = consonants = upper = lower = 0
with open("sample.txt", "r") as f:
text = f.read()
for ch in text:
if ch.isupper():
upper += 1
if ch.islower():
lower += 1
if ch.isalpha():
if ch.lower() in "aeiou":
vowels += 1
else:
consonants += 1
print("Vowels :", vowels)
print("Consonants :", consonants)
print("Uppercase :", upper)
print("Lowercase :", lower)
with open("source.txt", "r") as fin, open("target.txt", "w") as fout:
for line in fin:
if "a" not in line:
fout.write(line)
print("Lines containing 'a' were removed.")
with open("sample.txt", "r") as f:
for line in f:
words = line.split()
print("#".join(words))
freq = {}
with open("phishing.txt", "r") as f:
for line in f:
for word in line.lower().split():
word = word.strip(".,!?;:()\"'")
if word:
freq[word] = freq.get(word, 0) + 1
most_common = max(freq, key=freq.get)
print("Most common word:", most_common, "->", freq[most_common])
Always guard file operations when the file may be missing:
try:
with open("data.txt", "r") as f:
print(f.read())
except FileNotFoundError:
print("The file does not exist.")
except PermissionError:
print("Access denied.")
finally:
print("Operation attempted.")
A regular expression (regex) is a sequence of characters that defines a search pattern. Python's re module provides the tools to search, match, extract and replace text using these patterns.
import re
| Symbol | Meaning | Example | Matches |
|---|---|---|---|
. | Any character except newline | a.c | abc, a c, a9c |
^ | Start of string | ^Hello | Hello world |
$ | End of string | end$ | the end |
* | Zero or more repetitions | ab* | a, ab, abbb |
+ | One or more repetitions | ab+ | ab, abbb (not a) |
? | Zero or one (optional) | colou?r | color, colour |
{n} | Exactly n repetitions | \d{4} | 2024 |
{n,m} | Between n and m repetitions | \d{2,4} | 12, 1234 |
[] | Character class | [aeiou] | any vowel |
| | Alternation (OR) | cat|dog | cat or dog |
() | Grouping / capture | (ab)+ | ab, abab |
\ | Escape a metacharacter | \. | a literal dot |
| Sequence | Matches | Opposite |
|---|---|---|
\d | Any digit [0-9] | \D — non-digit |
\w | Word character [A-Za-z0-9_] | \W — non-word |
\s | Whitespace (space, tab, newline) | \S — non-whitespace |
\b | Word boundary | \B — non-boundary |
\A / \Z | Start / end of the entire string | — |
Write patterns as raw strings: r"\d{3}" rather than "\\d{3}". The r prefix stops Python from interpreting backslash escapes before re sees them.
re Module Functions| Function | Purpose | Returns |
|---|---|---|
re.match(p, s) | Match only at the beginning of the string | Match object or None |
re.search(p, s) | Find the first occurrence anywhere | Match object or None |
re.findall(p, s) | All non-overlapping matches | List of strings (or tuples) |
re.finditer(p, s) | All matches as an iterator | Iterator of match objects |
re.sub(p, repl, s) | Replace matches | New string |
re.split(p, s) | Split by pattern | List of strings |
re.compile(p) | Pre-compile a pattern for reuse | Pattern object |
re.match("bc", "abcde") returns None because the pattern must start at index 0. re.search("bc", "abcde") succeeds because it scans the whole string.
| Method | Returns |
|---|---|
group() | The matched substring |
group(n) | The n-th captured group |
groups() | Tuple of all captured groups |
start() / end() | Start / end index of the match |
span() | Tuple (start, end) |
import re
text = "My phone number is 9876543210 and office is 011-2345678"
print(re.findall(r"\d{10}", text))
# ['9876543210']
print(re.search(r"\d+", text).group())
# 9876543210
print(re.match(r"My", text) is not None) # True
print(re.match(r"phone", text) is None) # True (not at the start)
import re
pattern = r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
emails = ["aarav@lpu.in", "bad@@mail", "diya.s@co.org", "no-at-sign.com"]
for e in emails:
status = "Valid" if re.match(pattern, e) else "Invalid"
print(f"{e:<20} {status}")
aarav@lpu.in Valid
bad@@mail Invalid
diya.s@co.org Valid
no-at-sign.com Invalid
import re
pattern = r"^[6-9]\d{9}$" # starts with 6-9, then exactly 9 digits
for number in ["9876543210", "1234567890", "98123", "9999999999"]:
print(number, "->", bool(re.match(pattern, number)))
9876543210 -> True
1234567890 -> False
98123 -> False
9999999999 -> True
import re
log = "2024-08-15 10:32:45 ERROR Disk full"
pattern = r"(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2}):(\d{2})\s+(\w+)"
m = re.search(pattern, log)
if m:
year, month, day, hh, mm, ss, level = m.groups()
print("Date :", f"{day}-{month}-{year}")
print("Time :", f"{hh}:{mm}:{ss}")
print("Level:", level)
Date : 15-08-2024
Time : 10:32:45
Level: ERROR
import re
text = "Python is fun"
# Collapse multiple spaces into one
clean = re.sub(r"\s+", " ", text)
print(clean) # Python is fun
# Mask all digits
print(re.sub(r"\d", "#", "PIN is 4321")) # PIN is ####
# Split on any non-alphanumeric run
print(re.split(r"\W+", "a,b;c d")) # ['a', 'b', 'c', 'd']
import re
from collections import Counter
with open("sample.txt", "r") as f:
text = f.read().lower()
words = re.findall(r"\b[a-z]+\b", text) # only alphabetic words
common = Counter(words).most_common(5)
for word, count in common:
print(f"{word}: {count}")
For pattern-matching questions, always state the pattern in a raw string and briefly explain each token. Common exam patterns: email, mobile number, PIN code, date (dd/mm/yyyy), and password strength. Memorise \d, \w, \s, +, *, {n,m} and anchors ^ $.
CO4 requires you to “store, process and sort the data”. Python provides built-in sorting tools, and understanding the underlying algorithms is frequently examined.
sort() versus sorted()| Aspect | list.sort() | sorted(iterable) |
|---|---|---|
| Returns | None (sorts in place) | A new sorted list |
| Original changed? | Yes | No |
| Works on | Lists only | Any iterable |
| Example | L.sort() | new = sorted(L) |
key and reversewords = ["banana", "apple", "cherry"]
print(sorted(words)) # alphabetical
print(sorted(words, key=len)) # by length
print(sorted(words, reverse=True)) # descending
students = [("Aarav", 88), ("Diya", 95), ("Kabir", 72)]
print(sorted(students, key=lambda s: s[1])) # by marks asc
print(sorted(students, key=lambda s: s[1], reverse=True)) # desc
records = [{"name": "A", "cgpa": 8.7},
{"name": "B", "cgpa": 9.2},
{"name": "C", "cgpa": 8.1}]
records.sort(key=lambda r: r["cgpa"])
print(records)
Python's sort is stable: items that compare equal keep their original relative order. This makes multi-key sorting possible — sort by the secondary key first, then by the primary key.
Repeatedly compare adjacent elements and swap them if they are out of order.
Best \(O(n)\) (already sorted, with flag) • Average / Worst \(O(n^{2})\) • Space \(O(1)\) • Stable: Yes
def bubble_sort(arr):
n = len(arr)
for i in range(n - 1):
swapped = False
for j in range(n - 1 - i):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
swapped = True
if not swapped: # already sorted - early exit
break
return arr
print(bubble_sort([64, 34, 25, 12, 22, 11, 90]))
# [11, 12, 22, 25, 34, 64, 90]
def selection_sort(arr):
n = len(arr)
for i in range(n - 1):
min_idx = i
for j in range(i + 1, n):
if arr[j] < arr[min_idx]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i]
return arr
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
return arr
| Algorithm | Best | Average | Worst | Space | Stable |
|---|---|---|---|---|---|
| Bubble Sort | \(O(n)\) | \(O(n^2)\) | \(O(n^2)\) | \(O(1)\) | Yes |
| Selection Sort | \(O(n^2)\) | \(O(n^2)\) | \(O(n^2)\) | \(O(1)\) | No |
| Insertion Sort | \(O(n)\) | \(O(n^2)\) | \(O(n^2)\) | \(O(1)\) | Yes |
| Merge Sort | \(O(n\log n)\) | \(O(n\log n)\) | \(O(n\log n)\) | \(O(n)\) | Yes |
Tim Sort (sorted) | \(O(n)\) | \(O(n\log n)\) | \(O(n\log n)\) | \(O(n)\) | Yes |
def linear_search(arr, target):
for i, value in enumerate(arr):
if value == target:
return i
return -1
print(linear_search([4, 9, 2, 7], 7)) # 3
def binary_search(arr, target):
low, high = 0, len(arr) - 1
while low <= high:
mid = (low + high) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
low = mid + 1
else:
high = mid - 1
return -1
data = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]
print(binary_search(data, 23)) # 5
students = [
("Aarav", "CSE", 88),
("Diya", "ECE", 95),
("Kabir", "CSE", 95),
("Meera", "ECE", 88),
]
# Secondary key first: name ascending
students.sort(key=lambda s: s[0])
# Primary key: marks descending (stable keeps name order within equal marks)
students.sort(key=lambda s: s[2], reverse=True)
for name, branch, marks in students:
print(f"{name:<7} {branch} {marks}")
Diya ECE 95
Kabir CSE 95
Aarav CSE 88
Meera ECE 88
| Function | Purpose | Example → Result |
|---|---|---|
print() | Display output | print("hi") |
input() | Read a string from the user | input("Name: ") |
len(x) | Number of items / characters | len("abc") → 3 |
type(x) | Data type of an object | type(3.0) → float |
int/float/str | Type conversion | int("7") → 7 |
range() | Sequence of integers | list(range(3)) → [0,1,2] |
sum(), min(), max() | Aggregate values | sum([1,2,3]) → 6 |
sorted(), reversed() | Return sorted / reversed iterator | sorted([3,1]) → [1,3] |
enumerate() | Index + value pairs | list(enumerate("ab")) |
zip() | Pair elements of iterables | list(zip([1,2],"ab")) |
abs(), round(), pow() | Numeric utilities | round(3.456,1) → 3.5 |
isinstance(o, T) | Type check | isinstance(5, int) → True |
| Structure | Syntax | Ordered | Mutable | Duplicates | Indexed |
|---|---|---|---|---|---|
| String | "abc" | Yes | No | Yes | Yes |
| List | [1,2] | Yes | Yes | Yes | Yes |
| Tuple | (1,2) | Yes | No | Yes | Yes |
| Set | {1,2} | No | Yes | No | No |
| Dictionary | {"a":1} | Yes (3.7+) | Yes | Keys unique | By key |
| Concept | Syntax |
|---|---|
| Conditional | if c1: ... elif c2: ... else: ... |
| Ternary | A if cond else B |
| While loop | while cond: body |
| For loop | for x in iterable: body |
| Loop control | break / continue / pass / loop else |
| Function | def f(a, b=1, *args, **kwargs): return value |
| Lambda | f = lambda x: x * 2 |
| Class | class C(Base): def __init__(self): ... |
| Inheritance | super().__init__(...) |
| File open | with open("f.txt", "r") as f: |
| Regex | re.findall(r"\d+", text) |
| List comprehension | [f(x) for x in it if cond] |
| Dictionary comprehension | {k: v for k, v in items} |
| Exception handling | try: ... except E: ... else: ... finally: ... |
| Operation | List | Dict / Set |
|---|---|---|
| Index / key access | \(O(1)\) | \(O(1)\) average |
Search (in) | \(O(n)\) | \(O(1)\) average |
| Insert at end | \(O(1)\) amortised | \(O(1)\) average |
| Insert at front | \(O(n)\) | — |
| Delete | \(O(n)\) | \(O(1)\) average |
| Sort | \(O(n\log n)\) | — |
| Exception | Typical cause |
|---|---|
SyntaxError | Missing colon, unbalanced parentheses, bad indentation structure |
IndentationError | Inconsistent indentation |
NameError | Using an undefined variable |
TypeError | Unsupported operation between types, e.g. "1" + 1 |
ValueError | Right type, wrong value, e.g. int("abc") |
IndexError | Sequence index out of range |
KeyError | Missing dictionary key |
ZeroDivisionError | Division by zero |
AttributeError | Calling a method the object does not have |
FileNotFoundError | Opening a non-existent file in read mode |
RecursionError | Recursion depth limit (1000) exceeded |
UnboundLocalError | Reading a local variable before assignment |
if x > 0:, for i in range(5):, def f():, class C:, else:, try:.input() returns a string. Wrap it with int() or float() when arithmetic is required.range(1, n) runs from 1 to n−1; use range(1, n+1) for 1…n.s[0] = 'X'; build a new object instead.= is assignment, == is comparison. Confusing them is the most common logic error in if statements.with. Examiners award marks for resource management.for loop, and then again using recursion.MediumBankAccount with private balance, and methods deposit(), withdraw() and get_balance(). Demonstrate encapsulation.Medium+91-XXXXXXXXXX from a text block.Hardn = 4729.Mediumpush, pop, peek and is_empty operations, and demonstrate underflow handling.Harda = int(input("Enter first number : "))
b = int(input("Enter second number: "))
print("Addition :", a + b)
print("Subtraction :", a - b)
print("Multiplication :", a * b)
print("Division :", a / b)
print("Floor Division :", a // b)
print("Modulus :", a % b)
print("Exponent :", a ** b)
Sample (a=7, b=2): 9, 5, 14, 3.5, 3, 1, 49.
n = int(input("Enter a number: "))
if n < 2:
print(n, "is not a perfect number")
else:
divisor_sum = 0
for i in range(1, n):
if n % i == 0:
divisor_sum += i
if divisor_sum == n:
print(n, "is a perfect number")
else:
print(n, "is not a perfect number")
Trace for n = 28: divisors 1, 2, 4, 7, 14 → sum = 28 → perfect. For n = 12: 1+2+3+4+6 = 16 ≠ 12 → not perfect.
n = int(input("Enter a number: "))
num_digits = len(str(n))
temp = n
total = 0
while temp > 0:
digit = temp % 10
total += digit ** num_digits
temp //= 10
if total == n:
print(n, "is an Armstrong number")
else:
print(n, "is not an Armstrong number")
Trace for n = 153: digits = 3. 3³ = 27, 5³ = 125, 1³ = 1 → 27+125+1 = 153 → Armstrong.
# --- Iterative version ---
def fib_iter(n):
a, b = 0, 1
for _ in range(n):
print(a, end=" ")
a, b = b, a + b
print()
# --- Recursive version ---
def fib_rec(n):
if n <= 1:
return n
return fib_rec(n - 1) + fib_rec(n - 2)
terms = int(input("How many terms? "))
fib_iter(terms)
print("Recursive:", end=" ")
for i in range(terms):
print(fib_rec(i), end=" ")
print()
For terms = 8: 0 1 1 2 3 5 8 13 from both versions. The iterative version is \(O(n)\); the naive recursive version is \(O(2^n)\).
s = input("Enter a string: ").lower()
reversed_s = ""
for ch in s:
reversed_s = ch + reversed_s
if s == reversed_s:
print("Palindrome")
else:
print("Not a palindrome")
Trace for "madam": reversed_s builds m → am → dam → adam → madam. Equal → Palindrome.
vowels = consonants = upper = lower = digits = 0
vowel_set = set("aeiouAEIOU")
with open("sample.txt", "r") as f:
for line in f:
for ch in line:
if ch.isdigit():
digits += 1
if ch.isalpha():
if ch in vowel_set:
vowels += 1
else:
consonants += 1
if ch.isupper():
upper += 1
else:
lower += 1
print("Vowels :", vowels)
print("Consonants :", consonants)
print("Uppercase :", upper)
print("Lowercase :", lower)
print("Digits :", digits)
import pickle
def write_records(filename):
n = int(input("How many records? "))
records = []
for i in range(n):
name = input(f"Name {i+1}: ")
roll = int(input(f"Roll {i+1}: "))
records.append((roll, name))
with open(filename, "wb") as f:
pickle.dump(records, f)
print("Records saved.")
def search_record(filename, target_roll):
try:
with open(filename, "rb") as f:
records = pickle.load(f)
except FileNotFoundError:
print("File not found.")
return
for roll, name in records:
if roll == target_roll:
print(f"Roll {roll} -> {name}")
return
print(f"Roll number {target_roll} not found.")
write_records("students.dat")
search_record("students.dat", int(input("Search roll number: ")))
Key points: binary mode 'wb' / 'rb', pickle.dump / pickle.load, and a clear “not found” message.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance # private
def deposit(self, amount):
if amount <= 0:
print("Deposit amount must be positive.")
return
self.__balance += amount
print(f"Deposited Rs.{amount}. Balance = Rs.{self.__balance}")
def withdraw(self, amount):
if amount <= 0:
print("Withdrawal amount must be positive.")
elif amount > self.__balance:
print("Insufficient balance.")
else:
self.__balance -= amount
print(f"Withdrew Rs.{amount}. Balance = Rs.{self.__balance}")
def get_balance(self):
return self.__balance
acc = BankAccount("Aarav", 5000)
acc.deposit(2500)
acc.withdraw(1000)
acc.withdraw(100000)
print("Final balance:", acc.get_balance())
Encapsulation demonstrated: __balance is inaccessible as acc.__balance; all modification goes through validated methods.
import re
text = """
Contact aarav.sharma@lpu.in or diya@example.org for details.
Call +91-9876543210 or +91-9123456789 between 10am and 5pm.
Invalid: bad@@mail, +91-12345
"""
# --- Email validation ---
email_pattern = r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}"
emails = re.findall(email_pattern, text)
print("Emails found:", emails)
# --- Phone number extraction ---
phone_pattern = r"\+91-\d{10}"
phones = re.findall(phone_pattern, text)
print("Phone numbers:", phones)
# --- Validate one email strictly ---
candidate = "aarav.sharma@lpu.in"
strict = r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
print("Valid email?", bool(re.match(strict, candidate)))
Emails found: ['aarav.sharma@lpu.in', 'diya@example.org']
Phone numbers: ['+91-9876543210', '+91-9123456789']
Valid email? True
numbers = []
for i in range(10):
numbers.append(int(input(f"Enter number {i+1}: ")))
unique = list(set(numbers)) # remove duplicates
unique.sort(reverse=True) # descending order
print("Sorted unique list:", unique)
if len(unique) >= 2:
print("Second largest:", unique[1])
else:
print("Not enough distinct elements.")
Note: using set() loses the original order, which does not matter here because the list is sorted afterwards. If order preservation is required, use the loop-and-check method.
def digit_sum(n):
if n == 0:
return 0
return n % 10 + digit_sum(n // 10)
print(digit_sum(4729)) # 22
Recursion trace for n = 4729:
digit_sum(4729)
= 9 + digit_sum(472)
= 9 + (2 + digit_sum(47))
= 9 + (2 + (7 + digit_sum(4)))
= 9 + (2 + (7 + (4 + digit_sum(0))))
= 9 + 2 + 7 + 4 + 0
= 22
class Stack:
def __init__(self):
self.items = []
def is_empty(self):
return len(self.items) == 0
def push(self, item):
self.items.append(item)
print(f"Pushed: {item}")
def pop(self):
if self.is_empty():
print("Stack underflow - nothing to pop.")
return None
return self.items.pop()
def peek(self):
if self.is_empty():
print("Stack is empty.")
return None
return self.items[-1]
def size(self):
return len(self.items)
s = Stack()
s.push(10); s.push(20); s.push(30)
print("Top element:", s.peek()) # 30
print("Popped :", s.pop()) # 30
print("Size :", s.size()) # 2
s.pop(); s.pop()
print("Popped from empty:", s.pop()) # underflow message
| Code | Title | Author | Publisher |
|---|---|---|---|
| T-1 | Fundamentals of Python — First Programs | Kenneth A. Lambert | Cengage Learning |
| R-1 | Python Programming: Using Problem Solving Approach | Reema Thareja | Oxford University Press |
| Code | Resource | Purpose |
|---|---|---|
| RW-1 | datacamp.com — Intro to Python for Data Science | Guided Python course |
| RW-2 | w3schools.com/python/python_tuples.asp | Tuples and sequence reference |
| RW-3 | coursera.org/learn/python | Python from basics |
| AV-1 | nptel.ac.in/courses/106106145 | Video lectures on Python |
| SW-1 | python.org/downloads | Python interpreter |
| SW-2 | anaconda.org/anaconda/python | Anaconda distribution |
NameError.** is right-associative, and parentheses always take priority.if-elif-else selects exactly one branch; conditions should be ordered from most to least restrictive.for iterates over collections, while repeats on a condition; break, continue and loop else control the flow.*args/**kwargs; recursion needs a base case and consumes stack space.open() with modes, and with guarantees closure; pickle handles binary object storage.re module provide powerful pattern matching through metacharacters, character classes, quantifiers and groups.| Course Outcome | Covered in Sections | Key Deliverables |
|---|---|---|
| CO1 — Installation and basics | I, II | Installation steps, REPL vs script mode, variables, types, I/O |
| CO2 — Conditionals and iteration | III, IV, V | if-elif-else, ternary, while, for, nested loops, break/continue, random |
| CO3 — Functions and recursion | XI | def, parameters, arguments, scope, lambda, recursion with base case |
| CO4 — Core data structures | VI, VII, VIII, IX, X, XV | Strings, lists, tuples, dictionaries, sets, sorting and searching |
| CO5 — Object-oriented programming | XII | Classes, objects, encapsulation, inheritance, polymorphism, abstraction |
| CO6 — File handling and regex | XIII, XIV | Text/binary files, pickle, re patterns, validation and extraction |
| Practical | Program | Covered in |
|---|---|---|
| 1 | Arithmetic operations on two numbers | Solution 1, §III |
| 2 | Perfect number check | Example 5.12, Solution 2 |
| 3 | Armstrong number check | Example 5.13, Solution 3 |
| 4 | Factorial of a number | Example 11.1 |
| 5 | Fibonacci series | Example 5.11, Solution 4 |
| 6 | Palindrome using a loop | Example 6.2, Solution 5 |
| 7 | Factorial using recursion | Example 11.1 |
| 8 | Count vowels/consonants/upper/lower in a file | Example 13.3, Solution 6 |
| 9 | Binary file with name and roll; search | Example 13.1, Solution 7 |
| 10 | Read a file line by line and print it | Example 13.2 |
| 11 | Remove lines containing 'a' into another file | Example 13.4 |
| 12 | Random dice simulator (1–6) | Example 5.8 |
| 13 | Stack using a list | Example 7.5, Solution 12 |
| 14 | Most common word in a phishing email file | Example 13.6, Example 14.6 |
| 15 | Print each word separated by '#' | Example 13.5 |
Course weightage: ATT 5 + CA 50 + ETP 45. Programming Practice requires solving at least 50% of the assigned coding problems and 50% of the MCQs to be eligible for marks, which are then prorated against the proctored coding contests.
Python Programming · Complete Course Notes
INT108 · L:T:P 3:0:2 · 4 Credits
“First, solve the problem. Then, write the code.” — John Johnson