0% found this document useful (0 votes)
3 views36 pages

Python Practical Course

The document outlines a comprehensive self-study workbook for learning Python, structured into 12 modules covering fundamental concepts to advanced applications, including AI. Each module includes practical examples, exercises, and common mistakes to help learners grasp programming effectively. The course emphasizes hands-on practice by encouraging readers to type and run every example provided.
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)
3 views36 pages

Python Practical Course

The document outlines a comprehensive self-study workbook for learning Python, structured into 12 modules covering fundamental concepts to advanced applications, including AI. Each module includes practical examples, exercises, and common mistakes to help learners grasp programming effectively. The course emphasizes hands-on practice by encouraging readers to type and run every example provided.
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

The Practical Python Course


From First Variable to AI-Powered Applications

A compact, example-driven path to becoming job-ready


12 Modules • Real Projects • Line-by-Line Explanations

A complete self-study workbook — read it, type every example, build every project.
BEFORE YOU BEGIN

How to Use This Book


This book is written the way a good instructor teaches in a classroom: every topic starts with the idea, then the syntax, then code
you can run, explained one line at a time, with the output and a real-world reason for caring. Nothing here is meant to be
skimmed. The fastest way to learn programming is to type every example yourself, run it, break it on purpose, and fix it.

The shape of every topic

Section What it gives you

Concept Plain-language idea, why it exists, where it shows up in real projects.

Syntax The exact form you must type.

Example 1 & 2 A basic and a practical example, each explained line by line with output.

Common Mistakes The errors almost every beginner hits, so you can skip the pain.

Exercise + Solution You try first; then check against a fully explained answer.

Setup in 3 minutes Install Python from [Link]/downloads (tick “Add Python to PATH” on Windows). Install the free VS
Code editor and its Python extension. Create a file ending in .py , type code, and press the ▶ Run button. To run from a
terminal, type python [Link] . That is the entire toolchain you need for the whole book.
CONTENTS

Table of Contents
Module 1 — Python Fundamentals
Variables • Data Types • Input/Output • Type Conversion

Module 2 — Operators
Arithmetic • Comparison • Logical • Assignment

Module 3 — Conditions
if • if/else • elif • nested if • Project: Student Grade Calculator

Module 4 — Loops
for • while • break • continue • Project: Number Analyzer

Module 5 — Strings
Indexing • Slicing • Methods • Formatting • Project: Password Strength Checker

Module 6 — Lists
CRUD • Methods • Nested Lists • Comprehensions • Project: Shopping Cart

Module 7 — Tuples, Sets & Dictionaries


Each structure with examples • Project: Student Information System

Module 8 — Functions
Parameters • Arguments • Return • Scope • Recursion • Project: Calculator

Module 9 — Object-Oriented Programming


Classes • Objects • Constructor • Inheritance • Polymorphism • Encapsulation • Project: Bank System

Module 10 — Exceptions & File Handling


try/except/finally • Reading/Writing files • Project: Student Report Generator

Module 11 — Modules, JSON & APIs


import • JSON • requests • Project: Weather Information App

Module 12 — Python for AI


NumPy • Pandas • OpenAI API • LLM & RAG basics • Project: AI Chatbot

Final Project — AI Student Assistant


Architecture • Folder structure • Every file and function explained
MODULE 1

Python Fundamentals
Every program, no matter how advanced, is built from four humble actions: store a value, know what kind of value it is, talk to
the user, and change a value from one kind to another. Master these four and you have the vocabulary for everything that
follows.

1.1 Variables
Concept
A variable is a name that points to a value stored in the computer's memory. Think of memory as a giant wall of numbered
lockers. A value lives inside a locker; the variable name is the sticky-note label you put on it so you never have to remember the
locker number. Variables exist so that humans can work with names ( price , username ) instead of raw memory addresses.

Why it is used: programs must remember things — a user's name, a running total, the result of a calculation. Where in real
projects: a shopping site stores cart_total ; a game stores player_health ; a bank app stores balance . Practically every line of
real code touches a variable.

Syntax

variable_name = value

Example 1 — Basic

1 name = "John"
2 print(name)

Dissecting name = "John" exactly as asked:


name → the variable: a label we invented. It is not special to Python; we could have called it x . It now refers to whatever sits on its right.
= → the assignment operator. It does not mean “equals” in the math sense. It means “take the value on the right and make the name on the
left point to it.” Always reads right-to-left.
"John" → the value, specifically a string (text). The double quotes tell Python “this is text, not a command.”
What happens in memory: Python creates the text object "John" somewhere in memory, then makes the label name point at that location.
name doesn't contain "John"; it references it.
Line 2 — print(name) : print is a built-in function that displays things on screen; the parentheses pass it the value to show. We pass
name , so Python follows the label, finds "John", and prints it.

OUTPUT
John

Output explained: the quotes are gone — they were only instructions to Python, never part of the value. The user sees the text
content alone.

Example 2 — Practical

1 price = 100
2 quantity = 3
3 total = price * quantity
4 print("Total bill:", total)

Line 1–2: two variables hold whole numbers (integers). Each name points to its own value in memory.
Line 3: * multiplies. Python evaluates the right side first ( 100 * 3 = 300 ), then a brand-new variable total is created pointing to 300 .
Line 4: print receives two items separated by a comma; it prints them on one line with a space between, so we get a label and a number
together.

OUTPUT

Total bill: 300

Real-world use: this is the exact pattern behind every invoice, cart subtotal, and payroll calculation.
Common Mistakes • Writing 100 = price (assignment is always name = value, never the reverse).
• Forgetting quotes around text: name = John makes Python hunt for a variable called John and crash with a NameError .
• Using a name before assigning it. A variable must be given a value before it is read.
Practice Exercise Create variables for your first_name and last_name , then print Full name: <first> <last> .
Solution
1 first_name = "Ada"
2 last_name = "Lovelace"
3 print("Full name:", first_name, last_name)

Line 1–2 store two strings. Line 3 prints a label and both names; print inserts a space between each comma-separated item,
giving Full name: Ada Lovelace .

1.2 Data Types


Concept
A data type is the kind of value a variable holds. Python needs to know the kind because the rules differ: you can multiply
numbers but you concatenate text; you can ask whether a condition is True or False but not whether a number is. Knowing types
prevents nonsense like adding the word "apple" to the number 5.

Where used: a form's age field is an int , a price is a float , a name is a str , an “is the user logged in?” flag is a bool , and a
“no value yet” placeholder is None .

Type Means Example value

str String — text "hello" , '42'

int Integer — whole number 7 , -300

float Decimal number 3.14 , 99.0

bool Truth value True , False

NoneType Absence of a value None

Example 1 — Basic

1 age = 25
2 pi = 3.14
3 city = "Paris"
4 is_student = True
5 print(type(age), type(pi), type(city), type(is_student))

Lines 1–4: four variables, four different types. Python decides the type automatically from how the value is written (no quotes + no dot =
int; a dot = float; quotes = str; the words True/False = bool).
Line 5: type(x) is a built-in that reports the type of its argument. We print all four.

OUTPUT

<class 'int'> <class 'float'> <class 'str'> <class 'bool'>

Output explained: Python confirms each value's category. <class '...'> is just Python's way of naming a type.

Example 2 — Practical

1 product = "Laptop"
2 price = 899.99
3 in_stock = True
4 discount = None
5 print(product, "costs", price, "| available:", in_stock, "| discount:", discount)

Line 4: None is a real value meaning “nothing decided yet” — perfect for a discount that hasn't been set.
Line 5: mixing types in one print is fine because print turns everything into text for display.

OUTPUT
Laptop costs 899.99 | available: True | discount: None

Real-world use: this mirrors one row of a product catalog in any e-commerce backend.
Common Mistakes • Writing true / false (lowercase) — Python requires capital True / False .
• Quoting a number you intend to do math with: "5" + "5" gives "55" (text joined), not 10 .
• Confusing None with 0 or "" — None means “no value,” not “zero.”
Practice Exercise Create one variable of each type (str, int, float, bool) describing a movie, then print each value together with its
type.
Solution
1 title = "Inception"
2 year = 2010
3 rating = 8.8
4 is_classic = True
5 print(title, type(title))
6 print(year, type(year))
7 print(rating, type(rating))
8 print(is_classic, type(is_classic))

Each print shows a value next to type(...) , confirming Python inferred str , int , float , and bool respectively from how
each literal was written.

1.3 Input and Output


Concept
Output is the program talking to the user (via print ); input is the user talking back (via input ). Without input a program can
only ever do the same thing; input is what makes software interactive — login screens, search boxes, and chatbots all begin here.

Critical rule: input() always returns a string, even if the user types digits. We convert it when we need a number (see 1.4).

Syntax

value = input("prompt shown to user: ")


print("text", variable, "more text")

Example 1 — Basic

1 name = input("What is your name? ")


2 print("Hello,", name)

Line 1: input(...) prints the prompt, pauses, and waits for the user to type and press Enter. Whatever they type comes back as a string
and is stored in name .
Line 2: we greet them using the captured value.

OUTPUT (USER TYPED: SARA)


What is your name? Sara Hello, Sara

Example 2 — Practical

1 num1 = int(input("Enter first number: "))


2 num2 = int(input("Enter second number: "))
3 print("Sum =", num1 + num2)

