0% found this document useful (0 votes)
4 views62 pages

Python Complete Final

The document is a comprehensive guide to Python programming, covering basics to advanced topics including data types, operators, control flow, and functions. It is structured into chapters with clear explanations, code examples, and tips for effective coding practices. The guide is designed for learners at IIIT Hyderabad and aims to provide a thorough understanding of Python and its applications.

Uploaded by

anumayraiint
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views62 pages

Python Complete Final

The document is a comprehensive guide to Python programming, covering basics to advanced topics including data types, operators, control flow, and functions. It is structured into chapters with clear explanations, code examples, and tips for effective coding practices. The guide is designed for learners at IIIT Hyderabad and aims to provide a thorough understanding of Python and its applications.

Uploaded by

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

Python Complete Guide — Anumay Rai | IIIT Hyderabad

PYTHON
Complete Study Guide
Basics → Operators → Control Flow → Functions → Data Structures → Advanced

Anumay Rai | IIIT Hyderabad | SPCRC Lab | 2026

📌 How to Use This Guide


Each concept: What is it → Why use it → Code with explanations → Key rules.

💡 ⚠️ 📝 🔨 📌 Info
Comments in code are BOLD GREEN to stand out from the actual code.
Boxes: Tip (green) | Warning (red) | Note (yellow) | Project (purple) |
(teal).
Chapters 1–15: Python fundamentals. Chapters 16–30: Advanced & ML-relevant topics.

Page 1
🐍 Chapter 1: How Python Works
Python Complete Guide — Anumay Rai | IIIT Hyderabad

The foundation — before you write a single line

1.1 What Python Is


Python is a high-level, interpreted language. High-level means it reads almost like English — no
memory pointers, no type declarations. Interpreted means Python reads and runs your code line by line
through the Python Interpreter — unlike C which compiles everything first.

Term Plain meaning


Source code (.py) The text file you write
Interpreter The program that reads your .py file and runs it line by line
REPL Interactive shell — type python3 in terminal, test code instantly
Indentation Spaces at start of lines define code blocks — Python uses these
instead of { }

⚠️ Indentation is NOT optional


Python uses indentation (spaces) to define blocks. Get it wrong → IndentationError.
Standard: 4 spaces per level. Never mix tabs and spaces.
if True: ← colon opens a block
print('hi') ← indented block

1.2 Comments
Comments are text Python completely ignores. They exist only for humans reading the code. In all code
examples in this guide, comments are in bold green so they stand out clearly.

# Single-line comment — Python skips this entire line

x = 5 # inline comment after code on the same line

"""
Multi-line comment — technically a string literal that is never assigned.
Used for longer explanations or disabling multiple lines at once.
"""

# In your files, ## was used to temporarily disable code:


##a = int(input('Enter Number 1:')) # this line is completely ignored
##b = int(input('Enter Number 2:')) # so is this one

Page 2
Python Complete Guide — Anumay Rai | IIIT Hyderabad

1.3 print() — Displaying Output


print() sends values to the screen. You will use it constantly — for debugging, showing results, and
tracing program flow.

# Basic: print any value


print('Hello World') # Hello World
print(42) # 42
print(3.14) # 3.14

# Multiple items — Python puts a space between them by default


print('Age:', 20, 'City:', 'Lucknow') # Age: 20 City: Lucknow

# sep= controls the separator between items (default: single space)


print('A', 'B', 'C', sep='-') # A-B-C
print('A', 'B', 'C', sep='') # ABC (no separator at all)

# end= controls what is printed after the last item (default: newline \n)
print('Hello', end='') # Hello (cursor stays on same line, no newline)
print('Hello', end='!') # Hello!

# From your [Link] — print each character without newlines between them
for ch in 'Python':
print(ch, end='') # Python (all on one line)

Page 3
📦 Chapter 2: Variables and Data Types
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Storing information in Python

2.1 What is a Variable?


A variable is a named container for a value. You create one by writing name = value. Python
automatically detects the type — you never declare it.

# Python figures out the type automatically from the value you assign
age = 21 # whole number → int
price = 9.99 # decimal → float
name = 'Anumay' # text in quotes → str
is_student = True # True or False → bool
nothing = None # no value → NoneType

# type() tells you what type a variable is


print(type(age)) # <class 'int'>
print(type(price)) # <class 'float'>
print(type(name)) # <class 'str'>
print(type(is_student)) # <class 'bool'>
print(type(nothing)) # <class 'NoneType'>

Type Stores Example values


int Whole numbers 0, 25, -3, 1000
float Decimal numbers 3.14, 23.67, -0.5
str Text in quotes 'Hello', "Python", '123'
bool Only True or False True, False
NoneType No value / empty None

💡 Variable Naming Rules


CAN use: letters (a-z, A-Z), digits (0-9), underscore _
CANNOT start with a digit — 2name is invalid, name2 is fine
CASE-SENSITIVE: age, Age, AGE are three different variables
Convention: lowercase_with_underscores e.g. first_name, total_marks
Reserved words (if, for, while, print, etc.) cannot be variable names

2.2 Type Casting — Converting Between Types


Type casting means manually converting a value from one type to another. Python never does this
automatically — you must be explicit.

# From [Link] — all common type conversions:

# int → float: Python adds .0 to show it changed type

Page 4
Python Complete Guide — Anumay Rai | IIIT Hyderabad

int_value = 25
float_value = float(int_value)
print(float_value) # 25.0
print(type(float_value)) # <class 'float'>

# float → int: TRUNCATES — always drops the decimal, never rounds


float_value1 = 23.67
int_value1 = int(float_value1)
print(int_value1) # 23 ← .67 is dropped, NOT rounded to 24

# int → string: now you can concatenate it with other strings


int_value2 = 24
string_value = str(int_value2)
print(string_value) # '24' ← looks same but is now text, not a
number
print(type(string_value)) # <class 'str'>

# string → int: only works if the string contains PURE digits


string_value1 = '456'
int_value3 = int(string_value1)
print(int_value3) # 456 ← now you can do math with it

# bool conversions: True=1, False=0 in arithmetic


print(int(True)) # 1
print(int(False)) # 0
print(bool(0)) # False (0 is 'falsy' — treated as False)
print(bool(5)) # True (any non-zero number is 'truthy')
print(bool('')) # False (empty string is falsy)
print(bool('hi')) # True (non-empty string is truthy)

⚠️ Common type casting mistakes


int('23.67') → ERROR! Do: int(float('23.67')) — convert to float first, then int
int('hello') → ValueError — only digit strings work
int(23.99) → 23 not 24 — always truncates, never rounds

2.3 input() — Getting User Input


input() pauses the program and waits for the user to type something. It always returns a string — even
if the user types a number. You must convert it.

# From [Link] — your very first input program:


num1 = input('Enter a number') # user types 42, but num1 = '42' (a
string!)
num2 = input('Enter second number')

# Must convert to int before adding, otherwise '42' + '8' = '428' (string
concat!)
print('the sum is', int(num1) + int(num2))

# Cleaner: wrap input() in int() on the same line


user_age = int(input('User Age: ')) # reads string, immediately
converts
print('Age after 10 years:', user_age + 10) # now safe to do arithmetic

Page 5
Python Complete Guide — Anumay Rai | IIIT Hyderabad

# For decimal input use float() instead of int()


sub1 = float(input('Marks of Subject 1: ')) # e.g. user enters 87.5
km = float(input('Distance in km: '))
print('In metres:', km * 1000) # km is now a float, math works

2.4 id() — Memory Address


Every object in Python lives at a specific memory location. id() returns that address as a number. Mainly
used to understand how Python shares or copies objects.

# From Q2 [Link]
value1 = input('Enter a number: ')
value2 = input('Enter another number: ')
print(id(value1)) # prints a large number — the memory address of value1
print(id(value2)) # different address (separate variable, separate object)

# Interesting: Python caches small integers and short strings


value3 = 'Anumay'
value4 = 'Anumay'
print(id(value3) == id(value4)) # True — Python reused the same object!

Page 6
⚙️ Chapter 3: All Operators in Python
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Every way to compute, compare, and combine

3.1 Arithmetic Operators


Operator Name Example Result
a+b Addition 7+3 10
a-b Subtraction 7-3 4
a*b Multiplication 7*3 21
a/b Division 7/2 3.5 ← always float
a // b Floor Division 7 // 2 3 ← drops decimal
a%b Modulo (remainder) 7%3 1 ← remainder after division
a ** b Exponentiation 2 ** 3 8 ← 2 to the power 3

# From [Link]
a = int(input('Enter Number 1: ')) # say 10
b = int(input('Enter Number 2: ')) # say 3