Lines 1–2: input(...) hands back text like "8" ; int(...) wraps it and converts that text into the whole number 8 so arithmetic will
work. This “wrap and convert” pattern is everywhere.
Line 3: now that both are real integers, + adds them numerically.

OUTPUT (USER TYPED: 8 AND 5)


Enter first number: 8 Enter second number: 5 Sum = 13

Real-world use: any calculator, quiz, or data-entry tool reads typed input and converts it before computing.

Common Mistakes • Forgetting int() / float() around numeric input → "8" + "5" becomes "85" .
• Calling int(input(...)) when the user types letters → crashes with ValueError .
• Forgetting the space at the end of the prompt string, so the cursor jams against the text.
Practice Exercise Ask the user for their birth year, then print how old they will turn this year (use 2026).
Solution

1 year = int(input("Birth year: "))


2 age = 2026 - year
3 print("You turn", age, "this year.")

Line 1 converts the typed text to an int. Line 2 subtracts to get the age. Line 3 reports it. If the user types 2000 , output is You
turn 26 this year.

1.4 Type Conversion


Concept
Type conversion (also called casting) turns a value of one type into another: text to number, number to text, and so on. It exists
because data often arrives in the wrong shape — input is text, but you need a number; a number must become text to glue it into
a sentence. Real projects convert types constantly when reading forms, files, and API responses.

Syntax

int("42") # str -> int = 42


float("3.5") # str -> float = 3.5
str(42) # int -> str = "42"
bool(0) # int -> bool = False

Example 1 — Basic

1 text_number = "100"
2 real_number = int(text_number)
3 print(real_number + 50)

Line 1: "100" looks like a number but is text — you cannot do math with it.
Line 2: int(...) reads the text and produces the integer 100 , stored in a new variable.
Line 3: now arithmetic works: 100 + 50 .

OUTPUT

150

Example 2 — Practical

1 score = 95
2 message = "Your score is " + str(score) + " points"
3 print(message)

Line 1: score is an integer.


Line 2: you cannot join text and a number with + directly — Python refuses to mix the two. str(score) converts 95 into "95" so all
three pieces are strings and join cleanly.
Line 3: prints the assembled sentence.

OUTPUT

Your score is 95 points

Real-world use: building messages, log lines, and labels almost always needs number→string conversion.
Common Mistakes • "5 apples" cannot become an int — int("5 apples") crashes. Only clean numeric text converts.
• int("3.5") fails; use float("3.5") first, then int(...) to truncate if needed.
• Forgetting that int(3.9) gives 3 — it truncates, it does not round.
Practice Exercise Ask for a price as text, apply 10% tax, and print the final price as a sentence.
Solution

1 price = float(input("Price: "))


2 final = price * 1.10
3 print("Final price with tax: " + str(round(final, 2)))

Line 1 converts the typed text to a decimal. Line 2 adds 10% by multiplying by 1.10. Line 3 rounds to 2 decimals and converts the
number back to text so it can be joined into the sentence. Input 200 → Final price with tax: 220.0 .
MODULE 2

Operators
An operator is a symbol that performs an action on values, called operands. If variables are nouns, operators are the verbs. There
are four families you will use constantly: arithmetic (math), comparison (asking questions), logical (combining questions), and
assignment (storing results).

2.1 Arithmetic Operators