print('Add:', a + b) # 13
print('Subtraction:', a - b) # 7
print('Multiplication:', a * b) # 30
print('Division:', a / b) # 3.3333... (always float — even 10/2
gives 5.0)
print('Floor Division:', a // b) # 3 (only the integer part)
print('Remainder:', a % b) # 1 (10 = 3×3 + 1, remainder is 1)
print('Exponential:', a ** b) # 1000 (10 to the power 3)

# BODMAS / PEDMAS — order of evaluation


print(2 + 3 * 4) # 14 (multiply first: 3×4=12, then 2+12=14)
print(18 // (3 + 3) * 2) # 6 (brackets: 3+3=6, then 18//6=3, then 3×2=6)
print(((2**3) * 2) % 5) # 1 (exponent: 2**3=8, ×2=16, 16%5=1)

3.2 Assignment Operators — Shorthand Updates


These update a variable in one step. x += 5 means x = x + 5. They exist for every arithmetic operator.

# From Assignment [Link] — every shorthand in action


x = 5; x += 5 # x = x + 5 → x is now 10
x = 5; x -= 5 # x = x - 5 → x is now 0
x = 5; x *= 5 # x = x × 5 → x is now 25
x = 5; x /= 5 # x = x / 5 → x is now 1.0 (note: result is float)
x = 5; x //= 5 # x = x // 5 → x is now 1 (floor division)
x = 5; x **= 5 # x = x ** 5 → x is now 3125 (5 to the power 5)
x = 5; x %= 5 # x = x % 5 → x is now 0 (5 divides evenly)

Page 7
Python Complete Guide — Anumay Rai | IIIT Hyderabad

3.3 Comparison Operators


Always return True or False. Used inside if statements and while loops.

a = 7; b = 3

print(a == b) # False (7 is not equal to 3)


print(a != b) # True (7 is different from 3)
print(a > b) # True (7 is greater than 3)
print(a < b) # False (7 is not less than 3)
print(a >= b) # True (7 is greater than or equal to 3)
print(a <= b) # False (7 is not less than or equal to 3)

3.4 Logical Operators — Combining Conditions


Operator Rule Example Result
and ALL conditions must be True True and False False
or AT LEAST ONE must be True True or False True
not Flips True→False, False→True not True False

# From [Link] — job eligibility (both age AND experience needed)


age = int(input('Enter your Age: '))
exp = int(input('Enter your Experience: '))

# 'and' means ALL three conditions must be True for the block to run
if age >= 30 and age <= 60 and exp >= 5:
print('Eligible for job')
else:
print('Not eligible')

# 'or' — only ONE branch needs to be true


typ = input('Type of Service (Manager/Clerk): ')
years = int(input('Years of Service: '))
if (typ == 'Manager' and years >= 4) or (typ == 'Clerk' and years >= 8):
print('Eligible for festival bonus')

3.5 Bitwise Operators


Bitwise operators work on the individual binary bits (0s and 1s) of integers.

# Python converts to binary, applies operation bit by bit, then converts back
a = 5 # binary: 101
b = 3 # binary: 011

# AND: 1 only if BOTH bits are 1


# 101 & 011 = 001 = 1

Page 8
Python Complete Guide — Anumay Rai | IIIT Hyderabad

print(a & b) # 1

# OR: 1 if at least ONE bit is 1


# 101 | 011 = 111 = 7
print(a | b) # 7

# XOR: 1 only if bits are DIFFERENT


# 101 ^ 011 = 110 = 6
print(a ^ b) # 6

# LEFT SHIFT: multiply by 2^n (shift bits left, fill right with zeros)
print(5 << 1) # 10 (5 × 2 = 10)
print(5 << 2) # 20 (5 × 4 = 20)

# RIGHT SHIFT: divide by 2^n


print(20 >> 1) # 10 (20 / 2 = 10)

# bin() converts a number to its binary string for inspection


print(bin(5)) # '0b101'
print(int('111', 2)) # 7 (convert binary string back to decimal)

3.6 Identity Operators — is / is not


is checks if two variables point to the exact same object in memory, not just equal values. Use it only
for None checks.

# From Identity [Link]


a = int(input('Enter Number 1: ')) # say 500
b = int(input('Enter Number 2: ')) # say 500

# Both have value 500, but are they the SAME object?
print(a is b) # False for large numbers — two different objects
print(a is not b) # True

# Python caches small integers -5 to 256, so for those 'is' returns True:
x = 5; y = 5
print(x is y) # True (same cached object)

# BEST USE: checking for None


result = None
print(result is None) # True — correct way to check
print(result is not None) # False
# Never write: if result == None — use 'is None' instead

3.7 Membership Operators — in / not in


Check whether a value exists inside a string, list, tuple, set, or dictionary.

# Works with strings: searches for the substring


sentence = 'I love Python'
print('Python' in sentence) # True

Page 9
Python Complete Guide — Anumay Rai | IIIT Hyderabad

print('Java' in sentence) # False

# Works with lists: checks for item by value


fruits = ['Apple', 'Mango', 'Grapes']
print('Apple' in fruits) # True
print('Lychee' not in fruits) # True

# Works with dicts: checks KEYS only (not values)


student = {'Name': 'Anumay', 'Age': 21}
print('Name' in student) # True — 'Name' is a key
print('Anumay' in student) # False — 'Anumay' is a value, not a key

Page 10
🔀 Chapter 4: Decision Making
Python Complete Guide — Anumay Rai | IIIT Hyderabad

if, elif, else, ternary, match-case

4.1 if Statement
# Runs the indented block ONLY if the condition is True
num = int(input('Enter a number: '))
if num > 0:
print('Number is positive') # only runs when num > 0 is True
# If num = -3: condition is False, block is skipped completely

4.2 if-else — Two Paths


# One path for True, another for False
age = int(input('Enter your age: '))
if age >= 18:
print('Eligible for vote') # True branch
else:
print('Not eligible') # False branch (age < 18)

4.3 if-elif-else — Multiple Paths


Python checks each condition in order. The first True one wins — the rest are skipped.

# From [Link] — age category classifier


age = int(input('Enter Age: '))

if age > 0 and age <= 12: # checked first


print('CHILD')
elif age >= 13 and age <= 19: # only checked if above was False
print('TEENAGER')
elif age >= 20 and age <= 60: # only checked if all above were False
print('ADULT')
else: # runs only when ALL conditions above are
False
print('SENIOR CITIZEN')

# Divisibility check from [Link]


num = int(input('Enter a Number: '))
if num % 3 == 0 and num % 5 == 0: # divisible by BOTH
print('Divisible by 3 and 5')
elif num % 3 == 0: # only divisible by 3
print('Divisible by 3')
elif num % 5 == 0: # only divisible by 5
print('Divisible by 5')
else:
print('Divisible by neither')

Page 11
Python Complete Guide — Anumay Rai | IIIT Hyderabad

4.4 Nested if-else — Conditions Inside Conditions


The inner condition is only reached if the outer condition was True.

# From [Link] — grading system


marks = int(input('Enter your marks: '))

if marks >= 33: # outer gate: did the student pass at all?
# Only if passed, determine the grade:
if marks >= 90: # inner condition 1
print('PASS — Grade A')
elif marks >= 75: # inner condition 2
print('PASS — Grade B')
elif marks >= 65: # inner condition 3
print('PASS — Grade C')
else: # 33–64: passed but lowest grade
print('PASS — Grade D')
else: # outer else: marks < 33
print('FAIL')

4.5 Ternary (One-line) if-else


Write a simple if-else in a single line. Syntax: value_if_true if condition else value_if_false

# From shorthand if [Link]


marks = int(input('Marks: '))
result = 'Pass' if marks >= 33 else 'Fail'
# Same as: if marks>=33: result='Pass' else: result='Fail' — but one line
print(result)

# Inline print: decide and print in one expression


num = int(input('Number: '))
print('Even' if num % 2 == 0 else 'Odd')

# Chained ternary from [Link]: three outcomes in one line


num = int(input('Enter a Number: '))
print('Positive' if num > 0 else ('Zero' if num == 0 else 'Negative'))
# Reads: if num>0 → 'Positive', else check if num==0 → 'Zero', else →
'Negative'

4.6 match-case — Pattern Matching (Python 3.10+)


match-case is cleaner than long if-elif chains when checking one variable against fixed known values.

# From Match [Link] — day of week


day = int(input('Enter day number 1-7: '))
match day:
case 1: print('SUNDAY')
case 2: print('MONDAY')
case 3: print('TUESDAY')

Page 12
Python Complete Guide — Anumay Rai | IIIT Hyderabad

case 4: print('WEDNESDAY')
case 5: print('THURSDAY')
case 6: print('FRIDAY')
case 7: print('SATURDAY')
case _: print('Invalid') # _ is the default — matches anything not
above

# Multiple values in one case using | (pipe)


char = input('Enter a letter: ')
match char:
case 'a' | 'e' | 'i' | 'o' | 'u': # any of these matches this case
print('Vowel')
case _:
print('Consonant')

Page 13
🔁 Chapter 5: Loops
Python Complete Guide — Anumay Rai | IIIT Hyderabad

for, while, break, continue, nested loops

5.1 for Loop


Use when you know how many times to repeat. The range() function generates a sequence of numbers.

# range(stop) → 0 to stop-1
for i in range(5):
print(i, end=' ') # 0 1 2 3 4 (stops BEFORE 5)

# range(start, stop) → start to stop-1


for i in range(1, 11):
print(i, end=' ') # 1 2 3 4 5 6 7 8 9 10

# range(start, stop, step) — jump by 'step' each time


for i in range(2, 20, 2):
print(i, end=' ') # 2 4 6 8 10 12 14 16 18 (even numbers)

# Reverse countdown with negative step


for i in range(10, 0, -1):
print(i, end=' ') # 10 9 8 7 6 5 4 3 2 1

# Looping over a string — ch takes each character one by one


for ch in 'Python':
print(ch, end='') # Python (no space between letters)

# From [Link] — print A to Z using chr()


for x in range(1, 27): # x = 1, 2, ... 26
print(chr(64 + x), end=' ') # 64+1=65=A, 64+2=66=B, ..., 64+26=90=Z

5.2 while Loop


Use when you don't know in advance how many iterations you need. Runs as long as the condition is
True.

# From [Link] — basic while


num = 1
while num <= 5: # keep running as long as num is 5 or less
print(num, end=' ')
num += 1 # MUST update num — otherwise condition stays True
forever!
# Output: 1 2 3 4 5

# Infinite loop with break — used when exit condition is complex


while True: # condition is always True — runs forever
user_inp = input('Type exit to stop: ')
if user_inp == 'exit':
break # immediately exits the while loop
print('You typed:', user_inp)

Page 14
Python Complete Guide — Anumay Rai | IIIT Hyderabad

⚠️ Infinite loop danger


If you forget num += 1, the condition never becomes False and the loop runs forever.
Press Ctrl+C in the terminal to force-stop a stuck program.
Always ask: what makes my while condition eventually become False?

5.3 break, continue, pass


Statement What it does Loop continues?
break Immediately EXIT the loop — no more No — loop ends
iterations
continue Skip rest of THIS iteration, go straight to the Yes — next iteration
next
pass Does nothing — placeholder when a block is Yes — continues normally
required but empty

# From [Link]

# break: stops the loop at 5


for x in range(1, 11):
if x == 5:
break # exits the for loop when x is 5
print(x, end=' ') # prints 1 2 3 4 only

# continue: skips 5 but keeps going


for x in range(1, 11):
if x == 5:
continue # skips print for x=5, jumps to x=6
print(x, end=' ') # prints 1 2 3 4 6 7 8 9 10 (5 is missing)

# pass: does nothing — loop runs completely normally


for i in range(11):
if i == 5:
pass # Python reads this and does absolutely nothing
print(i, end=' ') # prints ALL numbers including 5

5.4 Nested Loops


A loop inside another loop. The inner loop completes fully for every single step of the outer loop.

# From [Link] — triangle patterns


num = int(input('Enter Number: '))

# Growing triangle: row 0 → 1 star, row 1 → 2 stars, ...


for i in range(num):
print('*' * (i + 1)) # i+1 stars per row

# Shrinking triangle: row 0 → num stars, row 1 → num-1 stars, ...


for i in range(num):

Page 15
Python Complete Guide — Anumay Rai | IIIT Hyderabad

print('*' * (num - i))

# Right-aligned triangle: spaces decrease, stars increase


for i in range(num):
spaces = num - i - 1 # spaces get fewer each row
stars = i + 1 # stars get more each row
print(' ' * spaces, end='') # spaces first (no newline)
print('*' * stars) # then stars (with newline)

5.5 for-else
The else block after a for loop runs only if the loop completed without a break.

# Runs completely — so else executes


for i in range(5):
print(i, end=' ')
else:
print('\nLoop finished normally') # runs because no break occurred

# With break — else does NOT run


for i in range(10):
if i == 5:
break # loop exited early
else:
print('This will NOT print') # skipped because of break

Page 16
🧩 Chapter 6: Functions
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Define once, call many times

6.1 Defining and Calling


# def creates the function — Python does NOT run it yet
def greet():
print('Hello')
print('Welcome')

# You must explicitly call it to run it


greet() # Output: Hello Welcome
greet() # Call as many times as you want

6.2 Parameters and Arguments


# Parameters are placeholders in the function definition
def student(name, age): # name and age are PARAMETERS
print(name, 'is', age, 'years old')

# Arguments are actual values passed when calling


student('Anumay', 20) # positional — order matters
student(age=20, name='Anumay') # keyword — order doesn't matter

# Default parameter: used when caller doesn't provide that argument


def greet(name='Guest'): # if name not given, use 'Guest'
print('Hello', name)

greet() # Hello Guest (default used)


greet('Anumay') # Hello Anumay (default overridden)

6.3 Return Values


# return sends a value back to the caller and exits the function
def add(a, b):
return a + b # compute sum, send it back

# Use the returned value


result = add(5, 3) # result stores 8
print(result) # 8
print('Sum:', add(10, 20)) # directly use return value in print

# From Cal_Project_Anumay_Rai.py — all calculator functions


def sub(a, b): return a - b
def mul(a, b): return a * b
def div(a, b): return a / b # always returns float

Page 17
Python Complete Guide — Anumay Rai | IIIT Hyderabad

def rem(a, b): return a % b


def fldiv(a, b): return a // b

6.4 Variable Scope — Local vs Global


# Variables inside a function are LOCAL — they die when the function ends
def show():
msg = 'Hello World' # local variable
print(msg) # works inside

show() # Hello World


# print(msg) # NameError! 'msg' doesn't exist outside the function

# global keyword: modify a module-level variable from inside a function


counter = 0 # defined at the top level → global

def increment():
global counter # without this, Python creates a NEW local 'counter'
counter += 1 # modifies the actual global counter

increment()
increment()
print(counter) # 2 — global was modified

6.5 Recursion
A function that calls itself. Must have a base case to stop. Without it, the function calls itself forever →
RecursionError.

# From [Link] — factorial


def factorial(n):
# BASE CASE: stop the recursion when n reaches 0 or 1
if n == 0 or n == 1:
return 1
# RECURSIVE CASE: factorial(n) = n × factorial(n-1)
return n * factorial(n - 1)

# Trace for factorial(4):


# factorial(4) = 4 × factorial(3)
# = 4 × 3 × factorial(2)
# = 4 × 3 × 2 × factorial(1)
# = 4 × 3 × 2 × 1 ← base case hit, starts unwinding
# = 24

print(factorial(0)) # 1
print(factorial(5)) # 120

Page 18
🔤 Chapter 7: Strings
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Python's most-used data type

7.1 Basics and Slicing


# Strings are immutable sequences of characters
text = 'Hello'
# Positions: H=0, e=1, l=2, l=3, o=4
# Negative: H=-5, e=-4, l=-3, l=-2, o=-1

# Single character (indexing)


print(text[0]) # H (first character)
print(text[-1]) # o (last character)

# Multiple characters (slicing) — [start:stop] stop is NOT included


word = 'PythonProgramming'
print(word[0:6]) # Python (characters at 0,1,2,3,4,5)
print(word[:6]) # Python (same — start defaults to 0)
print(word[6:]) # Programming (from 6 to end)
print(word[-4:]) # ming (last 4 characters)

# Slicing with step [start:stop:step]


print(word[::2]) # PtoPoramn (every 2nd character)
print(word[::-1]) # gni... (reverse the entire string)

print(len('Hello')) # 5 — number of characters


print('Hi ' * 3) # Hi Hi Hi — string repetition with *
print('Hi' + ' there') # Hi there — concatenation with +

7.2 String Methods


Methods are functions built into strings. They never modify the original — they return a new string.

text = 'HellO woRld '

# Case methods
print([Link]()) # 'HELLO WORLD ' — all caps
print([Link]()) # 'hello world ' — all lowercase
print([Link]()) # 'Hello World ' — first letter of each word
print('hi'.capitalize()) # 'Hi' — only very first letter of whole string

# Whitespace
print([Link]()) # 'HellO woRld' — removes leading/trailing
spaces

# Search and replace


print([Link]('woRld', 'Python')) # replaces ALL occurrences
print([Link]('l')) # 2 — index of FIRST 'l', or -1 if not found
print([Link]('l')) # 3 — how many times 'l' appears
print([Link]('He')) # True

Page 19
Python Complete Guide — Anumay Rai | IIIT Hyderabad

print([Link]('ld ')) # True (note the trailing space)

# Type checking
print('hello'.isalpha()) # True — all letters
print('123'.isdigit()) # True — all digits
print('abc1'.isalnum()) # True — letters and/or digits

7.3 split() and join()


# split() breaks a string into a list at each separator
fruits = 'Apple,Banana,Grapes'
print([Link](',')) # ['Apple', 'Banana', 'Grapes']

# split with no argument splits at any whitespace


print('Hello World Python'.split()) # ['Hello', 'World', 'Python']

# join() is the reverse — combines a list into one string


fruits_list = ['Apple', 'Banana', 'Papaya']
print('-'.join(fruits_list)) # 'Apple-Banana-Papaya'
print(' '.join(fruits_list)) # 'Apple Banana Papaya'
print(''.join(fruits_list)) # 'AppleBananaPapaya' (no separator)

7.4 chr(), ord(), f-strings


# chr(n) → character at ASCII position n
print(chr(65)) # 'A' (uppercase A is at position 65)
print(chr(97)) # 'a' (lowercase a is at position 97)

# ord(c) → ASCII number of character c


print(ord('A')) # 65
print(ord('a')) # 97

# From [Link] — print A to Z


for x in range(1, 27):
print(chr(64 + x), end=' ') # chr(65)=A, chr(66)=B, ... chr(90)=Z

# f-strings — embed variables and expressions directly in strings


name = 'Anumay'; age = 21
print(f'{name} is {age} years old') # Anumay is 21 years old
print(f'Next year: {age + 1}') # expressions inside {} work too
pi = 3.14159
print(f'Pi = {pi:.2f}') # Pi = 3.14 (.2f = 2 decimal places)

Page 20
📋 Chapter 8: Lists
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Ordered, mutable, indexed collections

8.1 Creating and Accessing


# Lists can hold any mix of types
a = ['raj', 'ram', 'ramu']
mixed = [1, 'Hello', 3.67, True, 20]
empty = []

# Positive index: starts at 0 from the left


print(mixed[0]) # 1 (first item)
print(mixed[1]) # 'Hello' (second item)

# Negative index: starts at -1 from the right


print(mixed[-1]) # 20 (last item)
print(mixed[-2]) # True (second from last)

8.2 Adding Items


my_list = ['Ram', 20, 9.5]

# .append(x) — adds x to the END (most common)


my_list.append('Ali')
print(my_list) # ['Ram', 20, 9.5, 'Ali']

# .insert(i, x) — inserts x at position i, shifts everything right


my_list.insert(1, 'NEW') # insert at index 1
print(my_list) # ['Ram', 'NEW', 20, 9.5, 'Ali']

# .extend(items) — adds all items from another list at once


my_list.extend(['hello', 'lucknow'])
print(my_list) # ['Ram', 'NEW', 20, 9.5, 'Ali', 'hello', 'lucknow']

8.3 Removing Items


nums = [22, 26, 28, 24, 21]

# .remove(value) — removes FIRST occurrence of that value


[Link](22) # removes 22 by value

# .pop(index) — removes item at index and RETURNS it


removed = [Link](0) # removes index 0, returns it
print(removed) # 26

# .pop() — no index means remove and return the LAST item

Page 21
Python Complete Guide — Anumay Rai | IIIT Hyderabad

[Link]() # removes last item

# .clear() — removes ALL items, list becomes []


[Link]()
print(nums) # []

# del — removes by index (Python keyword, not a method)


fruits = ['Apple', 'Mango', 'Grapes', 'Virus']
del fruits[3] # removes 'Virus' at index 3

8.4 Sorting, Searching, and Copying


nums = [22, 26, 28, 26, 24, 26, 21]

# Count and find


print([Link](26)) # 3 — how many times 26 appears
print([Link](26)) # 1 — first index where 26 is found

# Sorting — modifies the list in-place (changes the original)


[Link]() # ascending: [21, 22, 24, 26, 26, 26, 28]
[Link](reverse=True) # descending: [28, 26, 26, 26, 24, 22, 21]
[Link]() # just flip current order (NOT sorting)

# WARNING: assignment does NOT copy — both names point to SAME list
a = [1, 2, 3]
b = a # b is NOT a copy — it's another label for the same list
b[0] = 99
print(a) # [99, 2, 3] — a was also changed!

# .copy() — creates an independent copy


a = [1, 2, 3]
b = [Link]() # now b is a separate list
b[0] = 99
print(a) # [1, 2, 3] — original untouched

Page 22
🔒 Chapter 9: Tuples
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Ordered, immutable sequences

9.1 Creating and Accessing


# Tuples use parentheses — once created, cannot be changed
colors = ('Red', 'Black', 'Pink', 'White')
nums = (1, 4, 8, 7)
empty = ()

# Indexing and slicing work exactly like lists


print(colors[0]) # 'Red'
print(colors[-1]) # 'White'
print(colors[1:3]) # ('Black', 'Pink')

# COMMON MISTAKE: single-item tuple needs a trailing comma!


t_correct = ('Ram',) # ← tuple with 1 item
t_wrong = ('Ram') # ← just a string in parentheses!
print(type(t_correct)) # <class 'tuple'>
print(type(t_wrong)) # <class 'str'>

9.2 Tuple Unpacking


Assign all items of a tuple to individual variables in one step.

# From [Link]
person = ('Anumay', 20, 'India') # 3 items
name, age, country = person # unpack into 3 variables
print(name) # Anumay
print(age) # 20
print(country) # India

# Variable swap using tuple packing/unpacking — no temp variable needed


x = 10; y = 20
x, y = y, x # right side packs (20,10), then unpacks into x=20, y=10
print(x, y) # 20 10

Page 23
📖 Chapter 10: Dictionaries
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Key-value pairs — like a real dictionary

10.1 Creating and Accessing


student = {'Name': 'Anumay', 'Age': 20, 'Course': 'Python'}

# Direct access by key — crashes if key missing (KeyError)


print(student['Name']) # 'Anumay'

# .get() — safe access: returns None (or your default) if key missing
print([Link]('Age')) # 20
print([Link]('Salary')) # None (no crash)
print([Link]('Salary', 'N/A')) # 'N/A' (custom default)

10.2 Adding, Updating, Removing


student = {'Name': 'Anumay', 'Age': 20}

# Update existing key


student['Age'] = 21

# Add new key


student['Place'] = 'Lucknow'

# .pop(key) — remove key and return its value


removed = [Link]('Age') # returns 21

# .setdefault(key, value) — add key ONLY if it doesn't exist yet


fruits = {'Apple': 'Red'}
[Link]('Apple', 'Green') # Apple exists → no change
[Link]('Lychee', 'Pink') # Lychee missing → adds it
print(fruits) # {'Apple': 'Red', 'Lychee': 'Pink'}

10.3 Looping Through Dictionaries


student = {'Name': 'Anumay', 'Age': 21, 'Course': 'Python'}

# Loop over keys only (default behaviour)


for key in student:
print(key) # Name Age Course

# Loop over values only


for value in [Link]():
print(value) # Anumay 21 Python

Page 24
Python Complete Guide — Anumay Rai | IIIT Hyderabad

# Loop over key-value pairs — most common and useful


for key, value in [Link]():
print(f'{key} : {value}') # Name : Anumay / Age : 21 ...

Page 25
⭕ Chapter 11: Sets
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Unordered, unique collections

11.1 Creating and Adding


# Sets automatically remove duplicates
my_set = {10, 1, 2, 3, 4, 3, 2, 1}
print(my_set) # {1, 2, 3, 4, 10} — each value appears once

# Empty set MUST use set() — not {} which creates an empty dict!
empty_set = set() # correct
empty_dict = {} # this is an empty DICTIONARY

# .add(x) — add one item


my_set.add(7) # adds 7
my_set.add(3) # 3 already there — nothing happens

# .update(list) — add multiple items at once


my_set.update([8, 9, 10])

11.2 Removing and Set Operations


my_set = {1, 2, 3, 4, 5}

# .remove(x) — error if x not in set; .discard(x) — no error (safer)


my_set.remove(3) # removes 3, crashes if 3 not there
my_set.discard(99) # 99 not in set — no error, no change

# Set maths
a = {1, 2, 3, 4}
b = {2, 3, 5, 6}

print([Link](b)) # {1,2,3,4,5,6} — everything from both


print([Link](b)) # {2,3} — only items in BOTH
print([Link](b)) # {1,4} — in a but NOT in b
print([Link](a)) # {5,6} — in b but NOT in a
print(a.symmetric_difference(b)) # {1,4,5,6} — in either but NOT both

# Subset / superset checks


print({2,3}.issubset(a)) # True — {2,3} is fully inside a
print([Link]({2,3})) # True — a contains all of {2,3}

Page 26
📦 Chapter 12: Imports & Modules
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Using Python's built-in libraries

12.1 import and from-import


# Method 1: import whole module — must prefix every call with module name
import math
print([Link](16)) # 4.0
print([Link]) # 3.14159...
print([Link](3.7)) # 3
print([Link](3.2)) # 4

# Method 2: import specific things — no prefix needed


from math import sqrt, pi
print(sqrt(25)) # 5.0
print(pi) # 3.14159...

# random — used in your Hangman game


import random
words = ['apple', 'banana', 'mango']
print([Link](words)) # picks one at random
print([Link](1, 10)) # random integer between 1 and 10

# Counter from collections — used in Hangman to compare letter frequencies


from collections import Counter
c = Counter('abracadabra')
print(c) # Counter({'a': 5, 'b': 2, 'r': 2, ...})
print(Counter('abc') == Counter('cba')) # True — same letters, same counts

Page 27
🛡️ Chapter 13: Exception Handling
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Preventing crashes with try-except

13.1 try-except Structure


An exception is an error at runtime. Without handling it, Python prints an error message and stops your
program. With try-except, you catch it and respond gracefully.

# Basic structure
try:
# code that might raise an error
result = 10 / 0
except ZeroDivisionError:
# runs ONLY if ZeroDivisionError occurred
print('Cannot divide by zero!')

# Catching multiple error types


try:
a = int(input('Enter a: '))
b = int(input('Enter b: '))
print(a / b)
except ZeroDivisionError:
print('Cannot divide by zero!')
except ValueError:
print('Please enter numbers only!')

# else — runs only when NO exception occurred


try:
num = int(input('Number: '))
except ValueError:
print('Not a valid number')
else:
print('You entered:', num) # only reaches here if no error

# finally — ALWAYS runs, error or not (great for cleanup)


try:
f = open('[Link]', 'r')
except FileNotFoundError:
print('File not found')
finally:
print('This always runs — close files, release resources here')

Page 28
🚀 Chapter 14: Essential Extra Tools
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Lambda, comprehensions, enumerate, zip, OOP, file I/O

14.1 Lambda — One-Line Functions


# Regular function vs lambda — same result, different style
def double(x): return x * 2
double_l = lambda x: x * 2 # same thing — one line

print(double(5)) # 10
print(double_l(5)) # 10

# Lambda with multiple parameters


add = lambda a, b: a + b
print(add(3, 4)) # 7

# Most useful as the key= argument in sorted()


students = [('Anumay', 21), ('Ram', 20), ('Shyam', 22)]
[Link](key=lambda s: s[1]) # sort by age (index 1 of each tuple)
print(students) # [('Ram', 20), ('Anumay', 21), ('Shyam', 22)]

14.2 List Comprehension


A concise way to build lists. Syntax: [expression for item in iterable if condition]

# Long way (4 lines)


squares = []
for x in range(1, 6):
[Link](x * x)

# Comprehension (1 line) — identical result


squares = [x*x for x in range(1, 6)]
print(squares) # [1, 4, 9, 16, 25]

# With condition: only even numbers


evens = [x for x in range(1, 11) if x % 2 == 0]
print(evens) # [2, 4, 6, 8, 10]

# Transform strings
fruits = ['apple', 'banana', 'mango']
upper = [[Link]() for f in fruits]
print(upper) # ['APPLE', 'BANANA', 'MANGO']

# Dict comprehension
squares_dict = {x: x**2 for x in range(1, 6)}
print(squares_dict) # {1:1, 2:4, 3:9, 4:16, 5:25}

Page 29
Python Complete Guide — Anumay Rai | IIIT Hyderabad

14.3 *args and **kwargs


# *args — accept any number of positional arguments (packed into a tuple)
def total(*args):
print(type(args)) # <class 'tuple'>
return sum(args)

print(total(1, 2, 3)) # 6
print(total(10, 20, 30, 40)) # 100

# **kwargs — accept any number of keyword arguments (packed into a dict)


def show_info(**kwargs):
print(type(kwargs)) # <class 'dict'>
for key, value in [Link]():
print(key, ':', value)

show_info(name='Anumay', age=21, city='Lucknow')


# name : Anumay / age : 21 / city : Lucknow

14.4 enumerate() and zip()


# enumerate() — loop with index AND value at the same time
fruits = ['Apple', 'Banana', 'Mango']
for i, fruit in enumerate(fruits): # i is the index, fruit is the value
print(i, ':', fruit)
# 0 : Apple / 1 : Banana / 2 : Mango

# Start index at 1 instead of 0


for i, fruit in enumerate(fruits, start=1):
print(i, ':', fruit) # 1 : Apple / 2 : Banana ...

# zip() — loop over two lists at the same time, one pair per iteration
names = ['Anumay', 'Ram', 'Shyam']
ages = [21, 20, 22 ]
for name, age in zip(names, ages):
print(name, 'is', age, 'years old')

14.5 File I/O


# Writing to a file — 'w' creates or overwrites
with open('[Link]', 'w') as f:
[Link]('Hello World\n') # \n is newline
[Link]('Second line\n')
# 'with' automatically closes the file — even if an error occurs inside

# Reading entire file as one string


with open('[Link]', 'r') as f:
content = [Link]()
print(content)

Page 30
Python Complete Guide — Anumay Rai | IIIT Hyderabad

# Reading line by line — memory-efficient for large files


with open('[Link]', 'r') as f:
for line in f:
print([Link]()) # .strip() removes the \n at end of each line

# Appending — adds to end without erasing existing content


with open('[Link]', 'a') as f:
[Link]('Third line\n')

# File modes: 'r'=read 'w'=write(overwrite) 'a'=append 'rb'=binary read

14.6 OOP Introduction


# A class is a blueprint. An object is an instance built from that blueprint.
class Student:
def __init__(self, name, age): # constructor — runs on object creation
[Link] = name # [Link] stores it as an attribute
[Link] = age

def greet(self):
print(f'Hi, I am {[Link]}, {[Link]} years old')

# Create objects — each is independent


s1 = Student('Anumay', 21)
s2 = Student('Ram', 20)
[Link]() # Hi, I am Anumay, 21 years old
[Link]() # Hi, I am Ram, 20 years old

# Inheritance — child class reuses parent's methods


class GradStudent(Student): # inherits from Student
def __init__(self, name, age, lab):
super().__init__(name, age) # call parent's constructor
[Link] = lab

def greet(self): # OVERRIDE the parent's greet


print(f'Hi, I am {[Link]} from {[Link]} lab')

g = GradStudent('Anumay', 21, 'SPCRC')


[Link]() # Hi, I am Anumay from SPCRC lab

14.7 map(), filter(), sorted()


# map(function, iterable) — apply function to every item
nums = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x**2, nums))
print(squared) # [1, 4, 9, 16, 25]