Concept
These do mathematics: + - * / plus three special ones — // floor division (divide and drop the remainder), % modulo (keep
only the remainder), and ** power. Where used: totals, averages, splitting bills ( // ), detecting even/odd or “every Nth item”
( % ), and compound interest ( ** ).

Example 1 — line by line

1 a = 17
2 b = 5
3 print(a + b, a - b, a * b)
4 print(a / b, a // b, a % b, a ** 2)

Line 3: sum 22, difference 12, product 85.


Line 4: a / b = 3.4 (true division always gives a float); a // b = 3 (drops the .4); a % b = 2 (the leftover after taking 3 fives out of 17);
a ** 2 = 289 (17 squared).

OUTPUT
22 12 85 3.4 3 2 289

Example 2 — practical (even/odd check uses %)

1 number = 24
2 remainder = number % 2
3 print("Remainder when divided by 2:", remainder)

Line 2: % gives 0 for even numbers and 1 for odd. 24 is even, so remainder is 0. This single trick powers most “is it even / every 3rd row /
leap-year” logic.

OUTPUT
Remainder when divided by 2: 0

Common Mistakes • Expecting / to give a whole number — it always returns a float ( 10/2 is 5.0 ).
• Confusing // (drop remainder) with % (keep only remainder).
Practice Exercise Given seconds = 200 , print how many whole minutes and leftover seconds that is.
Solution

1 seconds = 200
2 minutes = seconds // 60
3 rest = seconds % 60
4 print(minutes, "min", rest, "sec")

// gives 3 whole minutes; % gives the leftover 20 seconds. Output: 3 min 20 sec .

2.2 Comparison Operators


Concept
These ask a yes/no question and always answer with a bool : == equal, != not equal, > greater, < less, >= , <= . They are the
engine of every decision a program makes (logins, eligibility, filtering).

Example 1 — line by line

1 x = 10
2 y = 7
3 print(x == y, x != y, x > y, x <= y)

Line 3: x == y → False (10 is not 7); x != y → True; x > y → True; x <= y → False. Note == (two equals) is a question; = (one
equals) is assignment — mixing them is the #1 beginner bug.
OUTPUT
False True True False

Example 2 — practical (age gate)

1 age = 20
2 can_vote = age >= 18
3 print("Eligible to vote:", can_vote)

Line 2: the comparison produces a boolean stored directly in can_vote . This is exactly how feature flags and permission checks are built.

OUTPUT
Eligible to vote: True

Common Mistakes • Using = instead of == inside a condition.


• Comparing different types loosely — "10" == 10 is False because text is not the number.
Practice Exercise Store two test scores and print whether the first is at least as high as the second.
Solution

1 s1 = 88
2 s2 = 90
3 print("First >= second:", s1 >= s2)

The comparison evaluates to False (88 is not ≥ 90). Output: First >= second: False .

2.3 Logical Operators


Concept
and , or , not combine yes/no answers. and is True only if both sides are True; or is True if at least one side is True; not flips
a value. They let you express real rules: “user is logged in and is an admin.”

Example 1 — line by line

1 a = True
2 b = False
3 print(a and b, a or b, not a)

Line 3: a and b → False (b is False); a or b → True (a is True); not a → False (flip of True).

OUTPUT
False True False

Example 2 — practical (loan rule)

1 age = 30
2 income = 45000
3 eligible = age >= 21 and income >= 30000
4 print("Loan eligible:", eligible)

Line 3: two comparisons each produce a boolean, then and requires both to be True. Age check is True, income check is True, so the
whole expression is True.

OUTPUT
Loan eligible: True

Common Mistakes • Writing age >= 21 and 30000 — each side of and must be a full condition, not a bare number.
• Using && / || (other languages) — Python uses the words and / or .
Practice Exercise A user may enter if they have a ticket or are on the guest list. Model both flags and print whether they may
enter.
Solution

1 has_ticket = False
2 on_guestlist = True
3 print("May enter:", has_ticket or on_guestlist)

or needs only one True side; the guest list is True, so the result is True.

2.4 Assignment Operators


Concept
= stores a value. The compound forms ( += , -= , *= , /= ) do an operation and re-store the result in one step. total += 5 is
shorthand for total = total + 5 . They keep running totals and counters clean and readable — the backbone of every loop that
accumulates a result.

Example 1 — line by line

1 score = 0
2 score += 10
3 score += 5
4 print(score)

Line 1: start at 0. Line 2: += adds 10 and re-stores → 10. Line 3: adds 5 → 15. The variable is updated in place each time.

OUTPUT
15

Example 2 — practical (shopping total)

1 total = 0
2 total += 250 # bread
3 total += 99 # milk
4 total *= 1.05 # add 5% tax
5 print("Pay:", round(total, 2))

Lines 2–3: accumulate item prices. Line 4: *= multiplies the whole running total by 1.05, applying tax in place. Line 5: rounds for
display.

OUTPUT
Pay: 366.45

Common Mistakes • Forgetting to initialise the variable first ( total += 5 before total exists → error).
• Reading += as “equals plus” — it is “add, then store back.”
Practice Exercise Start a counter at 100, subtract 30, then halve it using compound operators, and print the result.
Solution

1 value = 100
2 value -= 30
3 value /= 2
4 print(value)

-= gives 70; /= divides by 2 giving 35.0 (division yields a float). Output: 35.0 .
MODULE 3

Conditions
A condition lets a program choose what to do. Up to now code ran top to bottom, every line, always. Conditions break that
straight line so a program can react: show a discount only if the cart is big enough, deny a login if the password is wrong. The key
idea is indentation — Python uses spaces (4 by convention) to mark which lines belong inside a decision.

3.1 if
Concept & Syntax
if runs a block only when its condition is True. Where used: input validation, gating features, alerts.

if condition:
# runs only when condition is True (note the 4-space indent)

Example 1

1 temperature = 38
2 if temperature > 37.5:
3 print("You have a fever.")

Line 2: the condition 38 > 37.5 is True, and the colon : opens the block.
Line 3: indented, so it belongs to the if ; it runs because the condition was True.

OUTPUT
You have a fever.

3.2 if / else
else provides the “otherwise” path — it runs when the if condition is False, so exactly one of the two blocks always runs.

Example 2 — practical

1 password = input("Password: ")


2 if password == "secret123":
3 print("Access granted")
4 else:
5 print("Access denied")

Line 2: compares typed text to the stored password with == .


Lines 3 vs 5: only one runs. If the strings match exactly (case included), access is granted; otherwise the else block denies it.

OUTPUT (WRONG PASSWORD TYPED)

Password: hello Access denied

3.3 elif & 3.4 nested if


elif (“else if”) checks more conditions in order; the first True one wins and the rest are skipped. A nested if is an if inside
another if , used when a second question only makes sense after the first is answered.

1 marks = 82
2 if marks >= 90:
3 print("Grade A")
4 elif marks >= 75:
5 print("Grade B")
6 else:
7 print("Grade C")

Line 2: 82 ≥ 90 is False, skip. Line 4: 82 ≥ 75 is True → print B and skip the rest. Order matters: Python stops at the first match.

OUTPUT
Grade B

Common Mistakes • Forgetting the colon : at the end of if/elif/else lines.


• Inconsistent indentation (mixing tabs and spaces) → IndentationError .
• Putting the widest condition first in an elif chain so narrower ones can never be reached.

3.5 Project — Student Grade Calculator


This project ties together input, conversion, comparison, and the full if/elif/else ladder — the exact logic inside any school's
report-card software.

1 print("=== Student Grade Calculator ===")


2 name = input("Student name: ")
3 marks = float(input("Marks obtained (0-100): "))
4
5 if marks < 0 or marks > 100:
6 print("Invalid marks entered.")
7 else:
8 if marks >= 90:
9 grade = "A"
10 elif marks >= 75:
11 grade = "B"
12 elif marks >= 60:
13 grade = "C"
14 elif marks >= 40:
15 grade = "D"
16 else:
17 grade = "F"
18
19 print(name, "scored", marks, "=> Grade", grade)
20 if grade == "F":
21 print("Result: FAIL. Please reattempt.")
22 else:
23 print("Result: PASS. Well done!")

Line 1: a header so the user knows what the program does.


Line 2: capture the name as text.
Line 3: capture marks as text, then float(...) converts so we can compare numerically; float (not int ) allows decimals like 82.5.
Line 5: a validation guard — or catches marks below 0 or above 100. If either is true, we reject the input on line 6.
Line 7: else means the marks are valid, so we proceed to grading.
Lines 8–17: a nested if/elif/else ladder. Python tests top to bottom and assigns grade at the first threshold the marks reach; once one
matches, the others are skipped.
Line 19: report the result, mixing text and numbers in one print .
Lines 20–23: a second decision — a nested check on the computed grade to print PASS or FAIL.

OUTPUT (NAME: MAYA, MARKS: 82)


=== Student Grade Calculator === Student name: Maya Marks obtained (0-100): 82 Maya scored 82.0 => Grade B Result: PASS.
Well done!

Output explained: 82 fails the ≥90 test but passes ≥75, so grade B is assigned; since B is not F, the PASS branch runs.
Practice Exercise Extend the project: after the grade, also print “Distinction” if marks are 95 or above.
Solution

1 if marks >= 95:


2 print("Distinction awarded!")

Place this after line 19. It is an independent if (not part of the grade ladder) so it runs in addition to the grade message
whenever marks reach 95.
MODULE 4

Loops
A loop repeats a block of code so you don't write it a thousand times. Whenever you hear “for each…”, “keep going until…”, or
“repeat N times,” you need a loop. They drive everything from printing a report's rows to retrying a network call.

4.1 for loop


Concept & Syntax
A for loop walks through a sequence (a range of numbers, a list, the characters of a string) and runs its body once per item.
range(n) produces the numbers 0,1,…,n-1.

for item in sequence:


# body runs once for each item

Example 1

1 for i in range(5):
2 print("Count:", i)

Line 1: range(5) yields 0,1,2,3,4; each value is placed into i in turn.


Line 2: runs five times, once per value of i .

OUTPUT
Count: 0 Count: 1 Count: 2 Count: 3 Count: 4

Example 2 — practical (sum of a list)

1 prices = [120, 80, 50]


2 total = 0
3 for p in prices:
4 total += p
5 print("Total:", total)

Line 2: initialise an accumulator at 0 before the loop.


Lines 3–4: each price p is added into total with the compound operator from Module 2. After three passes, total holds 250.
Line 5: printed after the loop (un-indented), so it runs once at the end.

OUTPUT
Total: 250

4.2 while loop


A while loop repeats as long as a condition stays True. Use it when you don't know in advance how many repetitions you need —
e.g. “keep asking until the user types a valid answer.”

1 count = 3
2 while count > 0:
3 print("Launch in", count)
4 count -= 1
5 print("Lift off!")

Line 2: the condition is checked before each pass; while count is positive, the body runs.
Line 4: crucial — we shrink count each time so the condition eventually becomes False. Without this the loop never ends.

OUTPUT
Launch in 3 Launch in 2 Launch in 1 Lift off!

4.3 break & 4.4 continue


break exits the loop immediately. continue skips the rest of the current pass and jumps to the next item.
1 for n in range(1, 11):
2 if n == 7:
3 break
4 if n % 2 == 0:
5 continue
6 print(n)

Lines 2–3: when n reaches 7, break ends the loop entirely.


Lines 4–5: for even numbers, continue skips the print and moves on.
Line 6: only reached by odd numbers below 7.

OUTPUT
1 3 5

Common Mistakes • Infinite loop: forgetting to change the while condition variable.
• Off-by-one with range : range(1, 5) stops at 4, not 5.
• Indenting the “after loop” line so it accidentally runs every pass.

4.5 Project — Number Analyzer


Reads several numbers and reports count, sum, average, largest, smallest, and how many are even — the core of any statistics or
reporting tool.

1 how_many = int(input("How many numbers? "))


2 total = 0
3 largest = None
4 smallest = None
5 even_count = 0
6
7 for i in range(how_many):
8 num = float(input("Enter number: "))
9 total += num
10 if largest is None or num > largest:
11 largest = num
12 if smallest is None or num < smallest:
13 smallest = num
14 if num % 2 == 0:
15 even_count += 1
16
17 average = total / how_many
18 print("Count:", how_many)
19 print("Sum:", total)
20 print("Average:", round(average, 2))
21 print("Largest:", largest)
22 print("Smallest:", smallest)
23 print("Even numbers:", even_count)

Line 1: ask how many numbers, convert to int so it can drive the loop.
Lines 2–5: set up accumulators. largest / smallest start as None so the very first number can become both (we have no real value to
compare against yet).
Line 7: loop exactly how_many times.
Line 8: read each number as a float.
Line 9: add to the running total.
Lines 10–11: update largest if it is still unset ( is None ) or the new number beats it. is None is the correct way to test for “no value
yet.”
Lines 12–13: mirror logic for the smallest.
Lines 14–15: count evens using the modulo trick from Module 2.
Line 17: average = sum ÷ count, computed after the loop.
Lines 18–23: print the full report; round(...,2) keeps the average tidy.

OUTPUT (ENTERED 4, 10, 7, 8, 3)


How many numbers? 4 Enter number: 10 Enter number: 7 Enter number: 8 Enter number: 3 Count: 4 Sum: 28.0 Average: 7.0
Largest: 10.0 Smallest: 3.0 Even numbers: 2

Practice Exercise Add a count of how many numbers were greater than the average. (Hint: you must store the numbers first.)
Solution
1 nums = []
2 for i in range(how_many):
3 [Link](float(input("Enter number: ")))
4 average = sum(nums) / len(nums)
5 above = 0
6 for n in nums:
7 if n > average:
8 above += 1
9 print("Above average:", above)

We must keep the numbers in a list (Module 6) because we can only know the average after seeing them all, then loop a second
time to count those above it.
MODULE 5

Strings
A string is text — a sequence of characters. Since almost everything a user types or sees is text, strings are the most-handled
data type in real software: names, messages, file contents, API responses, search queries. This module covers reaching into a
string (indexing/slicing), transforming it (methods), and building it (formatting).

5.1 Indexing
Each character has a position number called an index, starting at 0. Negative indexes count from the end ( -1 is the last
character).

1 word = "PYTHON"
2 print(word[0], word[2], word[-1])

Line 2: word[0] is 'P' (first), word[2] is 'T' (third — remember counting starts at 0), word[-1] is 'N' (last).

OUTPUT
P T N

5.2 Slicing
Slicing extracts a substring with text[start:stop] — from start up to but not including stop .

1 email = "john@[Link]"
2 print(email[0:4])
3 print(email[5:])

Line 2: indexes 0,1,2,3 → "john" (index 4, the '@', is excluded).


Line 3: omitting stop means “to the end,” giving everything from index 5 onward.

OUTPUT
john [Link]

5.3 Methods
A method is a built-in action attached to a value, called with a dot: [Link]() . Strings come with dozens; these are the
everyday ones.

Method Does Example → result

.upper() ALL CAPS "hi".upper() → "HI"

.lower() all lowercase "HI".lower() → "hi"

.strip() trim outer spaces " hi ".strip() → "hi"

.replace(a,b) swap text "a-b".replace("-"," ") → "a b"

.split(sep) break into a list "a,b".split(",") → ["a","b"]

.find(x) index of x (or -1) "abc".find("b") → 1

len(s) length (a function) len("abc") → 3

Example — practical (clean a username)

1 raw = " John_Doe "


2 clean = [Link]().lower().replace("_", " ")
3 print("[" + clean + "]")

Line 2: methods chain left to right: strip() removes the outer spaces, lower() lowercases, replace turns the underscore into a space.
Each method returns a new string the next one acts on.
Line 3: brackets prove no stray spaces remain.

OUTPUT
[john doe]

5.4 Formatting (f-strings)


An f-string (prefix f ) lets you drop variables straight into text inside { } . It is the modern, readable way to build messages.

1 name = "Sara"
2 score = 91.5
3 print(f"{name} scored {score}% (rounded: {score:.0f}%)")

Line 3: {name} and {score} are replaced by their values; {score:.0f} formats the number with 0 decimals. Far cleaner than gluing
strings with + and str() .

OUTPUT

Sara scored 91.5% (rounded: 92%)

Common Mistakes • Expecting methods to change the original — strings are immutable; methods return a new string, so you
must capture it ( s = [Link]() ).
• Forgetting the f prefix → {name} prints literally.
• Index out of range: "hi"[5] crashes — valid indexes here are 0 and 1.

5.5 Project — Password Strength Checker


Scores a password by length and character variety — the same idea behind the strength meter on signup forms.

1 pwd = input("Enter a password: ")


2 score = 0
3 has_upper = False
4 has_digit = False
5 has_symbol = False
6 symbols = "!@#$%^&*()-_+"
7
8 if len(pwd) >= 8:
9 score += 1
10
11 for ch in pwd:
12 if [Link]():
13 has_upper = True
14 elif [Link]():
15 has_digit = True
16 elif ch in symbols:
17 has_symbol = True
18
19 if has_upper: score += 1
20 if has_digit: score += 1
21 if has_symbol: score += 1
22
23 if score <= 1:
24 strength = "Weak"
25 elif score <= 2:
26 strength = "Medium"
27 else:
28 strength = "Strong"
29
30 print(f"Password strength: {strength} ({score}/4)")

Line 1: read the password as text.


Lines 2–5: a score counter and three boolean flags, all starting at their “not seen yet” state.
Line 6: a string of characters we count as symbols; in will test membership against it later.
Lines 8–9: length rule — 8+ characters earns a point.
Line 11: loop over each character of the password (strings are iterable).
Lines 12–17: classify the character: .isupper() detects capitals, .isdigit() detects 0–9, and ch in symbols checks the symbol set.
Each sets its flag True. We only need to know each type appeared at least once.
Lines 19–21: award one point per variety present. (Multiple statements after a colon on one line are legal for short bodies.)
Lines 23–28: map the 0–4 score onto a label with an if/elif/else ladder.
Line 30: an f-string reports the verdict and the raw score.

OUTPUT (TYPED: HELLO@2026)

Enter a password: Hello@2026 Password strength: Strong (4/4)

Output explained: length ≥ 8 (point), has an uppercase H (point), has digits (point), has '@' (point) → 4/4 → Strong.

Practice Exercise Add a rule: subtract a point and warn if the password contains the word "password" (any casing).
Solution
1 if "password" in [Link]():
2 score -= 1
3 print("Warning: avoid the word 'password'.")

[Link]() makes the check case-insensitive, and in tests whether the substring appears anywhere. Place this before the
strength mapping so the lowered score is reflected.
MODULE 6

Lists
A list stores many values in one ordered, changeable container. Instead of item1 , item2 , item3 , you keep them together and
loop over them. Lists are the workhorse collection of Python: a cart's items, a file's lines, rows from a database, search results —
all lists.

6.1 CRUD Operations


CRUD = Create, Read, Update, Delete — the four things you do to stored data everywhere in software.

1 fruits = ["apple", "banana"] # CREATE


2 print(fruits[0]) # READ -> apple
3 [Link]("cherry") # CREATE (add to end)
4 fruits[1] = "mango" # UPDATE (replace banana)
5 [Link]("apple") # DELETE by value
6 print(fruits)

Line 1: square brackets create a list of two strings, in order.


Line 2: read by index — position 0 is the first item.
Line 3: .append(x) adds x to the end, growing the list.
Line 4: assign to an index to overwrite that slot — lists are mutable (changeable in place), unlike strings.
Line 5: .remove(v) deletes the first item equal to v .

OUTPUT
apple ['mango', 'cherry']

6.2 Useful Methods


Method Action

.append(x) add one item to the end

.insert(i, x) insert x at position i

.pop(i) remove & return item at i (last if no i)

.sort() / .reverse() sort ascending / flip order (in place)

len(list) / sum(list) count / total of items

x in list True if x is present

6.3 Nested Lists


A list can hold other lists — a nested list — perfect for grids and tables (rows of columns).

1 grid = [[1, 2], [3, 4], [5, 6]]


2 print(grid[1]) # the second row
3 print(grid[1][0]) # first item of the second row

Line 2: grid[1] selects the inner list [3, 4] .


Line 3: a second index reaches inside that row: row 1, column 0 → 3.

OUTPUT
[3, 4] 3

6.4 List Comprehensions


A comprehension builds a new list in one line: [expression for item in sequence if condition] . It replaces a whole loop and is
the Pythonic way to transform/filter data.

1 numbers = [1, 2, 3, 4, 5, 6]
2 squares_of_even = [n*n for n in numbers if n % 2 == 0]
3 print(squares_of_even)

Line 2: read it as: “for each n in numbers, keep only those where n is even, and put n*n into the new list.” The if filters, the
expression transforms.
OUTPUT
[4, 16, 36]

Common Mistakes • .append() returns None ; never write fruits = [Link]("x") — it wipes your list.
• Index out of range when the list is shorter than you think; check len() .
• Two variables pointing at the same list: editing one edits “both.” Use new = old[:] to copy.

6.5 Project — Shopping Cart System


A menu-driven cart using a list of items (each item itself a small list of name + price) — the data model behind any online store
cart.

1 cart = []
2 while True:
3 print("\n1) Add 2) Remove 3) View 4) Checkout")
4 choice = input("Choose: ")
5
6 if choice == "1":
7 name = input("Item name: ")
8 price = float(input("Price: "))
9 [Link]([name, price])
10 print(f"Added {name}.")
11 elif choice == "2":
12 name = input("Item to remove: ")
13 for item in cart:
14 if item[0] == name:
15 [Link](item)
16 print(f"Removed {name}.")
17 break
18 elif choice == "3":
19 if not cart:
20 print("Cart is empty.")
21 for item in cart:
22 print(f" - {item[0]}: {item[1]}")
23 elif choice == "4":
24 total = sum(item[1] for item in cart)
25 print(f"Items: {len(cart)} | Total: {round(total, 2)}")
26 break
27 else:
28 print("Invalid choice.")

Line 1: start with an empty cart list.


Line 2: while True loops forever until we break — the standard “menu loop.”
Line 3: \n prints a blank line for spacing; show the menu each cycle.
Lines 6–10: ADD — read name and price, then append a two-element list [name, price] as one cart entry.
Lines 11–17: REMOVE — loop the cart; item[0] is the name. On a match, remove that entry and break so we stop after the first hit (and
avoid editing the list while still looping over it).
Lines 18–22: VIEW — if not cart is True when the list is empty; otherwise print each item's name and price.
Lines 23–26: CHECKOUT — sum(item[1] for item in cart) adds every price (index 1 of each entry) using a generator expression, prints
the bill, and breaks out of the menu.
Lines 27–28: any other key is rejected.

OUTPUT (ADD PEN 20, ADD BOOK 150, CHECKOUT)


1) Add 2) Remove 3) View 4) Checkout Choose: 1 Item name: Pen Price: 20 Added Pen. ... (add Book 150) ... Choose: 4 Items: 2
| Total: 170.0