# filter(function, iterable) — keep items where function returns True


evens = list(filter(lambda x: x % 2 == 0, [1,2,3,4,5,6]))
print(evens) # [2, 4, 6]

Page 31
Python Complete Guide — Anumay Rai | IIIT Hyderabad

# sorted() — returns a NEW sorted list, original unchanged


nums = [5, 2, 8, 1]
print(sorted(nums)) # [1, 2, 5, 8] (ascending)
print(sorted(nums, reverse=True))# [8, 5, 2, 1] (descending)
print(nums) # [5, 2, 8, 1] (ORIGINAL unchanged!)
# Compare: [Link]() modifies the original; sorted() does not

Page 32
📌 Chapter 15: Quick Reference
Python Complete Guide — Anumay Rai | IIIT Hyderabad

All data structures and methods at a glance

Structure Synta Ordered? Mutable? Duplicates? Access by


x
List [] Yes Yes Yes Index [0]
Tuple () Yes No Yes Index [0]
Dictionary {:} Yes* Yes Keys: No Key ['k']
Set {} No Yes No Iteration only
String '' Yes No Yes Index [0]

Category Operators
Arithmetic + - * / // % **
Assignment += -= *= /= //= %= **=
Comparison == != > < >= <=
Logical and or not
Bitwise & | ^ ~ << >>
Identity is is not
Membership in not in