Practice Exercise Add option 5 to apply a discount code "SAVE10" that prints the total reduced by 10%.
Solution

1 elif choice == "5":


2 code = input("Code: ")
3 total = sum(item[1] for item in cart)
4 if code == "SAVE10":
5 total *= 0.90
6 print(f"Total after discount: {round(total, 2)}")

We recompute the total, then multiply by 0.90 (i.e. keep 90%) only when the code matches. For the cart above, 170 → 153.0.
MODULE 7

Tuples, Sets & Dictionaries


Lists are not the only collection. Three more cover different needs: tuples (fixed data that must not change), sets (unique items,
fast membership tests), and dictionaries (label → value lookups). Choosing the right one makes code clearer and faster.

7.1 Tuples
A tuple is like a list but immutable — once created it cannot change. Use it for data that should stay fixed: coordinates, RGB
colours, a database record. The fixedness is a safety feature.

1 point = (3, 5)
2 x, y = point # unpacking
3 print("x:", x, "y:", y)
4 # point[0] = 9 # would crash: tuples can't be changed

Line 1: round brackets create a tuple of two values.


Line 2: unpacking — Python assigns each tuple element to a variable in one move; x gets 3, y gets 5. Very common for returning multiple
values.
Line 4: commented out because assigning to a tuple element raises TypeError — that immutability is the whole point.

OUTPUT
x: 3 y: 5

7.2 Sets
A set holds only unique items and is unordered. It instantly removes duplicates and checks membership faster than a list. Use it
for tags, unique visitors, or “have I seen this already?”

1 visitors = {"ann", "bob", "ann", "cara"}


2 print(visitors)
3 print("bob" in visitors)
4 a = {1, 2, 3}; b = {2, 3, 4}
5 print(a & b, a | b, a - b)

Lines 1–2: the duplicate "ann" is automatically dropped — a set keeps one of each.
Line 3: membership test, very fast even with millions of items.
Line 5: set maths — & intersection (in both), | union (in either), - difference (in a but not b).

OUTPUT
{'ann', 'bob', 'cara'} True {2, 3} {1, 2, 3, 4} {1}

7.3 Dictionaries
A dictionary stores key → value pairs. Instead of remembering position numbers, you look things up by a meaningful label. This
is the single most important real-world structure: every JSON object, API response, and config file is a dictionary.

1 student = {"name": "Lee", "age": 20, "gpa": 3.8}


2 print(student["name"]) # READ by key
3 student["age"] = 21 # UPDATE
4 student["city"] = "Seoul" # CREATE new pair
5 del student["gpa"] # DELETE
6 print(student)
7 for key, value in [Link]():
8 print(f"{key} -> {value}")

Line 1: curly braces with key: value pairs.


Line 2: look up a value by its key — clearer than student[0] .
Lines 3–5: full CRUD: reassign a key, add a new key just by assigning to it, and del to remove one.
Lines 7–8: .items() yields each pair so we can loop over keys and values together.

OUTPUT
Lee {'name': 'Lee', 'age': 21, 'city': 'Seoul'} name -> Lee age -> 21 city -> Seoul

Common Mistakes • Accessing a missing key ( student["phone"] ) → KeyError ; use [Link]("phone") to get None instead.
• Trying to change a tuple, or expecting a set to keep insertion order or allow indexing.
• Using a list as a dictionary key — keys must be immutable (string, number, tuple).
7.4 Project — Student Information System
Stores many students as a list of dictionaries — exactly how records arrive from a database or API. Demonstrates all three
structures working together.

1 students = []
2 subjects_seen = set()
3
4 while True:
5 action = input("\n[a]dd [l]ist [s]earch [q]uit: ").lower()
6 if action == "a":
7 record = {
8 "name": input("Name: "),
9 "roll": int(input("Roll no: ")),
10 "marks": float(input("Marks: ")),
11 "subject": input("Subject: ")
12 }
13 [Link](record)
14 subjects_seen.add(record["subject"])
15 elif action == "l":
16 for s in students:
17 print(f"{s['roll']:>3} | {s['name']:<10} | {s['marks']:>5} | {s['subject']}")
18 print("Subjects offered:", subjects_seen)
19 elif action == "s":
20 target = int(input("Roll no to find: "))
21 found = None
22 for s in students:
23 if s["roll"] == target:
24 found = s
25 break
26 print(found if found else "No such student.")
27 elif action == "q":
28 print(f"Saved {len(students)} students. Bye!")
29 break

Line 1: a list that will hold dictionaries — one per student.


Line 2: a set to collect unique subjects automatically.
Line 5: read the menu choice and .lower() it so "A" and "a" both work.
Lines 7–12: build one student as a dictionary; each field is read and converted on the spot (roll → int, marks → float).
Line 13: append the whole dictionary as one element of the list.
Line 14: add the subject to the set; duplicates vanish on their own.
Lines 16–17: list every student. The f-string format specs align columns: :>3 right-aligns in 3 spaces, :<10 left-aligns in 10 — producing
a neat table.
Line 18: show the unique subjects gathered by the set.
Lines 20–26: search — loop until a roll number matches, store it in found , and break. Line 26 prints the record if found, else a message (a
compact conditional expression).
Lines 27–29: quit, reporting how many records exist via len() .

OUTPUT (ADD LEE/1/88/MATH, THEN LIST)


[a]dd [l]ist [s]earch [q]uit: a Name: Lee Roll no: 1 Marks: 88 Subject: Math [a]dd [l]ist [s]earch [q]uit: l 1 | Lee | 88.0
| Math Subjects offered: {'Math'}

Practice Exercise Add a "[t]op" command that prints the student with the highest marks.
Solution

1 elif action == "t":


2 if students:
3 top = max(students, key=lambda s: s["marks"])
4 print(f"Top: {top['name']} ({top['marks']})")

max(..., key=...) finds the dictionary whose "marks" value is largest; the lambda (a tiny inline function, see Module 8) tells
max which field to compare.
MODULE 8

Functions
A function is a named, reusable block of code. You define the steps once, then call it by name whenever you need them.
Functions stop you from copying code, make programs readable, and let you fix a bug in one place. Every real codebase is mostly
functions calling functions.

8.1 Basics, Parameters & Return


Syntax

def function_name(parameters):
# body
return value # optional: hands a result back

def defines a function. Parameters are the named inputs in the definition; arguments are the actual values you pass when
calling. return sends a result back to the caller; without it a function returns None .

Example 1 — basic

1 def greet(name):
2 return f"Hello, {name}!"
3
4 message = greet("Sam")
5 print(message)

Line 1: define greet with one parameter name .


Line 2: build and return a greeting string; control leaves the function here.
Line 4: call it with the argument "Sam" ; the returned string is stored in message .
Line 5: print it. The function can be reused with any name.

OUTPUT
Hello, Sam!

Example 2 — practical (default & multiple params)

1 def total_price(price, qty, tax=0.05):


2 subtotal = price * qty
3 return subtotal + subtotal * tax
4
5 print(total_price(100, 3))
6 print(total_price(100, 3, tax=0.18))

Line 1: three parameters; tax=0.05 is a default — used when the caller omits it.
Lines 2–3: compute subtotal then add tax, returning the final figure.
Line 5: only two arguments given, so tax defaults to 5% → 315.0.
Line 6: tax passed as a keyword argument (18%), overriding the default → 354.0.

OUTPUT
315.0 354.0

8.2 Scope
Scope is where a variable is visible. Variables created inside a function are local — they exist only during that call and cannot be
seen outside. This isolation prevents functions from accidentally clobbering each other's data.

1 def calc():
2 result = 42 # local
3 return result
4 calc()
5 # print(result) # crashes: result is not visible here

Line 2: result lives only inside calc .


Line 5: referencing it outside raises NameError — proof of scope isolation.

8.3 Recursion
Recursion is a function that calls itself to solve a smaller version of the same problem. It needs a base case (a stopping point) or
it loops forever. Great for naturally nested problems: factorials, folders within folders, tree menus.

1 def factorial(n):
2 if n <= 1: # base case
3 return 1
4 return n * factorial(n - 1) # recursive step
5
6 print(factorial(5))

Lines 2–3: the base case — factorial of 0 or 1 is 1; this stops the chain.
Line 4: otherwise return n × factorial of n-1 . So factorial(5) = 5×4×3×2×1.
Line 6: the calls stack and unwind: 120.

OUTPUT
120

Common Mistakes • Confusing print with return — printing shows a value but doesn't hand it back for further use.
• Forgetting the base case in recursion → infinite calls, then RecursionError .
• Putting a required parameter after a default one in the definition (illegal).

8.4 Project — Calculator Application


A menu calculator where each operation is its own function — clean, testable, and easy to extend, exactly how real apps separate
logic.

1 def add(a, b): return a + b


2 def sub(a, b): return a - b
3 def mul(a, b): return a * b
4 def div(a, b):
5 if b == 0:
6 return "Error: division by zero"
7 return a / b
8
9 def calculate(op, a, b):
10 table = {"+": add, "-": sub, "*": mul, "/": div}
11 if op in table:
12 return table[op](a, b)
13 return "Unknown operation"
14
15 while True:
16 raw = input("\nExpression (e.g. 8 * 3) or 'q': ")
17 if raw == "q":
18 break
19 left, op, right = [Link]()
20 answer = calculate(op, float(left), float(right))
21 print("=", answer)

Lines 1–3: three one-line functions, each returning a single arithmetic result.
Lines 4–7: division guards against dividing by zero, returning a message instead of crashing.
Lines 9–13: calculate maps each symbol to its function in a dictionary. table[op] retrieves the matching function and (a, b)
immediately calls it — a clean alternative to a long if/elif chain. Functions are values you can store, exactly like numbers.
Lines 15–18: menu loop; 'q' breaks out.
Line 19: .split() breaks "8 * 3" on spaces into three pieces, unpacked into left , op , right .
Line 20: convert the operands to floats and dispatch to the right function.
Line 21: print the result with an "=" sign.

OUTPUT
Expression (e.g. 8 * 3) or 'q': 8 * 3 = 24.0 Expression (e.g. 8 * 3) or 'q': 10 / 0 = Error: division by zero

Practice Exercise Add a power operation ^ using a new function and register it in the dispatch table.
Solution

1 def power(a, b): return a ** b


2 # then add to the table:
3 table = {"+": add, "-": sub, "*": mul, "/": div, "^": power}

Define power using ** , then add the "^" key. No other code changes — that is the payoff of the dispatch-table design. Now 2 ^
5 returns 32.0.
MODULE 9

Object-Oriented Programming
OOP is a way of organising code around things (objects) rather than loose functions and variables. A class is a blueprint; an
object is a real item built from that blueprint. If "Car" is the class, your specific red Toyota is an object. OOP bundles data and the
actions on that data together, which is how every large system — banking, games, web frameworks — stays manageable.