Page 33
⚡ Chapter 16: Generators & yield
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Memory-efficient iteration for large datasets

A generator is a function that uses yield instead of return. Instead of computing all values at once and
storing them in memory, it produces one value at a time — on demand. This is critical for large
datasets (e.g. reading millions of audio frames in ML pipelines).

16.1 yield vs return


return exits the function and sends one value back permanently. yield pauses the function, sends a
value, and resumes from that exact point on the next call.

# Regular function — computes ALL values, stores ALL in memory at once


def squares_list(n):
result = []
for i in range(n):
[Link](i ** 2)
return result # returns one big list — all in RAM

# Generator function — produces ONE value at a time


def squares_gen(n):
for i in range(n):
yield i ** 2 # pauses here, sends value, resumes on next call

print(squares_list(5)) # [0, 1, 4, 9, 16] — full list loaded in RAM

gen = squares_gen(5) # creates generator object — NOTHING computed yet!


print(next(gen)) # 0 — computes just the first value
print(next(gen)) # 1 — resumes, computes next
print(next(gen)) # 4
# StopIteration is raised after last value

# In a for loop — for handles StopIteration automatically


for val in squares_gen(5):
print(val, end=' ') # 0 1 4 9 16

16.2 Generator Expressions — Lazy List Comprehensions