9.1 Classes, Objects & the Constructor


The constructor __init__ runs automatically when an object is created; it sets up the object's instance variables (its personal
data). self refers to “this particular object.”

1 class Dog:
2 def __init__(self, name, breed):
3 [Link] = name
4 [Link] = breed
5
6 def bark(self):
7 return f"{[Link]} says Woof!"
8
9 d = Dog("Rex", "Labrador")
10 print([Link])
11 print([Link]())

Line 1: class Dog: defines the blueprint (class names use CapWords by convention).
Line 2: the constructor; self is always the first parameter and represents the object being built.
Lines 3–4: store the passed values on the object as [Link] and [Link] — these persist for the object's lifetime.
Lines 6–7: a method — a function inside a class. It can read the object's own data via self .
Line 9: create an object; Python calls __init__ with "Rex" and "Labrador". d now holds a Dog object.
Lines 10–11: access data with [Link] and call behaviour with [Link]() .

OUTPUT
Rex Rex says Woof!

9.2 Inheritance
Inheritance lets a new class reuse another class's code and add or change parts. The child inherits from the parent. It models
“is-a” relationships (a SavingsAccount is an Account) and avoids duplicating shared logic.

1 class Animal:
2 def __init__(self, name):
3 [Link] = name
4 def speak(self):
5 return "Some sound"
6
7 class Cat(Animal):
8 def speak(self):
9 return f"{[Link]} says Meow"
10
11 c = Cat("Milo")
12 print([Link], "->", [Link]())

Line 7: class Cat(Animal) means Cat inherits everything from Animal — including __init__ , so we don't rewrite it.
Lines 8–9: Cat overrides speak with its own version.
Line 12: [Link] comes from the inherited constructor; [Link]() uses the overridden method.

OUTPUT
Milo -> Milo says Meow

9.3 Polymorphism & Encapsulation


Polymorphism (“many forms”) means different classes can offer the same method name, and the right one runs automatically —
so you can treat different objects uniformly. Encapsulation means hiding internal data behind methods; a leading underscore
( _balance ) signals “private — touch only through methods,” protecting data from accidental corruption.

1 animals = [Cat("Milo"), Animal("Thing")]


2 for a in animals:
3 print([Link]()) # each object picks its own speak()
OUTPUT
Milo says Meow Some sound

Common Mistakes • Forgetting self as the first method parameter, or forgetting to write self. when storing data.
• Calling a method without parentheses: [Link] shows the method object; [Link]() runs it.
• Touching “private” attributes directly instead of via the class's methods.

9.4 Project — Bank Management System


A complete OOP system: a base Account with encapsulated balance, a SavingsAccount subclass adding interest (inheritance +
polymorphism), and validation via exceptions.

1 class Account:
2 def __init__(self, owner, balance=0):
3 [Link] = owner
4 self._balance = balance # encapsulated
5
6 def deposit(self, amount):
7 if amount <= 0:
8 print("Deposit must be positive.")
9 return
10 self._balance += amount
11 print(f"Deposited {amount}. Balance: {self._balance}")
12
13 def withdraw(self, amount):
14 if amount > self._balance:
15 print("Insufficient funds.")
16 return
17 self._balance -= amount
18 print(f"Withdrew {amount}. Balance: {self._balance}")
19
20 def show(self):
21 print(f"{[Link]}'s balance: {self._balance}")
22
23 class SavingsAccount(Account):
24 def __init__(self, owner, balance=0, rate=0.04):
25 super().__init__(owner, balance)
26 [Link] = rate
27
28 def add_interest(self):
29 interest = self._balance * [Link]
30 self._balance += interest
31 print(f"Interest {round(interest, 2)} added. Balance: {round(self._balance, 2)}")
32
33 acc = SavingsAccount("Nadia", 1000)
34 [Link](500)
35 [Link](200)
36 acc.add_interest()
37 [Link]()

Lines 1–4: the base class stores the owner and a balance. _balance with a leading underscore marks it as internal (encapsulation) —
outsiders should change it only through deposit / withdraw .
Lines 6–11: deposit validates the amount is positive (guard clause + early return ), then increases the balance and reports it.
Lines 13–18: withdraw refuses to overdraw, otherwise subtracts and reports.
Lines 20–21: show displays the current state.
Line 23: SavingsAccount(Account) inherits all of the above.
Lines 24–26: its constructor adds an interest rate . super().__init__(...) calls the parent constructor so we reuse the owner/balance
setup instead of duplicating it.
Lines 28–31: a new method unique to savings: compute interest on the balance and add it.
Lines 33–37: create a savings account, then deposit, withdraw, add interest, and show — note deposit / withdraw were inherited
unchanged, while add_interest is the subclass's own.

OUTPUT
Deposited 500. Balance: 1500 Withdrew 200. Balance: 1300 Interest 52.0 added. Balance: 1352.0 Nadia's balance: 1352.0

Output explained: 1000 + 500 − 200 = 1300; interest at 4% is 52; final balance 1352. The inherited methods and the new one
work together seamlessly.
Practice Exercise Add a CurrentAccount subclass that allows overdraft down to −500.
Solution
1 class CurrentAccount(Account):
2 def withdraw(self, amount):
3 if amount > self._balance + 500:
4 print("Overdraft limit reached.")
5 return
6 self._balance -= amount
7 print(f"Withdrew {amount}. Balance: {self._balance}")

By overriding withdraw , the current account permits the balance to fall to −500 (polymorphism: same method name, different
rule). All other behaviour is inherited from Account .
MODULE 10

Exception Handling & File Handling


Real programs face the unexpected: a user types letters where a number was asked, a file is missing, a network drops. Exception
handling lets a program catch these errors and respond gracefully instead of crashing. File handling lets a program remember
things permanently by reading and writing files on disk.

10.1 try / except / else / finally


Put risky code in try ; handle failures in except ; else runs if no error occurred; finally always runs (perfect for cleanup).

1 try:
2 age = int(input("Enter age: "))
3 except ValueError:
4 print("That was not a whole number.")
5 else:
6 print(f"Next year you will be {age + 1}.")
7 finally:
8 print("Thanks for using the app.")

Line 2: the risky step — int() fails if the text isn't numeric.
Lines 3–4: if a ValueError is raised, this block handles it; the program continues instead of crashing.
Lines 5–6: else runs only when the try succeeded.
Lines 7–8: finally runs no matter what — success or failure.

OUTPUT (TYPED: HELLO)


Enter age: hello That was not a whole number. Thanks for using the app.

10.2 Reading & Writing Files


Use open(path, mode) inside a with block — with automatically closes the file even if an error occurs. Modes: "w" write
(overwrite), "a" append, "r" read.

1 with open("[Link]", "w") as f:


2 [Link]("Line one\n")
3 [Link]("Line two\n")
4
5 with open("[Link]", "r") as f:
6 content = [Link]()
7 print(content)

Line 1: open in write mode; as f names the file object. Write mode creates the file (or wipes an existing one).
Lines 2–3: \n adds a newline so the lines don't run together.
Line 5: reopen in read mode.
Line 6: .read() returns the whole file as one string.

OUTPUT
Line one Line two

Common Mistakes • Using "w" when you meant "a" — write mode erases the existing file.
• Catching every error with a bare except: — name the specific exception so real bugs aren't hidden.
• Forgetting \n , so all writes land on one line.

10.3 Project — Student Report Generator


Reads marks safely, computes results, and writes a permanent report file — the pattern behind any batch report or invoice
exporter.
1 students = []
2 n = int(input("How many students? "))
3
4 for i in range(n):
5 name = input("Name: ")
6 while True:
7 try:
8 marks = float(input("Marks: "))
9 break
10 except ValueError:
11 print("Please type a number.")
12 grade = "Pass" if marks >= 40 else "Fail"
13 [Link]({"name": name, "marks": marks, "grade": grade})
14
15 with open("[Link]", "w") as f:
16 [Link]("STUDENT REPORT\n")
17 [Link]("=" * 30 + "\n")
18 for s in students:
19 line = f"{s['name']:<15} {s['marks']:>6} {s['grade']}\n"
20 [Link](line)
21 average = sum(s["marks"] for s in students) / len(students)
22 [Link]("=" * 30 + "\n")
23 [Link](f"Class average: {round(average, 2)}\n")
24
25 print("[Link] written successfully.")

Lines 1–2: a list for records and the number of students to process.
Lines 6–11: an input-validation loop — keep asking until float() succeeds. The break on line 9 exits only after a valid number; a
ValueError instead triggers the retry message. This is robust real-world input handling.
Line 12: a conditional expression assigns Pass/Fail in one line.
Line 13: store the student as a dictionary.
Line 15: open the report file for writing inside a with block so it always closes.
Lines 16–17: write a title; "=" * 30 repeats the character 30 times to draw a divider.
Lines 18–20: write one aligned line per student using f-string field widths.
Lines 21–23: compute and write the class average.
Line 25: confirm to the user; the data now lives on disk permanently.

[Link] (AFTER ENTERING ALI 80, BEA 35)


STUDENT REPORT ============================== Ali 80.0 Pass Bea 35.0 Fail ============================== Class average: 57.5