# List comprehension — ALL values computed immediately, stored in RAM
squares_list = [x**2 for x in range(1_000_000)] # uses ~8MB RAM

# Generator expression — uses () instead of [] — nothing computed yet


squares_gen = (x**2 for x in range(1_000_000)) # uses ~200 bytes RAM!

# Both iterate the same way — but generator is far more memory efficient
print(sum(squares_gen)) # computes values one by one during sum()

# Practical: filter a huge dataset without loading it all

Page 34
Python Complete Guide — Anumay Rai | IIIT Hyderabad

data = range(10_000_000)
large = (x for x in data if x > 9_999_990)
for val in large: # only the few matching values are computed
print(val)

16.3 Infinite Generators


# A generator that never stops — impossible with a list!
def counter(start=0):
n = start
while True: # runs forever — fine inside a generator
yield n
n += 1

gen = counter(1)
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3 — we control when to stop

# itertools has built-in infinite generators


from itertools import count, cycle
for i in count(10): # 10, 11, 12, 13, ...
if i > 14: break
print(i, end=' ') # 10 11 12 13 14

💡 Why generators matter for ML/DSP


When processing large .wav files or datasets that don't fit in RAM, use generators.
PyTorch DataLoader uses this concept — it loads one batch at a time, not the whole dataset.
sum(x**2 for x in big_data) — the generator expression inside sum() is lazy.

Page 35
🎨 Chapter 17: Decorators
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Wrapping functions to add behaviour — @syntax

A decorator is a function that takes another function, wraps it, and returns the new version. The
@syntax is clean shorthand for: func = decorator(func). You will see decorators constantly in Flask,
PyTorch, and pytest.

17.1 How Decorators Work


# A decorator is a function that takes a function as input and returns a new
one
def my_decorator(func):
def wrapper(*args, **kwargs): # wrapper runs INSTEAD of the original
func
print('Before the function') # extra code added BEFORE
result = func(*args, **kwargs) # call the original function
print('After the function') # extra code added AFTER
return result
return wrapper # return wrapper — NOT wrapper() (don't call it!)

# Using @ syntax — cleaner way to apply the decorator


@my_decorator
def say_hello(name):
print(f'Hello, {name}!')
return 'done'

say_hello('Anumay')
# Before the function
# Hello, Anumay!
# After the function

# @my_decorator is exactly equivalent to: say_hello = my_decorator(say_hello)

17.2 Practical Decorators — Timer & Logger


import time
from functools import wraps

# Timer decorator — measures how long any function takes


def timer(func):
@wraps(func) # preserves the original function's name and docstring
def wrapper(*args, **kwargs):
start = [Link]() # record start time
result = func(*args, **kwargs) # run the actual function
end = [Link]() # record end time
print(f'{func.__name__} took {end - start:.4f} seconds')
return result
return wrapper

Page 36
Python Complete Guide — Anumay Rai | IIIT Hyderabad

@timer
def slow_sum(n):
return sum(range(n))

slow_sum(10_000_000) # slow_sum took 0.3142 seconds

# Logger decorator — logs every call


def logger(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f'Calling {func.__name__} with args={args}')
result = func(*args, **kwargs)
print(f'{func.__name__} returned {result}')
return result
return wrapper

17.3 @staticmethod and @classmethod


class Temperature:
def __init__(self, celsius):
[Link] = celsius

# Regular method: needs 'self', accesses the specific instance


def to_fahrenheit(self):
return [Link] * 9/5 + 32

# @staticmethod: no self, no cls — utility function that belongs here


logically
@staticmethod
def is_valid(temp):
return temp >= -273.15 # check if above absolute zero

# @classmethod: receives the CLASS as first arg (cls not self)


# Used for alternative constructors — different ways to create the object
@classmethod
def from_fahrenheit(cls, f):
return cls((f - 32) * 5/9) # creates a new Temperature instance

t = Temperature(100)
print(t.to_fahrenheit()) # 212.0
print(Temperature.is_valid(-300)) # False (called on class, no instance
needed)
t2 = Temperature.from_fahrenheit(212) # alternative constructor
print([Link]) # 100.0

Page 37
🔧 Chapter 18: Dunder (Magic) Methods
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Making your classes behave like built-in types

Dunder methods (double underscore on both sides) are called automatically by Python when you use
built-in operations. Defining them makes your custom class feel native.

18.1 Essential Dunders


class Vector:
def __init__(self, x, y):
self.x = x
self.y = y

# __str__: called by print() — human-readable output


def __str__(self):
return f'Vector({self.x}, {self.y})'

# __repr__: called in the REPL — should look like code to recreate it


def __repr__(self):
return f'Vector(x={self.x}, y={self.y})'

# __len__: called by len() — must return an integer


def __len__(self):
return 2 # a 2D vector has 2 components

# __eq__: called by == — compare two vectors


def __eq__(self, other):
return self.x == other.x and self.y == other.y

# __add__: called by + — operator overloading


def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1) # Vector(1, 2) — calls __str__
print(len(v1)) # 2 — calls __len__
print(v1 == v2) # False — calls __eq__
print(v1 + v2) # Vector(4, 6) — calls __add__

18.2 __enter__ and __exit__ — Context Managers


These two dunders implement the with statement. __enter__ runs on entry, __exit__ runs on exit —
even if an error occurs inside.

import time

class Timer:

Page 38
Python Complete Guide — Anumay Rai | IIIT Hyderabad

# __enter__ runs when the 'with' block starts


def __enter__(self):
[Link] = [Link]()
print('Timer started')
return self # 'as t' binds to this object

# __exit__ runs when the 'with' block ends — normal or exception


# exc_type, exc_val, exc_tb are None if no error occurred
def __exit__(self, exc_type, exc_val, exc_tb):
elapsed = [Link]() - [Link]
print(f'Elapsed: {elapsed:.4f} seconds')
return False # False = don't suppress exceptions

with Timer() as t:
total = sum(range(1_000_000)) # timed code
# Output:
# Timer started
# Elapsed: 0.0412 seconds

# You already use context managers with files:


# with open('[Link]') as f: — open().__enter__() is called, f.__exit__()
closes file

Page 39
📝 Chapter 19: Type Hints & Annotations
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Self-documenting code — standard in modern Python

Type hints tell readers and tools (VS Code Pylance, mypy) what types a function expects. Python does
NOT enforce them at runtime — they are purely for readability and tooling.

19.1 Basic Annotations


# Syntax: parameter: type, return type after ->
def add(a: int, b: int) -> int:
return a + b

def greet(name: str) -> str:


return 'Hello ' + name

def is_even(n: int) -> bool:


return n % 2 == 0

# None return type — function returns nothing


def log(msg: str) -> None:
print(msg)

# Annotating variables
learning_rate: float = 0.001
epochs: int = 100
model_name: str = 'ResNet50'

19.2 typing Module — Complex Types


from typing import List, Dict, Tuple, Optional, Union

# Optional: value can be the type OR None


def find_user(uid: int) -> Optional[str]:
return None # could return a name string or None

# List, Dict with specific element types


def process(items: List[int]) -> List[str]:
return [str(x) for x in items]

# Union: accepts multiple types


def stringify(val: Union[int, float, str]) -> str:
return str(val)

# Python 3.9+ — use built-in types directly (no import needed)


def process_new(items: list[int]) -> list[str]:
return [str(x) for x in items]

# Python 3.10+ — use | instead of Union

Page 40
Python Complete Guide — Anumay Rai | IIIT Hyderabad

def stringify_new(val: int | float | str) -> str:


return str(val)

💡 Type hints in ML code


def train(model: [Link], data: DataLoader, epochs: int = 10) -> float:
Type hints make it immediately clear what a function needs and returns.
They also power autocomplete in VS Code — heavily used in PyTorch codebases.

Page 41
🗃️ Chapter 20: collections Module
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Specialised container datatypes

20.1 defaultdict — Dict with Automatic Default Values


from collections import defaultdict

# Regular dict: accessing a missing key raises KeyError


# defaultdict: accessing a missing key creates it with a default

# Count word frequencies — no need to check if key exists first


word_count = defaultdict(int) # default factory: int() → 0
sentence = 'the cat sat on the mat the cat'
for word in [Link]():
word_count[word] += 1 # first access: key created with 0, then +1
print(dict(word_count)) # {'the': 3, 'cat': 2, 'sat': 1, ...}

# Group items by category


groups = defaultdict(list) # default factory: list() → []
data = [('fruit','apple'), ('veg','carrot'), ('fruit','mango')]
for category, item in data:
groups[category].append(item) # no need to check if key exists
print(dict(groups)) # {'fruit': ['apple', 'mango'], 'veg': ['carrot']}

20.2 deque — Double-Ended Queue


from collections import deque

# List append/pop from RIGHT is O(1) — fast


# List insert/pop from LEFT is O(n) — slow (shifts all items)
# deque is O(1) for BOTH ends — much faster for queue operations

dq = deque([1, 2, 3, 4])
[Link](5) # add to RIGHT
[Link](0) # add to LEFT — O(1) unlike [Link](0,...)
[Link]() # remove from RIGHT
[Link]() # remove from LEFT — O(1)
print(dq) # deque([1, 2, 3, 4])

# maxlen — bounded sliding window: automatically discards oldest items


recent = deque(maxlen=3) # holds only the 3 most recent values
for x in range(6):
[Link](x)
print(list(recent))
# [0] → [0,1] → [0,1,2] → [1,2,3] → [2,3,4] → [3,4,5]
# Used for: sliding window averages, BFS in graphs, recent-history buffers

Page 42
Python Complete Guide — Anumay Rai | IIIT Hyderabad

20.3 namedtuple — Readable Tuple


from collections import namedtuple

# Regular tuple: point[0] and point[1] are unclear


# namedtuple: point.x and point.y are self-documenting

Point = namedtuple('Point', ['x', 'y'])


Person = namedtuple('Person', ['name', 'age', 'city'])

p = Point(3, 4)
print(p.x, p.y) # 3 4 — named access
print(p[0], p[1]) # 3 4 — index access still works too
print(p) # Point(x=3, y=4) — clean repr for free

person = Person('Anumay', 21, 'Lucknow')


print([Link]) # Anumay
print(person._asdict()) # {'name': 'Anumay', 'age': 21, 'city': 'Lucknow'}

20.4 OrderedDict & ChainMap


from collections import OrderedDict, ChainMap

# OrderedDict remembers insertion order (regular dicts do too in Python 3.7+)


# Extra feature: move_to_end()
od = OrderedDict([('first', 1), ('second', 2), ('third', 3)])
od.move_to_end('first') # move 'first' to the end
print(list([Link]())) # ['second', 'third', 'first']
od.move_to_end('third', last=False) # move 'third' to front
print(list([Link]())) # ['third', 'second', 'first']

# ChainMap — search multiple dicts as if they were one


defaults = {'colour': 'blue', 'theme': 'light', 'font': 'Arial'}
user_prefs = {'colour': 'red'} # user overrides colour only

config = ChainMap(user_prefs, defaults) # user_prefs takes priority


print(config['colour']) # 'red' — found in user_prefs (first map)
print(config['theme']) # 'light' — not in user_prefs, falls back to
defaults
print(config['font']) # 'Arial' — falls back to defaults

Page 43
🔗 Chapter 21: itertools & functools
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Power tools for iteration and functional programming

21.1 itertools — Efficient Iteration Tools


import itertools

# chain — combine multiple iterables into one seamless sequence


a = [1, 2, 3]; b = [4, 5, 6]; c = ['x', 'y']
print(list([Link](a, b, c))) # [1, 2, 3, 4, 5, 6, 'x', 'y']

# product — cartesian product (equivalent to nested for loops)


colors = ['Red', 'Blue']
sizes = ['S', 'M', 'L']
for combo in [Link](colors, sizes):
print(combo, end=' ') # ('Red','S') ('Red','M') ('Red','L') ('Blue','S')
...

# combinations — unique groups, no repetition, order doesn't matter


items = ['A', 'B', 'C']
print(list([Link](items, 2)))
# [('A','B'), ('A','C'), ('B','C')]

# permutations — all ordered arrangements


print(list([Link](items, 2)))
# [('A','B'), ('A','C'), ('B','A'), ('B','C'), ('C','A'), ('C','B')]

# islice — take first N items from ANY iterator (works on generators too)
gen = [Link](0) # infinite counter: 0, 1, 2, 3, ...
print(list([Link](gen, 5))) # [0, 1, 2, 3, 4]

# groupby — group consecutive identical elements


data = [1, 1, 2, 2, 2, 3, 1, 1]
for key, group in [Link](data):
print(key, list(group)) # 1 [1,1] / 2 [2,2,2] / 3 [3] / 1 [1,1]

21.2 functools — Higher-Order Function Tools


from functools import partial, reduce, lru_cache, wraps

# partial — fix some arguments of a function to create a simpler new function


def power(base, exp):
return base ** exp

square = partial(power, exp=2) # fix exp=2, only base needs to be given


cube = partial(power, exp=3) # fix exp=3
print(square(4)) # 16 — calls power(4, exp=2)
print(cube(3)) # 27 — calls power(3, exp=3)

Page 44
Python Complete Guide — Anumay Rai | IIIT Hyderabad

# reduce — apply function cumulatively to collapse sequence to one value


nums = [1, 2, 3, 4, 5]
total = reduce(lambda acc, x: acc + x, nums) # 1+2+3+4+5 = 15
product = reduce(lambda acc, x: acc * x, nums) # 1×2×3×4×5 = 120
print(total, product) # 15 120

# lru_cache — memoize expensive function calls (cache results automatically)


@lru_cache(maxsize=None) # cache unlimited results
def fib(n):
if n <= 1: return n
return fib(n-1) + fib(n-2)