Practice Exercise Also handle the case where the file can't be written (e.g. no permission) and tell the user instead of crashing.
Solution

1 try:
2 with open("[Link]", "w") as f:
3 [Link]("...")
4 except (PermissionError, OSError) as e:
5 print("Could not write the report:", e)

Wrapping the file block in try/except catches OS-level problems. Naming the exception as e lets us print the actual reason,
turning a crash into a clear message.
MODULE 11

Modules, JSON & APIs


You don't build everything from scratch. A module is a file of ready-made code you import . JSON is the universal text format for
exchanging data between programs. An API is another program you talk to over the internet to fetch live data. Together they
connect your code to the wider world.

11.1 Modules
import pulls in code from Python's standard library or installed packages, instantly giving you tested tools.

1 import random
2 from datetime import date
3
4 print([Link](1, 6)) # dice roll
5 print([Link]()) # today's date

Line 1: import the whole random module; use its tools as [Link] .
Line 2: import just one name ( date ) from a module so we can use it directly.
Line 4: randint(1, 6) returns a random whole number 1–6, inclusive.
Line 5: [Link]() returns the current date.

OUTPUT (EXAMPLE)
4 2026-06-04

11.2 JSON
JSON looks like a Python dictionary and is how APIs and config files store structured data. [Link] turns a Python object into
a JSON string; [Link] parses a JSON string back into Python.

1 import json
2 data = {"name": "Lee", "skills": ["python", "sql"]}
3 text = [Link](data) # Python -> JSON string
4 back = [Link](text) # JSON string -> Python
5 print(text)
6 print(back["skills"][0])

Line 3: serialise the dictionary to a text string suitable for saving or sending.
Line 4: parse it back into a real dictionary you can index.
Line 6: proves the round-trip worked — we read the first skill.

OUTPUT
{"name": "Lee", "skills": ["python", "sql"]} python

11.3 APIs with requests

The requests library (install with pip install requests ) fetches data from web APIs. You send a request to a URL and get back a
response — usually JSON.

11.4 Project — Weather Information App


Fetches live weather for a city from a public API and prints a friendly summary — the template for any app that consumes a REST
API.
1 import requests
2
3 def get_weather(city):
4 url = "[Link]
5 geo = [Link](
6 "[Link]
7 params={"name": city, "count": 1}
8 ).json()
9 if "results" not in geo:
10 return f"City '{city}' not found."
11 place = geo["results"][0]
12 lat, lon = place["latitude"], place["longitude"]
13 resp = [Link](url, params={
14 "latitude": lat, "longitude": lon, "current_weather": True
15 })
16 data = [Link]()
17 now = data["current_weather"]
18 return f"{[Link]()}: {now['temperature']}°C, wind {now['windspeed']} km/h"
19
20 print(get_weather("Paris"))

Line 1: import the HTTP library.


Line 3: wrap the logic in a function so it's reusable for any city.
Lines 5–8: first call a geocoding API to turn a city name into coordinates. params={...} adds query parameters to the URL; .json()
parses the JSON response into a dictionary.
Lines 9–10: defensive check — if the API returned no results, report it instead of crashing.
Lines 11–12: read the first matching place and pull its latitude/longitude (tuple unpacking).
Lines 13–16: second call to the weather API with those coordinates, asking for current weather; parse the JSON.
Line 17: drill into the nested dictionary to the current_weather object.
Line 18: build a readable summary; [Link]() capitalises nicely.
Line 20: call and print. (Requires internet access.)

OUTPUT (EXAMPLE)
Paris: 18.3°C, wind 11.2 km/h

Common Mistakes • Assuming the response always has the keys you expect — APIs fail or change; check before indexing.
• Forgetting .json() and trying to index the raw response object.
• Not handling network errors — wrap calls in try/except [Link] in production.
Practice Exercise Wrap the network calls so a connection failure prints “Weather service unavailable” instead of crashing.
Solution

1 try:
2 print(get_weather("Paris"))
3 except [Link]:
4 print("Weather service unavailable.")

[Link] is the parent of all network errors (timeouts, DNS failures, connection drops), so one except catches
them all and the program stays alive.
MODULE 12

Python for AI
Python is the default language of modern AI because of its libraries. This module gives you working literacy in the five building
blocks: NumPy (fast numbers), Pandas (tables of data), the OpenAI API (calling a large language model), LLM concepts (how
chat models behave), and RAG (giving a model your own documents to answer from).

12.1 NumPy Basics


NumPy stores numbers in fast arrays and does maths on the whole array at once (called vectorisation) — far quicker than Python
loops. It underpins every AI library. Install with pip install numpy .

1 import numpy as np
2 arr = [Link]([10, 20, 30, 40])
3 print(arr * 2)
4 print([Link](), [Link]())

Line 1: import NumPy under the standard nickname np .


Line 2: create an array from a list.
Line 3: arr * 2 multiplies every element at once — no loop needed (this is vectorisation).
Line 4: built-in statistics on the whole array.

OUTPUT
[20 40 60 80] 25.0 40

12.2 Pandas Basics


Pandas gives you the DataFrame — a programmable spreadsheet (rows and named columns). It's how data scientists load,
clean, filter, and summarise data. Install with pip install pandas .

1 import pandas as pd
2 df = [Link]({
3 "name": ["Ali", "Bea", "Cy"],
4 "score": [80, 35, 90]
5 })
6 print(df["score"].mean())
7 print(df[df["score"] >= 40])

Lines 2–5: build a DataFrame from a dictionary — each key becomes a column.
Line 6: select the score column and average it.
Line 7: boolean filtering — df["score"] >= 40 makes a True/False mask, and indexing with it keeps only the passing rows.

OUTPUT
68.33333333333333 name score 0 Ali 80 2 Cy 90

12.3 The OpenAI API & LLM Basics


An LLM (Large Language Model) like GPT predicts text. You send it messages with roles — system (instructions/persona), user
(the question), assistant (its replies) — and it returns a completion. Install with pip install openai and set your key in the
environment variable OPENAI_API_KEY .

1 from openai import OpenAI


2 client = OpenAI() # reads OPENAI_API_KEY
3
4 response = [Link](
5 model="gpt-4o-mini",
6 messages=[
7 {"role": "system", "content": "You are a concise tutor."},
8 {"role": "user", "content": "Explain a variable in one sentence."}
9 ]
10 )
11 print([Link][0].[Link])

Lines 1–2: import the client and create it; it automatically reads your secret key from the environment (never hard-code keys).
Lines 4–5: request a chat completion and choose the model.
Lines 6–9: the message list. The system role shapes behaviour; the user role is the actual prompt.
Line 11: the reply text lives at choices[0].[Link] — the model can return several choices; we take the first.
OUTPUT (EXAMPLE)
A variable is a named label that points to a value stored in memory.

12.4 RAG Basics


RAG (Retrieval-Augmented Generation) lets an LLM answer using your documents. The idea: chunk your text into pieces, turn
each piece into an embedding (a list of numbers capturing its meaning), store them in a vector database, then for a question,
retrieve the most similar chunks and feed them to the LLM as context. This grounds answers in real sources and reduces made-
up facts. The Final Project includes a tiny, dependency-free RAG so you can see the whole loop.

12.5 Project — Simple AI Chatbot


A terminal chatbot that remembers the conversation (sends history each turn) and degrades gracefully if no API key is present —
so you can run it either way.

1 import os
2
3 def reply(history):
4 """Return the assistant's next message given the conversation."""
5 if not [Link]("OPENAI_API_KEY"):
6 last = history[-1]["content"].lower()
7 if "hello" in last or "hi" in last:
8 return "Hello! (offline mode) How can I help?"
9 return "I'm in offline mode, but I heard: " + last
10 from openai import OpenAI
11 client = OpenAI()
12 resp = [Link](
13 model="gpt-4o-mini", messages=history
14 )
15 return [Link][0].[Link]
16
17 history = [{"role": "system", "content": "You are a friendly assistant."}]
18 print("Chatbot ready. Type 'quit' to exit.")
19 while True:
20 user = input("You: ")
21 if [Link]() == "quit":
22 break
23 [Link]({"role": "user", "content": user})
24 answer = reply(history)
25 [Link]({"role": "assistant", "content": answer})
26 print("Bot:", answer)

Line 1: os lets us check environment variables.