print(fib(50)) # 12586269025 — instant! Without cache: minutes


print(fib.cache_info()) # shows hits, misses — shows how many calls were
saved

Page 45
📋 Chapter 22: Dataclasses
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Auto-generated boilerplate for data-holding classes

The @dataclass decorator automatically generates __init__, __repr__, and __eq__ from the field
definitions. Perfect for ML config objects, result containers, and anything where you mostly store data.

22.1 Basic Dataclass


from dataclasses import dataclass, field

# Without @dataclass — all this boilerplate written manually:


class StudentOld:
def __init__(self, name: str, age: int, gpa: float):
[Link] = name
[Link] = age
[Link] = gpa
def __repr__(self):
return f'Student(name={[Link]}, age={[Link]}, gpa={[Link]})'
def __eq__(self, other):
return [Link] == [Link] and [Link] == [Link]

# With @dataclass — Python generates ALL of the above automatically


@dataclass
class Student:
name: str # type annotation required
age: int
gpa: float = 0.0 # default value

s1 = Student('Anumay', 21, 9.2)


s2 = Student('Anumay', 21) # gpa uses default 0.0
print(s1) # Student(name='Anumay', age=21, gpa=9.2)
print(s1 == Student('Anumay', 21, 9.2)) # True — __eq__ compares all fields

22.2 Advanced Dataclass Features


# frozen=True — makes instances immutable (like a named tuple)
@dataclass(frozen=True)
class ModelConfig:
learning_rate: float = 0.001
epochs: int = 100
batch_size: int = 32

cfg = ModelConfig(learning_rate=0.01, epochs=50)


print(cfg) # ModelConfig(learning_rate=0.01, epochs=50,
batch_size=32)
# [Link] = 200 # FrozenInstanceError — cannot modify frozen dataclass

# field(default_factory=list) — give each instance its OWN mutable default

Page 46
Python Complete Guide — Anumay Rai | IIIT Hyderabad

@dataclass
class Team:
name: str
members: list = field(default_factory=list) # new list per instance

t1 = Team('Lab A')
t2 = Team('Lab B')
[Link]('Anumay')
print([Link]) # ['Anumay']
print([Link]) # [] — t2 has its own separate list
# NEVER write: members: list = [] — all instances would share the SAME list!

Page 47
🏗️ Chapter 23: Advanced OOP
Python Complete Guide — Anumay Rai | IIIT Hyderabad

@property, encapsulation, abstract classes

23.1 @property — Controlled Attribute Access


class Temperature:
def __init__(self, celsius: float):
self._celsius = celsius # _name convention means 'protected —
internal use'

# @property makes the method look like an attribute (no parentheses on


access)
@property
def celsius(self) -> float:
return self._celsius # getter

# @[Link] runs when you write: [Link] = value


@[Link]
def celsius(self, value: float):
if value < -273.15:
raise ValueError('Temperature below absolute zero!')
self._celsius = value # validated before storing

# Computed property — derived from celsius, no setter needed


@property
def fahrenheit(self) -> float:
return self._celsius * 9/5 + 32

t = Temperature(100)
print([Link]) # 100 — looks like attribute access, runs getter
print([Link]) # 212.0 — computed on the fly
[Link] = 0 # runs setter, validates
print([Link]) # 32.0
# [Link] = -300 # ValueError: Temperature below absolute zero!

23.2 Encapsulation — Public, Protected, Private


class BankAccount:
def __init__(self, owner: str, balance: float):
[Link] = owner # public — anyone can access freely
self._balance = balance # 'protected' — internal use by convention
(_)
self.__pin = 1234 # 'private' — name-mangled by Python (__)

def deposit(self, amount: float):


if amount > 0:
self._balance += amount

def withdraw(self, amount: float, pin: int):

Page 48
Python Complete Guide — Anumay Rai | IIIT Hyderabad

if pin != self.__pin:
raise ValueError('Wrong PIN')
if amount > self._balance:
raise ValueError('Insufficient funds')
self._balance -= amount

@property
def balance(self) -> float:
return self._balance # read-only — no setter defined

acc = BankAccount('Anumay', 1000.0)


[Link](500)
print([Link]) # 1500.0 — accessed via property
# acc.__pin # AttributeError — name-mangled to _BankAccount__pin
[Link](200, 1234)
print([Link]) # 1300.0

23.3 Abstract Base Classes — Enforcing an Interface


An abstract class defines methods that subclasses must implement. You cannot instantiate the abstract
class itself — it's purely a contract.

from abc import ABC, abstractmethod

# ABC = Abstract Base Class — cannot be created directly


# Forces every subclass to implement the abstract methods
class Shape(ABC):
@abstractmethod
def area(self) -> float: # subclass MUST implement this
pass

@abstractmethod
def perimeter(self) -> float: # subclass MUST implement this too
pass

def describe(self): # non-abstract — shared implementation


print(f'Area={[Link]():.2f}, Perimeter={[Link]():.2f}')

class Circle(Shape):
def __init__(self, r: float): self.r = r
def area(self): return 3.14159 * self.r ** 2
def perimeter(self): return 2 * 3.14159 * self.r

class Rectangle(Shape):
def __init__(self, w, h): self.w, self.h = w, h
def area(self): return self.w * self.h
def perimeter(self): return 2 * (self.w + self.h)

c = Circle(5)
[Link]() # Area=78.54, Perimeter=31.42
r = Rectangle(3, 4)
[Link]() # Area=12.00, Perimeter=14.00
# Shape() # TypeError: Can't instantiate abstract class Shape

Page 49
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Page 50
📸 Chapter 24: Shallow vs Deep Copy
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Copying nested structures correctly

Chapter 8 covered .copy() for flat lists. When you have nested structures (lists inside lists, dicts inside
dicts), .copy() is not enough — it only copies the outer container.

24.1 The Problem with Shallow Copy


import copy

# Shallow copy — copies the outer container but SHARES inner objects
original = [[1, 2, 3], [4, 5, 6]]
shallow = [Link]() # or: original[:]

shallow[0][0] = 99 # modifying inner list through shallow copy...


print(original) # [[99, 2, 3], [4, 5, 6]] — ORIGINAL ALSO CHANGED!
# Why? shallow[0] and original[0] point to the SAME inner list in memory

# Deep copy — recursively copies EVERYTHING to completely new objects


original = [[1, 2, 3], [4, 5, 6]]
deep = [Link](original)

deep[0][0] = 99
print(original) # [[1, 2, 3], [4, 5, 6]] — original untouched!
print(deep) # [[99, 2, 3], [4, 5, 6]] — only deep copy changed

24.2 When to Use Each


# a = b → assignment: same object, NOT a copy at all
# a = [Link]() → shallow copy: new outer, inner objects still shared
# a = [Link](b) → deep copy: everything is independently copied

# Shallow is fine for flat structures (no nesting):


nums = [1, 2, 3, 4, 5]
nums_copy = [Link]() # integers are immutable — shallow is safe

# Deep copy required for nested mutable structures:


config = {
'model': {'layers': [64, 128, 64], 'dropout': 0.3},
'training': {'lr': 0.001, 'epochs': 100}
}
config_copy = [Link](config) # completely independent clone
config_copy['model']['layers'].append(32) # modify copy...
print(config['model']['layers']) # [64, 128, 64] — original
intact!

⚠️ Common mistake with mutable defaults


NEVER use a mutable object (list, dict) as a default argument in a function:

Page 51
Python Complete Guide — Anumay Rai | IIIT Hyderabad

def bad(x, lst=[]): [Link](x); return lst ← lst is SHARED across all calls!
Correct: def good(x, lst=None): if lst is None: lst = [] ← creates new list each time

Page 52
📦 Chapter 25: Virtual Environments & pip
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Package management — mandatory for ML/AI work

Every Python project should have its own isolated environment so package versions don't conflict. This is
mandatory for ML/AI — different projects need different NumPy or PyTorch versions.

25.1 Creating and Using Virtual Environments


# In your terminal (not in Python) — all these are shell commands

# Create the environment (creates a folder named 'myenv')


python -m venv myenv

# Activate it — all pip installs now go ONLY into this environment


myenv\Scripts\activate # Windows
source myenv/bin/activate # macOS / Linux

# Your terminal prompt changes to show (myenv) at the start

# Deactivate when you are done with this project


deactivate

# NEVER commit the venv folder to git — add this to .gitignore


# myenv/

25.2 pip — Package Manager


# Install packages
pip install numpy
pip install torch torchvision # PyTorch
pip install numpy pandas matplotlib # multiple at once
pip install numpy==1.24.0 # specific version

# See what is installed


pip list
pip show numpy # details about one specific package

# Uninstall
pip uninstall numpy

# [Link] — snapshot of all dependencies for reproducibility


pip freeze > [Link] # save current environment state
pip install -r [Link] # recreate environment on another
machine

# Example [Link]:
# numpy==1.24.0
# pandas==2.0.1

Page 53
Python Complete Guide — Anumay Rai | IIIT Hyderabad

# scikit-learn==1.3.0
# torch==2.0.1

💡 IIIT Hyderabad lab tip


Always create one venv per project.
If a repo has [Link], run: pip install -r [Link] after activating.
This ensures you exactly match the package versions the project was developed with.

Page 54
🚦 Chapter 26: TheGuard
Python Complete Guide — Anumay Rai | IIIT Hyderabad

__name__ == '__main__'
Writing files that work as both scripts and modules

Every Python file has a built-in variable called __name__. When you run a file directly (python
[Link]), __name__ is set to '__main__'. When another file imports it, __name__ is set to the module's
own filename. This lets you write code that only runs when executed directly.

26.1 Why It Matters


# [Link]
def add(a, b): return a + b
def sub(a, b): return a - b

# WITHOUT the guard — this code runs even when someone imports [Link]:
# result = add(10, 5)
# print(result) # 15 — prints unexpectedly on import!

# WITH the guard — only runs when you directly execute: python [Link]
if __name__ == '__main__':
print(add(10, 5)) # 15 — only shows when run directly
print(sub(10, 5)) # 5

# In another file that imports it:


# import calculator
# [Link]() and [Link]() work fine
# The print statements above NEVER run on import

📝 Rule of thumb
Every Python script that might ever be imported should wrap its 'run' code inside:
if __name__ == '__main__':
All professional Python projects and libraries follow this convention.
It separates reusable library code (functions/classes) from script-level execution.

Page 55
🔗 Chapter 27: nonlocal & Closures
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Nested function scope and the closure pattern

Chapter 6.4 covered global. nonlocal is the same idea but for nested functions — it lets an inner
function modify a variable in its enclosing outer function's scope, without reaching all the way to global.

27.1 nonlocal vs global


# global — modifies a module-level variable
count = 0
def increment_global():
global count # reach all the way up to the module level
count += 1

# nonlocal — modifies an enclosing function's variable (not global)


def make_counter():
count = 0 # this lives in make_counter's local scope

def increment():
nonlocal count # refers to make_counter's 'count' — not global
count += 1
return count

return increment # return the inner function itself (a closure!)

counter = make_counter() # counter IS the increment function


print(counter()) # 1
print(counter()) # 2
print(counter()) # 3 — count persists between calls!

counter2 = make_counter() # INDEPENDENT counter — starts fresh at 0


print(counter2()) # 1
print(counter()) # 4 — original counter continues from where it left off

# This pattern — inner function remembering outer scope's state — is a


CLOSURE
# Decorators work using closures internally

Page 56
🦭 Chapter 28: Walrus Operator := (Python 3.8+)
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Assign and test in one expression

The walrus operator := assigns a value to a variable AND returns that value in one expression. This
avoids calling a function twice or writing an extra assignment line.

28.1 Usage Examples


import re

# Without walrus — search called, then result used in if


match = [Link](r'\d+', 'Room 404')
if match:
print([Link]()) # 404

# With walrus — assign and check in ONE line


if match := [Link](r'\d+', 'Room 404'):
print([Link]()) # 404

# while loop — avoid repeating input() call

# Without walrus (repeated code):


data = input('Enter value (blank to stop): ')
while data != '':
print('You entered:', data)
data = input('Enter value (blank to stop): ') # repeated!

# With walrus — cleaner, no repetition:


while (data := input('Enter value (blank to stop): ')) != '':
print('You entered:', data)

# In list comprehensions — compute once, use twice


results = [y for x in range(10) if (y := x**2) > 20]
print(results) # [25, 36, 49, 64, 81]
# Without walrus: x**2 would be computed twice (once for if, once for output)

Page 57
🔍 Chapter 29: Regular Expressions (re module)
Python Complete Guide — Anumay Rai | IIIT Hyderabad

Pattern matching, search, and extraction

29.1 Pattern Basics


import re

text = 'Phone: 9876543210, Email: anumay@[Link], Age: 21'

# [Link]() — find FIRST match anywhere, returns a match object or None


match = [Link](r'\d+', text) # \d = digit, + = one or more
if match:
print([Link]()) # '9876543210' — the matched text
print([Link]()) # 7 — starting index in the string

# [Link]() — find ALL matches, returns a list of strings


numbers = [Link](r'\d+', text)
print(numbers) # ['9876543210', '21']

# [Link]() — replace all matches with something else


cleaned = [Link](r'\d+', 'NUM', text)
print(cleaned) # 'Phone: NUM, Email: anumay@[Link], Age: NUM'

# [Link]() — split by a pattern


parts = [Link](r'[,\s]+', 'one, two, three')
print(parts) # ['one', 'two', 'three']

29.2 Capture Groups & Real Examples


# Capture groups () extract specific parts of a match
text = 'Born: 1995-08-22'
match = [Link](r'(\d{4})-(\d{2})-(\d{2})', text)
if match:
print([Link](0)) # '1995-08-22' — full match
print([Link](1)) # '1995' — first group
print([Link](2)) # '08' — second group
print([Link](3)) # '22' — third group

# Email validation
def is_valid_email(email: str) -> bool:
pattern = r'^[\w.-]+@[\w.-]+\.\w{2,}$'
return bool([Link](pattern, email))

print(is_valid_email('anumay@[Link]')) # True
print(is_valid_email('not-an-email')) # False

Pattern Matches
\d Any digit 0–9

Page 58
Python Complete Guide — Anumay Rai | IIIT Hyderabad

\w Word character: letter, digit, or _


\s Whitespace: space, tab, newline
. Any character except newline
^ Start of string
$ End of string
* 0 or more repetitions
+ 1 or more repetitions
? 0 or 1 (optional)
{n} Exactly n repetitions
[abc] Any one character in the set
[^abc] Any character NOT in the set
(group) Capture group — extract this part

Page 59
🔢 Chapter 30:Broadcasting
Python Complete Guide — Anumay Rai | IIIT Hyderabad

NumPy — Arrays, Slicing &


The foundation of all ML/data science

30.1 Creating Arrays


import numpy as np

# From lists
a = [Link]([1, 2, 3, 4, 5]) # 1D array — shape (5,)
b = [Link]([[1,2,3],[4,5,6]]) # 2D array — shape (2,3): 2 rows, 3
cols

print([Link]) # (5,) — 5 elements, 1 dimension


print([Link]) # (2, 3) — 2 rows, 3 columns
print([Link]) # int64 — data type
print([Link]) # 1 — number of dimensions

# Special arrays
[Link]((3, 4)) # 3×4 matrix of all zeros
[Link]((2, 3)) # 2×3 matrix of all ones
[Link](3) # 3×3 identity matrix
[Link](0, 10, 2) # [0 2 4 6 8] — like range() but returns array
[Link](0, 1, 5) # [0. 0.25 0.5 0.75 1.] — 5 evenly spaced points
[Link](3, 3) # 3×3 random values from standard normal
distribution

30.2 Indexing, Slicing & Reshaping


# 1D — same rules as Python lists
a = [Link]([10, 20, 30, 40, 50])
print(a[0]) # 10 — first element
print(a[-1]) # 50 — last element
print(a[1:4]) # [20 30 40]
print(a[::2]) # [10 30 50] — every 2nd element

# 2D — [row, col] (comma separated, not double brackets)


m = [Link]([[1,2,3],[4,5,6],[7,8,9]])
print(m[0, 1]) # 2 — row 0, column 1
print(m[1, :]) # [4 5 6] — entire row 1
print(m[:, 2]) # [3 6 9] — entire column 2
print(m[0:2, 1:3]) # [[2 3] — rows 0–1, columns 1–2
# [5 6]]

# Boolean indexing — select elements where condition is True


a = [Link]([1, 2, 3, 4, 5, 6])
print(a[a > 3]) # [4 5 6] — only elements greater than 3
print(a[a % 2 == 0]) # [2 4 6] — only even elements

Page 60
Python Complete Guide — Anumay Rai | IIIT Hyderabad

# reshape — change shape without changing data


a = [Link](12)
b = [Link](3, 4) # 3 rows, 4 columns
c = [Link](-1, 3) # -1 means 'figure out this dim' → shape (4,3)

30.3 Broadcasting & Vectorised Operations


Broadcasting lets you do arithmetic between arrays of different shapes. NumPy automatically 'stretches'
the smaller array. This replaces explicit for-loops and is much faster.

# Scalar broadcast — one value applied to every element


a = [Link]([1, 2, 3, 4])
print(a * 2) # [2 4 6 8] — 2 is broadcast to match shape of a
print(a + 10) # [11 12 13 14]
print(a ** 2) # [1 4 9 16]

# Broadcasting rule: shapes are compatible if equal OR one dimension is 1

# 2D array + 1D row vector: row applied to every row


matrix = [Link]((3, 4))
row = [Link]([10, 20, 30, 40]) # shape (4,)
print(matrix + row)
# [[11. 21. 31. 41.]
# [11. 21. 31. 41.] ← row is broadcast: added to each of 3 rows
# [11. 21. 31. 41.]]

# Vectorised comparisons — produces boolean array


data = [Link]([0.5, 1.2, 0.8, 2.1, 0.3])
print(data > 1.0) # [False True False True False]
print(data[data > 1.0]) # [1.2 2.1] — filtered via boolean indexing

30.4 Matrix Operations


a = [Link]([[1,2],[3,4]])
b = [Link]([[5,6],[7,8]])

# Element-wise multiplication (NOT matrix multiply)


print(a * b) # [[5 12][21 32]] — each element multiplied individually

# Matrix multiply — @ operator (or [Link])


print(a @ b) # [[19 22][43 50]]
# Computed as: [1×5+2×7, 1×6+2×8] = [19, 22] for first row

# Transpose — swap rows and columns


print(a.T) # [[1 3][2 4]]

# Aggregation
data = [Link]([[1,2,3],[4,5,6]])
print([Link]()) # 21 — sum of ALL elements

Page 61
Python Complete Guide — Anumay Rai | IIIT Hyderabad

print([Link](axis=0)) # [5 7 9] — sum DOWN each column


print([Link](axis=1)) # [6 15] — sum ACROSS each row
print([Link]()) # 3.5
print([Link]()) # 6
print([Link]()) # 5 — flat index of the maximum value

You now have the complete Python foundation, Anumay!


IIIT Hyderabad • SPCRC Lab • Python Complete Guide 2026
🚀

Page 62

You might also like