Lines 3–4: the reply function; the docstring documents it.
Lines 5–9: graceful fallback — if no API key is set, run a simple rule-based reply so the program still works for learning/testing.
Lines 10–15: with a key, call the real LLM, passing the whole history so the model has full context (memory), and return its text.
Line 17: seed the conversation with a system persona.
Lines 19–26: the chat loop: read input, quit on "quit", append the user turn, get a reply, append it too (so it's remembered next round), and
print it. Appending both sides each turn is exactly how chat memory works.

OUTPUT (OFFLINE MODE)


Chatbot ready. Type 'quit' to exit. You: hello Bot: Hello! (offline mode) How can I help? You: quit

Common Mistakes • Hard-coding the API key in the file (a security leak) — always read it from the environment.
• Not sending the conversation history, so the bot “forgets” everything each turn.
• Forgetting that API calls cost money and can fail — wrap them in try/except in production.
Practice Exercise Limit the bot's memory to the last 10 messages so very long chats don't grow forever.
Solution

1 if len(history) > 11: # 1 system + last 10


2 history = [history[0]] + history[-10:]

Keep the system message (index 0) and only the most recent 10 turns by slicing. This caps the data sent to the model, controlling
cost and staying within context limits.
CAPSTONE

Final Project — AI Student Assistant


This capstone combines everything: OOP (a Student class), file handling (saving records as JSON), APIs (an LLM call), and a
basic AI feature (a tiny Retrieval-Augmented Generation system that answers questions from a notes file). It is deliberately split
into separate files — the way professionals structure projects so each piece has one job.

Architecture & Design


The guiding principle is separation of concerns: data shape, storage, AI, and the user interface each live in their own file. This
makes the code testable, swappable (e.g. change storage from JSON to a database without touching the menu), and easy to
reason about. Data flows in one direction: the menu ( [Link] ) calls helpers in the other modules; the helpers never call back into
the menu.

Folder Structure
ai_student_assistant/
├── [Link] # entry point: the menu loop and user interaction
├── [Link] # the Student class (OOP: data + behaviour)
├── [Link] # load/save students to a JSON file (persistence)
├── ai_helper.py # AI features: LLM answer + tiny RAG over notes
└── data/
├── [Link] # where records are saved between runs
└── [Link] # the knowledge base the RAG searches

[Link] — the only file the user interacts with; it orchestrates the others.
[Link] — defines what a student is; pure data and behaviour, no I/O.
[Link] — the only file that knows about reading/writing JSON.
ai_helper.py — the only file that knows about the LLM and retrieval.
data/ — holds files that persist, kept separate from code.

File 1 — [Link] (OOP)


1 class Student:
2 def __init__(self, name, roll, marks):
3 [Link] = name
4 [Link] = roll
5 [Link] = marks
6
7 @property
8 def grade(self):
9 if [Link] >= 75: return "A"
10 if [Link] >= 50: return "B"
11 if [Link] >= 40: return "C"
12 return "F"
13
14 def to_dict(self):
15 return {"name": [Link], "roll": [Link], "marks": [Link]}
16
17 @staticmethod
18 def from_dict(d):
19 return Student(d["name"], d["roll"], d["marks"])
20
21 def __str__(self):
22 return f"{[Link]:>3} | {[Link]:<12} | {[Link]:>5} | {[Link]}"

Lines 2–5: the constructor stores the three pieces of student data on the object.
Lines 7–12: grade is computed from marks. The @property decorator lets you read it like an attribute ( [Link] ) even though it's
calculated on the fly — so the grade is always correct and never stored stale.
Lines 14–15: to_dict converts the object into a plain dictionary so it can be saved as JSON (JSON can't store custom objects).
Lines 17–19: from_dict is a @staticmethod (a function that belongs to the class but needs no specific object) that rebuilds a Student from
a saved dictionary — the reverse of to_dict .
Lines 21–22: __str__ defines how the object looks when printed, giving a neat one-line row.

File 2 — [Link] (File Handling)


1 import json, os
2 from models import Student
3
4 DATA_FILE = "data/[Link]"
5
6 def load_students():
7 if not [Link](DATA_FILE):
8 return []
9 with open(DATA_FILE, "r") as f:
10 raw = [Link](f)
11 return [Student.from_dict(d) for d in raw]
12
13 def save_students(students):
14 [Link]("data", exist_ok=True)
15 with open(DATA_FILE, "w") as f:
16 [Link]([s.to_dict() for s in students], f, indent=2)

Lines 1–2: import JSON/OS tools and the Student class so we can rebuild objects.
Line 4: one place defines the file path (a constant) — change it here only.
Lines 6–8: on first run the file won't exist, so return an empty list instead of crashing.
Lines 9–11: read the JSON into a list of dictionaries, then a comprehension turns each dictionary back into a Student object.
Line 14: makedirs(..., exist_ok=True) creates the data folder if missing, harmlessly if it exists.
Lines 15–16: convert every student to a dictionary and write the list as nicely-indented JSON. indent=2 makes the saved file human-
readable.

File 3 — ai_helper.py (AI Features + Tiny RAG)


1 import os
2
3 def _load_notes():
4 if not [Link]("data/[Link]"):
5 return []
6 with open("data/[Link]") as f:
7 return [[Link]() for line in f if [Link]()]
8
9 def retrieve(question, k=2):
10 """Tiny RAG: rank note lines by shared words with the question."""
11 notes = _load_notes()
12 q_words = set([Link]().split())
13 scored = []
14 for note in notes:
15 overlap = len(q_words & set([Link]().split()))
16 [Link]((overlap, note))
17 [Link](reverse=True)
18 return [note for score, note in scored[:k] if score > 0]
19
20 def answer(question):
21 context = retrieve(question)
22 context_text = "\n".join(context) if context else "No notes found."
23 if not [Link]("OPENAI_API_KEY"):
24 return f"(offline) Based on notes:\n{context_text}"
25 from openai import OpenAI
26 client = OpenAI()
27 prompt = (
28 "Answer using ONLY the context.\n"
29 f"Context:\n{context_text}\n\nQuestion: {question}"
30 )
31 resp = [Link](
32 model="gpt-4o-mini",
33 messages=[{"role": "user", "content": prompt}]
34 )
35 return [Link][0].[Link]

Lines 3–7: load the knowledge base, one cleaned line per note (blank lines skipped). This is our document store.
Lines 9–18: the heart of RAG, written without heavy libraries so you can see the mechanism: (a) turn the question into a set of words; (b)
for each note, count shared words using set intersection & as a similarity score; (c) sort highest-first; (d) return the top k relevant notes.
A real system replaces this word-overlap score with vector embeddings, but the retrieve-then-rank loop is identical.
Lines 20–22: answer first retrieves context, then joins it into a text block — the “augmentation” step.
Lines 23–24: offline fallback so the project runs without a key: it simply shows the retrieved notes.
Lines 25–34: with a key, build a prompt that grounds the model — “Answer using ONLY the context” — then send it. This is what stops the
model from inventing facts.
Line 35: return the generated answer.
File 4 — [Link] (Entry Point)
1 from models import Student
2 from storage import load_students, save_students
3 from ai_helper import answer
4
5 def main():
6 students = load_students()
7 print("=== AI Student Assistant ===")
8 while True:
9 choice = input("\n[a]dd [l]ist [r]eport [ask] [q]uit: ").lower()
10 if choice == "a":
11 name = input("Name: ")
12 roll = int(input("Roll: "))
13 marks = float(input("Marks: "))
14 [Link](Student(name, roll, marks))
15 save_students(students)
16 print("Saved.")
17 elif choice == "l":
18 for s in students:
19 print(s)
20 elif choice == "r":
21 if students:
22 avg = sum([Link] for s in students) / len(students)
23 top = max(students, key=lambda s: [Link])
24 print(f"Students: {len(students)} | Average: {round(avg, 2)}")
25 print(f"Top scorer: {[Link]} ({[Link]})")
26 elif choice == "ask":
27 q = input("Ask about your notes: ")
28 print(answer(q))
29 elif choice == "q":
30 save_students(students)
31 print("Goodbye!")
32 break
33
34 if __name__ == "__main__":
35 main()

Lines 1–3: import the pieces from the other modules — this is where everything comes together.
Line 6: load any previously saved students at startup, so data persists across runs.
Lines 8–9: the menu loop and the user's choice.
Lines 10–16: ADD — collect and convert fields, create a Student object, append it, and immediately save_students so nothing is lost on a
crash.
Lines 17–19: LIST — printing each object triggers the __str__ method we defined, giving formatted rows for free.
Lines 20–25: REPORT — compute the average and find the top scorer with max(..., key=...) ; [Link] uses the computed property.
Lines 26–28: ASK — send the question to the AI helper, which retrieves relevant notes and answers from them (the RAG feature).
Lines 29–32: QUIT — save once more, then break.
Lines 34–35: the if __name__ == "__main__" guard runs main() only when this file is executed directly (not when imported) — standard
Python project hygiene.

SAMPLE RUN (WITH A DATA/[Link] ABOUT PYTHON)


=== AI Student Assistant === [a]dd [l]ist [r]eport [ask] [q]uit: a Name: Ali Roll: 1 Marks: 82 Saved. [a]dd [l]ist [r]eport
[ask] [q]uit: l 1 | Ali | 82.0 | A [a]dd [l]ist [r]eport [ask] [q]uit: ask Ask about your notes: what is a variable
(offline) Based on notes: A variable is a named reference to a value stored in memory. [a]dd [l]ist [r]eport [ask] [q]uit: q
Goodbye!

How to run it 1) Create the folder structure above. 2) Put a few facts (one per line) in data/[Link] . 3) From inside the project
folder run python [Link] . 4) Optionally set OPENAI_API_KEY and pip install openai to enable real LLM answers — without it,
the assistant still runs in offline mode.
Capstone Challenges 1) Add a search command to find a student by roll number.
2) Add a delete command and re-save.
3) Replace the word-overlap RAG score with real embeddings (e.g. sentence-transformers ) and observe better retrieval.
4) Export the report to a .txt file using what you learned in Module 10.

You've reached the end. You now have the core of a working Python developer's toolkit — fundamentals, data structures,
functions, OOP, files, APIs, and the on-ramp to AI. The only step left is the most important one: build things. Re-type every project,
break it, extend it, and start a project of your own.

You might also like