1print("Hello,
World!") # the classic first program2print("Welcome to
Python.")
Explanation
Python is a high-level, general-purpose programming language created by Guido van
Rossum and first released in 1991. "High-level" means it hides most of the low-level detail —
memory management, machine instructions — so you can focus on solving the problem instead of managing the
computer.
Its defining trait is readable syntax. Where many languages lean on curly braces and
semicolons, Python uses indentation and plain keywords, so code tends to read close to English. That's a big
reason it's often recommended as a first language.
The line print("Hello, World!") above calls Python's built-in print() function, which displays whatever is inside the parentheses on the screen.
It's traditionally the first program anyone writes in a new language.
How to Use & Where is Used
How to use: Python code is run by an interpreter, which
reads your code and executes it line by line — there's no separate compile step to run a script.
Where is used: Web backends, data analysis, AI and machine
learning, automation scripts, and scientific computing.
Common in: Companies like Google, Netflix, and Instagram, plus
most university intro-to-programming courses.
Example
The >>> below is the Python shell prompt — it means the code is typed
interactively. Do not type >>> yourself; just type the code after it.
Typed into the interactive shell, Python runs each line the moment you press Enter and shows the result right
away — useful for trying things out without writing a full file.
Practice Exercises
Exercise 1 — Say Hello:
Write a program that prints your own name, in the form Hello, my name is ___.
💡 Put the whole sentence inside one pair of quotes and pass it to print().
python
1print("Hello, my
name is Alex")
Exercise 2 — Three Things:
Using three separate print() calls, print three of your favourite things, one
per line.
💡 Each print() call starts on a new line automatically — just write three of
them, one after another.
Exercise 3 — Do Some Math:
Print the result of 15 * 3 — without doing the multiplication yourself, let
Python do it.
💡 You can put a math expression directly inside print() — no quotes needed for
numbers.
python
1print(15 *
3) # 45
Quick Quiz
3 questions · Introduction
1. Who created Python?
Correct! Guido van Rossum created Python and released it in 1991.
Not quite — that's Guido van Rossum. The others created C, Java, and C++ respectively.
2. What does it mean that Python is a "high-level" language?
Correct! High-level means it hides memory management and hardware detail so you can focus on logic.
Not quite — "high-level" is about abstraction from hardware, not speed or release date.
3. Which of these is not a typical use case for Python?
Correct! Device drivers need direct hardware control, which is C/C++/Rust territory, not Python.
Not quite — Python is heavily used for data/ML, web backends, and automation. Device drivers need
lower-level languages.
Quiz complete!
Chapter 1
2. Installation
Setting up Python on your machine.
Code
terminal
1$ python3 --version2Python 3.12.4
Explanation
Before you can run Python code, you need the Python interpreter installed. Download it
from python.org/downloads — pick the latest stable 3.x release for your operating
system.
Windows: run the installer, and make sure you tick Add python.exe to
PATH on the first screen — this is what lets you type python from any
terminal window.
macOS: macOS ships with an old system Python; install a current version from python.org or
with Homebrew (brew install python) instead of relying on the built-in one.
Linux: most distributions already include Python 3 — check with the command below before
installing anything.
The installer also sets up pip, Python's package manager, which you'll use later to
install libraries other people have written.
How to Use & Where is Used
How to use: Open a terminal (Command Prompt / PowerShell on
Windows, Terminal on macOS/Linux) and run python3 --version to confirm the
install worked.
Where is used: The same interpreter you install here is what runs
your scripts, powers your code editor's "Run" button, and is what production servers use to run Python
applications.
Common in: A code editor like VS Code is the standard companion —
it's free and has excellent Python support via an extension.
Example
Typing python3 alone (no filename after it) drops you into the interactive shell — a
good way to sanity-check your install.
Once you see the >>> prompt, Python is installed and working. Type exit() and press Enter to leave the shell.
Practice Exercises
Exercise 1 — Confirm Your Install:
Open a terminal and print your installed Python version.
💡 On Windows try python --version; on macOS/Linux try python3 --version.
terminal
1$ python3 --version
Exercise 2 — Check pip:
Print the installed version of pip, Python's package manager.
💡 pip's version flag follows the same pattern as Python's own.
terminal
1$ pip3 --version
Exercise 3 — Enter and Exit:
Open the interactive shell, run 1 + 1, then leave the shell.
💡 python3 with nothing after it opens the shell; exit() closes it.
terminal
1$ python32>>>1 + 1324>>> exit()
Quick Quiz
3 questions · Installation
1. Where should you download Python from?
Correct! python.org is the official source for Python installers.
Not quite — always get Python from the official python.org site.
2. What does pip do?
Correct! pip is Python's package manager, used to install libraries.
Not quite — pip installs and manages third-party packages, it doesn't compile or format code.
3. Which command checks your installed Python version on macOS/Linux?
Correct! python3 --version prints the installed version.
Not quite — the flag is --version, run as python3 --version.
Quiz complete!
Chapter 1
3. Your First Program
Writing and running your first script.
Code
hello.py
1name = "Cynpho"2print("Hello,",
name)3print("This is my first
Python program.")
Explanation
So far you've typed code straight into the interactive shell, which runs each line immediately but forgets
everything once you close it. A real program lives in a .py file — plain text saved with a
.py extension — that you can run again any time and share with others.
The file above stores the text "Cynpho" in a variable called name (you'll cover variables properly in lesson 5), then uses two separate print() calls to display messages. Passing print() two
things separated by a comma, like line 2 does, prints them on the same line with a space between.
You write .py files in a plain text editor or, better, a code editor with Python support like VS
Code, which adds syntax highlighting and a built-in way to run the file.
How to Use & Where is Used
How to use: Save your code as hello.py,
then in a terminal, navigate to that folder and run python3 hello.py.
Where is used: Every real Python project — from a ten-line script
to a production web app — is just .py files run this same way.
Common in: Most editors also have a "Run" button that does the
same python3 file.py command behind the scenes.
Example
This is a terminal session, not the Python shell — the $ is a normal command-line
prompt.
terminal
1$ python3 hello.py2Hello, Cynpho3This is my first Python program.
Running the file executes every line top to bottom, exactly once, then returns you to the terminal prompt —
unlike the shell, it doesn't wait for more input.
Practice Exercises
Exercise 1 — Save and Run:
Create a file called about_me.py that prints your name and your favourite hobby
on two separate lines, then run it.
💡 Two print() calls, saved in a file, then python3
about_me.py in the terminal.
about_me.py
1print("Alex")2print("Rock
climbing")
Exercise 2 — Comma Printing:
In one print() call, print the words "Python", "is", and "fun" separated by spaces, using commas.
💡 print() automatically puts a space between every comma-separated item.
python
1print("Python", "is", "fun") # Python is fun
Exercise 3 — Fix the Typo:
This line has a mistake and won't run: print("Hi there" — find the problem and
write the corrected version.
💡 Count the parentheses — every ( needs a matching ).
python
1print("Hi
there") # the closing ) was missing
Quick Quiz
3 questions · Your First Program
1. What file extension do Python scripts use?
Correct! Python files are saved with a .py extension.
Not quite — Python scripts use the .py extension.
2. Which terminal command runs a file called app.py?
Correct! python3 app.py tells the interpreter to run that file.
Not quite — the interpreter runs a script with python3 app.py.
3. What does print("Python", "is", "fun") output?
Correct! print() joins comma-separated items with a single space between each.
Not quite — comma-separated arguments to print() are joined with a space: Python is fun.
Quiz complete!
Chapter 1
4. Syntax & Comments
Python syntax rules and how to write comments.
Code
python
1# This is a single-line comment2print("Python reads this
line") # ignored by Python3"""4This is a multi-line comment.5Python skips this whole block too.6"""7print("...but not this
line")
Explanation
Python has no curly braces and no semicolons marking the end of a line. Instead, a statement simply ends at
the newline, and indentation — not punctuation — is what groups lines into a block. You'll
see this properly once if statements and loops arrive, but the rule starts now: stay
consistent, and use 4 spaces per indent level (the PEP 8 convention).
A comment is a note left for humans that Python completely ignores when it runs your code.
Start one with # — everything after it on that line is skipped. Comments are
perfect for explaining why code does something, not restating what it obviously does.
A """triple-quoted string""" that isn't assigned to anything or returned is
created and immediately thrown away, which makes it a handy way to write a multi-line
comment. Placed as the very first line inside a function or class, that same syntax becomes a
docstring — a real piece of documentation tools can read, not just a discarded note.
How to Use & Where is Used
How to use: Type # before a note for a
single line, or wrap several lines in """triple quotes""" for a longer
explanation.
Where is used: Explaining tricky logic, documenting functions and
classes with docstrings, and leaving notes for teammates (or yourself, months later).
Common in: Every well-maintained open-source project — good
docstrings are how tools and other developers understand a function without reading its full
implementation.
Example
Indentation only matters inside a block (like a function or an if). Indenting a plain line for no reason is a syntax error — Python will refuse to
run it.
python — interactive shell
1>>>print("Line one") # fine — comment after
code2Line one3>>>print("Oops")4IndentationError: unexpected
indent
The comment on line 1 is harmless — it's just skipped. The unexpected indent on line 3 isn't a comment, and
Python has no block there to justify it, so it raises an error instead of guessing what you meant.
Practice Exercises
Exercise 1 — Comment Your Code:
Write one print() statement, and add a single-line comment above it explaining
what it does.
💡 Start the comment line with #, on its own line, right above the code it
describes.
python
1# Prints a short welcome
message2print("Welcome to
Python!")
Exercise 2 — Multi-line Note:
Write a triple-quoted comment block describing what a script does, then add one print() call below it.
💡 Triple quotes can span as many lines as you like — Python treats the whole thing as one string.
A variable is a name that refers to a value stored in memory. name =
"Maya" creates the variable name and points it at the string "Maya" — the single = is the assignment
operator, not a math equals sign.
Python is dynamically typed: you never declare a type up front. A variable's type is
simply whatever value it currently holds, and it can hold a different type entirely after a reassignment —
age = 25 then later age = "twenty-five" is completely
legal.
Naming rules: names can use letters, digits, and underscores, but can't start with a digit
and can't be a reserved keyword like class or True.
Names are case-sensitive, so age and Age are two
different variables. The convention is snake_case for regular variables and UPPER_CASE for values meant to stay constant.
How to Use & Where is Used
How to use: Pick a clear, descriptive name, then assign a value to
it with =. Reuse the name later to read or update that value.
Where is used: Absolutely everywhere — user data, configuration
values, loop counters, and every intermediate result in a calculation.
Common in: Forms that store a user's name and email, game state
like score and lives, and shopping-cart totals.
Example
Reassigning a variable doesn't just change its value — it can change its type too,
since Python figures out the type from whatever the variable currently points to.
score starts out as the int 15, then is reassigned to a
completely different type — a string. Python doesn't complain; the old value is simply replaced.
Practice Exercises
Exercise 1 — Store Your Info:
Create three variables for your name, age, and favorite language, then print all three on one line.
💡 print() accepts several comma-separated values and prints them on one line.
Exercise 2 — Multiple Assignment:
Assign x, y, and z to 1, 2, and 3 in a single line,
then print their sum.
💡 Python lets you write a, b, c = 1, 2, 3 to assign several variables at once.
python
1x, y, z = 1, 2, 32print(x + y + z) #
6
Exercise 3 — Spot the Invalid Name:
Which of these are invalid Python variable names, and why: 2total,
total_2, total-2, class?
💡 Check the rule about the first character, the rule about hyphens, and the list of reserved keywords.
notes
1# 2total -> invalid, starts with a
digit2# total_2 -> valid3# total-2 -> invalid, "-" is the
subtraction operator4# class -> invalid, it's a reserved
keyword
Quick Quiz
3 questions · Variables
1. Which symbol assigns a value to a variable in Python?
Correct! A single = is the assignment operator. == is used for comparison
instead.
Not quite — a single = assigns a value; == compares two values.
2. Which of these is a valid Python variable name?
Correct! Letters, digits, and underscores are fine as long as the name doesn't start with a digit.
Not quite — variable names can't start with a digit, contain a hyphen, or contain a space.
3. What happens if you reassign a variable to a value of a different type?
Correct! Python is dynamically typed — a variable's type is just whatever it currently points to.
Not quite — reassigning to a different type is completely legal in Python.
Every value in Python has a data type, whether you state it or not. The four you'll use
constantly: str for text, int for whole numbers, float for decimal numbers, and bool for True/False. A fifth, None, is its
own type — NoneType — and represents "no value at all," not zero or an empty
string.
The built-in type() function tells you exactly what type a value is. It's the
fastest way to check your assumptions when a variable isn't behaving the way you expect.
Text always needs quotes: "Jit" is a string, but Jit
without quotes would be treated as a variable name instead. A whole number like 17
is an int; the moment a decimal point appears, like 99.99, it becomes a float — even if that decimal is .0.
How to Use & Where is Used
How to use: Wrap type(x) around any
value or variable to see exactly what data type it is.
Where is used: Databases, APIs, and forms all describe fields by
type — Python's built-in types map directly onto that.
Common in: A user profile is a great example — name is a str, age is an int, account_balance is a float,
and is_logged_in is a bool.
Example
type() always prints its answer as <class
'...'> — the part inside the quotes is the type's actual name.
Correct! type(x) returns exactly what data type x currently is.
Not quite — type() reports a value's data type; it doesn't convert or delete anything.
2. What is the key difference between int and float?
Correct! int is a whole number, float has a decimal point.
Not quite — the difference is whole numbers (int) versus decimal numbers
(float).
3. What are the only two possible values of a bool?
Correct! bool values are always exactly True or False, capitalized.
Not quite — a bool is always True or False, not 0/1 or text.
Quiz complete!
Chapter 1
7. Functions in Python
Functions are blocks of reusable code that perform a specific task.
Code
python
1defgreet(name):2 message = f"Hello,
{name}!"# f-string — embeds name inside string3return message # sends
value back to caller45result = greet("Cynpho") # call the function6print(result) # Hello,
Cynpho!
Explanation
We define a function named greet() using the def
keyword. It takes one parameter name.
Inside the function, f"Hello, {name}!" is an f-string — the
f prefix tells Python to embed the variable value directly into the string.
The return statement sends the value back to wherever the function was called.
On line 5 we call the function by writing its name followed by parentheses: greet("Cynpho"). The result is stored in result and printed.
How to Use & Where is Used
How to use: Define with the def keyword,
give it a name, list parameters in parentheses, write the body indented, and call it by name.
Where is used: Functions organize code, avoid repetition, and make
programs modular and easy to maintain.
Common in: Web development, data analysis, automation scripts,
APIs, and every Python project.
Example
The >>> below is the Python shell prompt — it means the code is typed
interactively. Do not type >>> yourself; just type the code after it.
python — interactive shell
1>>>defadd(a, b):2...return a +
b3...4>>> total = add(5, 7)5>>>print(total)612
The add() function takes two numbers and returns their sum. Line 6 shows the output
— no prompt because it's printed by Python, not typed by you.
Practice Exercises
Exercise 1 — Define and Call:
Write a function called square() that takes one number as a parameter and
returns its square. Then call it with the value 9 and print the result.
💡 Use the ** operator to raise a number to a power. 9 **
2 gives 81.
python
1defsquare(n):2return n **234print(square(9)) # 81
Exercise 2 — Default Parameters:
Write a function called greet() that takes a name and a greeting with a default
value of "Hello". Calling greet("Alice") should print
Hello, Alice! and greet("Bob", "Hi") should print Hi, Bob!
💡 Default parameters are set in the function signature: def greet(name,
greeting="Hello"):
Exercise 3 — Return Multiple Values:
Write a function called min_max() that takes a list of numbers and returns both
the smallest and the largest value. Test it with [3, 1, 8, 4, 9, 2].
💡 Python can return multiple values as a tuple: return min(nums), max(nums).
You can unpack them with lo, hi = min_max(...).
1. What keyword is used to define a function in Python?
Correct! def is the Python keyword for defining a function.
Not quite — the correct keyword is def. Python uses short, readable keywords throughout.
2. What does the return statement do inside a function?
Correct! return sends a value back to wherever the function was called from.
Not quite — return sends a value back to the caller, it does not print or repeat anything.
3. What will this code print?
def double(n): return n * 2 print(double(6))
Correct! double(6) returns 6 * 2 = 12, which is then printed.
Not quite — double(6) returns 6 * 2, which equals 12.
Quiz complete!
Chapter 1
8. User Input
Reading data from the user with input().
Code
python
1name = input("Enter your
name: ")23print("Hello,",
name)
Explanation
input() lets a running program pause and wait for a person to type something.
Whatever you pass in as an argument — like "Enter your name: " — is displayed
first as the prompt, then Python waits until the user presses Enter.
Whatever the user typed is returned as a string, always — even if they typed 42, it comes back as the text "42", not the number. If you
need a number, you have to convert it explicitly (the next lesson covers exactly that).
Without input(), every run of a program would behave identically — it's the
simplest way to make a program interactive.
How to Use & Where is Used
How to use: Call input("your prompt: ")
and store the result in a variable — always write a clear prompt so the user knows what to type.
Where is used: Login and registration flows, command-line
calculators and tools, and games, quizzes, and surveys.
Common in: An ATM asking for a PIN, or a quiz app asking the
player to type an answer, are both built on exactly this pattern.
Example
In the shell, the prompt text and whatever you type appear on the same line — the text
after the prompt below (Alex) is what the user typed.
python — interactive shell
1>>> name = input("Enter your name: ")2Enter your name: Alex3>>>print("Hello,", name)4Hello, Alex
name now holds the string "Alex" — exactly what was typed,
nothing more.
Practice Exercises
Exercise 1 — Greet the User:
Ask for the user's name and print a personalized greeting.
💡 Store the result of input() in a variable, then pass it to print().
python
1name = input("What's your name? ")2print("Nice to
meet you,", name)
Exercise 2 — Ask Two Things:
Read the user's name and favorite color with two separate input() calls, then
print both.
💡 Each call to input() needs its own prompt and its own variable.
Type conversion changes a value from one data type into another. Python does some of this
automatically (implicit conversion — like 1 + 2.0 becoming a
float without you asking), but most of the time you'll do it yourself with
explicit conversion: int(), float(),
and str().
age = "17" is a string. int(age) reads that string and
returns the whole number 17, which gets reassigned back to age. The same pattern works with float() to turn an int like 10 into 10.0.
The single most common reason you'll reach for this: input() always hands back a
string, even when the user typed a number — so any calculation needs an explicit conversion first.
How to Use & Where is Used
How to use: Wrap the value in int(),
float(), or str() — always validate untrustworthy data
before converting it, so a bad conversion doesn't crash your program.
Where is used: Processing user input, reading data from files and
APIs, and any calculation that mixes text with numbers.
Common in: A web form's age field always arrives as text and must
be converted to an int before you can do arithmetic with it.
Example
Converting text that isn't actually a valid number raises a ValueError — always a good
reason to check the text first.
python — interactive shell
1>>>int("42")2423>>>str(99)4'99'5>>>int("abc")6ValueError: invalid literal for int() with base
10: 'abc'
"abc" isn't a number in any form, so int() has nothing
valid to convert and raises an error instead of guessing.
Practice Exercises
Exercise 1 — String to Int:
Convert the string "25" to an int, add 5 to it, and
print the result.
💡 "25" + 5 fails — convert the string with int()
first.
python
1value = int("25") + 52print(value) #
30
Exercise 2 — Build a Sentence:
Given age = 17, print "Age: 17" by concatenating a
string with str(age).
💡 The + operator can't join a string and an int directly — convert the int to a
string first.
python
1age = 172print("Age:
" + str(age))
Exercise 3 — Fix the Conversion: int("3.5") raises a ValueError. Why, and how would you convert "3.5" into a whole number instead?
💡 int() can't parse a decimal point directly — go through float() first.
1. What's the difference between implicit and explicit type conversion?
Correct! Implicit is automatic (like int + float); explicit means you call int(), float(), or str()
yourself.
Not quite — implicit conversion happens automatically; explicit conversion is done manually.
2. What does input() always return, before any conversion?
Correct! input() always returns a string, which is exactly why conversion is so common.
Not quite — input() always returns a string, regardless of what was typed.
3. What exception is raised when a conversion fails, like int("abc")?
Correct! A conversion that can't make sense of the text raises a ValueError.
Not quite — a failed conversion like int("abc") raises a ValueError.
Quiz complete!
Chapter 1
10. Operators
Arithmetic, comparison, and logical operators.
Code
python
1a = 102b = 334print(a + b) #
addition5print(a - b) #
subtraction6print(a * b) #
multiplication7print(a / b) # true
division8print(a // b) # floor
division9print(a % b) # modulus
(remainder)10print(a ** b) #
exponent
Explanation
Arithmetic operators combine numbers: +-*///%**. Most are familiar from math class, but two are
Python-specific: // is floor division — it divides then drops
anything after the decimal point — and % is the modulus, which
returns only the remainder of a division.
Comparison operators — ==!=><>=<= — compare two values and always produce a bool: True or False. Don't confuse ==
(compares) with = (assigns) — mixing them up is one of the most common beginner
mistakes.
Logical operators — and, or, not — combine or invert boolean values. and needs both sides
to be true, or needs at least one side true, and not
flips True to False and back.
How to Use & Where is Used
How to use: Place an operator between two values to build an
expression — Python evaluates ** first, then * / // %,
then + -, similar to the math order of operations.
Where is used: Calculating totals and averages, comparing scores
or ages, and combining multiple conditions in decision-making code.
Common in: A shopping cart totaling item prices, or a game
checking whether a score beats a high score.
Example
Wrap comparisons in parentheses when combining them with and/or — it makes the intent obvious at a glance.
Correct! // is floor division — it keeps only the whole-number part of the result.
Not quite — / is true division, // is floor division (it drops anything after
the decimal point).
2. What does the modulus operator % compute?
Correct! % returns whatever is left over after floor division — commonly used to check
even/odd numbers.
Not quite — % is the modulus: the remainder after division.
3. What does and require in order to return True?
Correct! and only returns True when both operands are true — that's the whole
point of "and."
Not quite — and needs both sides to be true; for "at least one," use or instead.
Quiz complete!
Chapter 2
11. If Statements
Running code only when a condition is true.
Code
python
1age = 1823if age >= 18:4print("Adult")
Explanation
The if statement lets a program execute a block of code only when a specific
condition evaluates to True. If the condition is False,
Python simply skips the indented block beneath it.
In the example above, age >= 18 compares two values and produces a boolean
result. Since 18 >= 18 is True, Python runs the
indented line and prints "Adult".
Indentation — not braces or parentheses — is what defines an if block in Python.
Every line that belongs to the block must be indented by the same amount, conventionally four spaces.
How to Use & Where is Used
How to use: Write if, then a condition,
then a colon — everything indented on the following lines only runs when that condition is True.
Where is used: Login systems and security checks, games and AI
decision-making, and validating user input before it's processed.
Common in: Age verification before granting access to
age-restricted content, or checking a password before allowing a login to proceed.
Example
Read a condition like a plain English sentence — "if age is greater than or equal to 18" — it makes
logic mistakes much easier to spot.
python — interactive shell
1>>> age = 152>>>if age >= 18:3...print("Adult")4...
Because 15 >= 18 is False, the indented print() line never runs — the program produces no output at all for this block.
Practice Exercises
Exercise 1 — Temperature Check:
Given temperature = 35, print "Hot day!" if the
temperature is above 30.
💡 Compare temperature to 30 using >, then put
print() on the indented line beneath the if.
python
1temperature = 3523if temperature > 30:4print("Hot
day!")
Exercise 2 — Login Check:
Given password = "cynpho123", print "Access granted"
if it matches "cynpho123".
💡 Use == to compare two strings for equality — not =,
which assigns instead of compares.
Exercise 3 — Free Shipping:
Given cart_total = 55, print "You get free shipping!"
if the total is at least $50.
💡 "At least" means the condition should also be True when the values are exactly equal — use >=.
python
1cart_total = 5523if cart_total >= 50:4print("You get
free shipping!")
Quick Quiz
3 questions · If Statements
1. What data type must the condition in an if statement evaluate to?
Correct! An if condition always evaluates to a boolean — True runs the block,
False skips it.
Not quite — an if condition always reduces to a boolean, True or False, no matter how complex the
comparison looks.
2. What is the difference between = and == in Python?
Correct! = stores a value in a variable; == checks whether two values are equal.
Not quite — = is assignment, == is comparison. Mixing them up is one of the most
common beginner mistakes.
3. What happens if you forget the colon after an if condition?
Correct! The colon is required — leaving it out immediately raises a SyntaxError before the
code even runs.
Not quite — Python requires the colon after an if condition; without it, you'll get a
SyntaxError.
Quiz complete!
Chapter 2
12. If…Else
Choosing between two code paths.
Code
python
1age = 1523if age >= 18:4print("Adult")5else:6print("Minor")
Explanation
if-else provides two possible paths of execution: one branch runs when the
condition is True, the other runs when it's False —
exactly one of the two always executes.
Here, age >= 18 is False because 15 is less than 18,
so Python skips the if block and instead runs the else
block, printing "Minor".
Unlike a plain if with no else, this guarantees the program always does something
meaningful for both outcomes — the positive case and the negative one.
How to Use & Where is Used
How to use: Add an else: block directly
beneath an if block's indented code — it runs only when the if condition was
False.
Where is used: Login success/failure handling, payment approval or
decline, and pass/fail results in grading systems.
Common in: A payment system that either approves a transaction or
declines it — there's always exactly one outcome, never both and never neither.
Example
Always ask "what happens when this condition is False?" — it's easy to design only for the success path
and forget the else.
python — interactive shell
1>>> age = 202>>>if age >= 18:3...print("Adult")4...else:5...print("Minor")6...7Adult
This time age >= 18 is True, so only the if branch runs — the else block is skipped entirely.
Practice Exercises
Exercise 1 — Even or Odd:
Given number = 7, print "Even" if it's even, otherwise
print "Odd".
💡 A number is even when number % 2 == 0 — the modulus tells you the remainder
after dividing by 2.
python
1number = 723if number % 2 == 0:4print("Even")5else:6print("Odd")
Exercise 2 — Pass or Fail:
Given marks = 35, print "Pass" if marks are 40 or
above, otherwise print "Fail".
💡 Use >= for "at least this value," and put the failing case in the else block.
python
1marks = 3523if marks >= 40:4print("Pass")5else:6print("Fail")
Exercise 3 — Ticket Price:
Given age = 10, print "Child ticket" if age is under
12, otherwise print "Adult ticket".
💡 "Under 12" means strictly less than 12 — use <.
python
1age = 1023if age < 12:4print("Child
ticket")5else:6print("Adult
ticket")
Quick Quiz
3 questions · If…Else
1. Can both the if block and the else block run in the same execution?
Correct! Exactly one branch of an if-else always runs — they're mutually exclusive.
Not quite — if-else guarantees exactly one branch runs, never both and never neither.
2. When does the else block execute?
Correct! The else block is the fallback — it only runs when the if condition evaluates to False.
Not quite — else only runs when the if condition was False.
3. Why is if-else important for real-world applications?
Correct! Most real decisions have two outcomes worth handling — if-else lets your program respond to both.
Not quite — the real value of if-else is handling both possible outcomes of a check, not just the success
case.
Quiz complete!
Chapter 2
13. Elif Ladder
Handling multiple conditions in sequence.
Code
python
1marks = 8223if marks >= 90:4print("Grade
A")5elif marks >= 80:6print("Grade
B")7elif marks >= 70:8print("Grade
C")9else:10print("Fail")
Explanation
elif is short for "else if" — it lets a program check several conditions in
sequence, one after another, extending if/else to handle more than two possible
outcomes.
Python checks each condition top to bottom and stops at the first one that's True
— here, 82 >= 90 is False, but 82 >= 80 is True,
so "Grade B" prints and every condition below it is skipped.
A final else catches whatever doesn't match any condition above it — order
matters: conditions should generally run from most specific to most general.
How to Use & Where is Used
How to use: Stack one or more elif
condition: blocks between an if and an optional final else — Python runs the first True branch and skips the rest.
Where is used: Grading systems with multiple grade bands, menu
systems with more than two options, and pricing rules with several tiers.
Common in: A grading system that maps a numeric score to a letter
grade is one of the most common real-world uses of elif.
Example
Arrange conditions from most specific to most general — otherwise a narrower case can get caught by a
broader one that comes first.
python — interactive shell
1>>> marks = 952>>>if marks >= 90:3...print("Grade A")4...elif marks
>= 80:5...print("Grade B")6...else:7...print("Grade C or below")8...9Grade A
Because 95 >= 90 is already True, Python runs the first matching branch and never
even checks the elif or else below it.
Practice Exercises
Exercise 1 — Traffic Light:
Given light = "yellow", print "Go", "Slow down", or "Stop" for "green", "yellow", "red"
respectively.
💡 Chain the checks with elif — compare light to each
string with ==.
1. Does Python check every elif block, or stop at the first True one?
Correct! Python stops at the first True condition in the chain — everything after it is skipped, even if
it would also be True.
Not quite — Python stops checking as soon as one condition is True; the rest of the chain never runs.
2. How many elif blocks can a single if chain have?
Correct! There's no built-in limit — you can chain as many elif blocks as the logic requires.
Not quite — Python places no limit on how many elif blocks a single chain can have.
3. Why does condition ORDER matter in an if/elif chain?
Correct! Since Python stops at the first True match, a narrower condition needs to come before a broader
one that would otherwise catch it first.
Not quite — order affects correctness, not just style: a specific case listed after a broader one may
never be reached.
Quiz complete!
Chapter 2
14. Nested If
Conditions inside conditions.
Code
python
1age = 202has_id = True34if age >= 18:5if has_id:6print("Access
Granted")
Explanation
A nested if is an if statement placed inside another
if statement — used when one decision genuinely depends on a previous one having
already been satisfied.
Here, the inner if has_id: is only even checked because the outer condition age >= 18 was already True. Both conditions must pass for
"Access Granted" to print.
Nesting expresses a genuine dependency between decisions — but keep it shallow. Two levels is usually the
practical limit before the logic becomes hard to follow.
How to Use & Where is Used
How to use: Indent a second if inside
the first one's block — the inner condition is only evaluated when the outer one is already True.
Where is used: Banking systems with multiple verification steps,
two-factor authentication, and AI decision trees.
Common in: ATM verification often nests conditions: first check
the card is valid, then — only if that passed — check the PIN is correct.
Example
If nesting starts to feel hard to read, that's a signal to split the logic into a well-named helper
function instead of nesting further.
python — interactive shell
1>>> age = 162>>> has_id = True3>>>if age >= 18:4...if
has_id:5...print("Access Granted")6...
The outer condition age >= 18 is already False here, so
the inner if has_id: is never even reached — nothing prints.
Practice Exercises
Exercise 1 — Two-Factor Check:
Given password_ok = True and otp_ok = False, print
"Login successful" only if both are True.
💡 Nest a second if inside the first — or combine both with and, which reads just as clearly here.
Exercise 2 — Movie Rating:
Given age = 15 and has_parent = False, print "Can watch" only if age is 18+, or if age is at least 13 and a parent is
present.
💡 This needs an outer check for the age-13 case, with a nested check for parental presence inside it.
python
1age = 152has_parent = False34if age >= 18:5print("Can
watch")6elif age >= 13:7if has_parent:8print("Can
watch")
Exercise 3 — Loan Approval:
Given income = 45000 and credit_score = 720, print
"Loan approved" only if income is at least 30000 and credit_score is at
least 700.
💡 Check income first — only look at the credit score if that outer condition already passed.
Correct! A nested if is simply an if statement written inside the block of another if statement.
Not quite — nesting specifically means placing one if statement inside another one's indented block.
2. When is nesting genuinely necessary instead of just using and?
Correct! Nesting is most useful when each level needs to respond differently — like showing a specific
message for each failed step.
Not quite — plain and often works fine for a single combined check; nesting earns its place
when each condition needs distinct handling.
3. How can excessive nesting be avoided in real code?
Correct! Once nesting gets deep, pulling the logic out into its own function usually reads far more
clearly.
Not quite — the standard fix for deep nesting is refactoring it into a separate function.
Quiz complete!
Chapter 2
15. For Loops
Iterating over sequences.
Code
python
1for i inrange(1, 6):2print(i)
Explanation
A for loop iterates over a sequence of items, running its indented block once for
each one — it automates repetition that would otherwise mean writing the same code by hand for every item.
range(1, 6) produces the numbers 1, 2, 3, 4, 5 — the
stop value, 6, is never included. Each pass through the loop, i takes the next value in that sequence.
A for loop can iterate over far more than range() — lists, tuples, strings,
dictionaries, and sets are all iterable in exactly the same way.
How to Use & Where is Used
How to use: Write for, a loop variable
name, in, then an iterable, followed by a colon — the indented block runs once
per item.
Where is used: Iterating over lists, tuples, strings,
dictionaries, and sets; processing files line by line; and general automation or data-processing
tasks.
Common in: Printing every student's name in a class list, or
processing every row returned from a database query, are both classic for-loop use cases.
Example
Prefer a for loop whenever the number of iterations — or the collection being processed — is already
known in advance.
python — interactive shell
1>>>for i inrange(3):2...print(i *
i)3...405164
range(3) produces 0, 1, 2 — for each one, the loop prints
its square, giving 0, 1, and 4.
Practice Exercises
Exercise 1 — Countdown:
Print the numbers 5 down to 1 (inclusive), one per line.
💡 range() can count downward too — pass a negative step as its third argument.
python
1for i inrange(5, 0, -1):2print(i)
Exercise 2 — Sum of a List:
Given numbers = [4, 8, 15, 16, 23], print the total of all the numbers.
💡 Start a total = 0 variable before the loop, then add each number to it as you
iterate.
python
1numbers = [4, 8, 15, 16, 23]2total = 034for n in
numbers:5 total = total + n67print(total)
Exercise 3 — Print Even Numbers:
Print every even number from 1 to 10 using a for loop.
💡 Loop through range(1, 11) and use if n % 2 == 0 to
filter for even numbers.
python
1for n inrange(1, 11):2if n % 2
== 0:3print(n)
Quick Quiz
3 questions · For Loops
1. What values does range(1, 6) actually produce?
Correct! range(1, 6) starts at 1 and stops before 6, producing 1 through 5.
Not quite — range()'s stop value is never included, so range(1, 6) produces 1, 2, 3, 4, 5.
2. What is an "iterable" in Python?
Correct! An iterable is anything a for loop can step through one item at a time — lists, tuples, strings,
dictionaries, sets, and range() all qualify.
Not quite — an iterable is any object a for loop can step through item by item, not just range().
3. When would you prefer a for loop over a while loop?
Correct! A for loop is the natural fit when the iteration count or collection is already known — a while
loop suits condition-based repetition instead.
Not quite — a for loop shines when the number of repetitions (or the collection itself) is already known
ahead of time.
A while loop repeats its block for as long as a given condition remains True, checking that condition before every single pass.
Here, count <= 5 is checked before each iteration; as long as it holds, Python
prints count and then increments it with count += 1.
That increment step is essential — without something inside the loop that eventually makes the condition
False, a while loop runs forever.
How to Use & Where is Used
How to use: Write while, then a
condition, then a colon — the indented block repeats as long as that condition stays True, so make sure
something inside it moves toward ending the loop.
Where is used: Situations where the number of iterations isn't
known in advance, menus that repeat until the user chooses to quit, and polling or retry logic that waits
for a condition to change.
Common in: A command-line menu that keeps showing options and
re-prompting until the user specifically chooses "Quit" is a classic while-loop pattern.
Example
If you're unsure whether a loop will ever end, trace through the condition by hand with the loop's
actual starting values before running it.
Exercise 3 — Sum Until Limit:
Starting from 1, keep adding numbers to a total until the total reaches or exceeds 20, then print the
total.
💡 The loop's condition should check the running total, not a fixed counter.
python
1total = 02n = 134while total < 20:5 total += n6 n += 178print(total)
Quick Quiz
3 questions · While Loops
1. When is the while loop's condition checked — before or after each pass?
Correct! A while loop checks its condition before every single pass — if it's False from the start, the
loop body never runs at all.
Not quite — the condition is checked before each pass, not after.
2. What would happen if count += 1 were removed from this loop?
Correct! Without something that eventually changes the condition, count would stay at 1 forever and the
loop would never end.
Not quite — removing that line means count never changes, so the condition stays True forever and the loop
never ends.
3. When would you choose a while loop over a for loop?
Correct! A while loop fits naturally when repetition depends on a condition rather than a known count or
collection.
Not quite — while loops are the better fit when you don't know the number of iterations in advance.
Quiz complete!
Chapter 2
17. Break, Continue, Pass
Controlling loop execution.
Code
python
1for i inrange(1, 11):23if i == 5:4continue56if i == 9:7break89print(i)
Explanation
break and continue are loop-control statements that
change a loop's normal flow: break exits the loop immediately, continue skips straight to the next iteration without running the rest of the current
one.
In the example above, continue skips printing 5 and
jumps straight to the next value of i, while break stops
the loop completely the moment i reaches 9 — so 9 and 10 never print either.
pass is a third, unrelated keyword that does absolutely nothing — it's a
placeholder used where Python's syntax requires a statement but you don't have one yet, like an empty
function body or an unfinished branch of an if statement.
How to Use & Where is Used
How to use: Place continue to skip just
the current pass, or break to exit the loop entirely — pass goes anywhere a statement is syntactically required but no action is needed
yet.
Where is used: Searching, where you stop as soon as a match is
found; skipping invalid records while processing data; and stubbing out functions or classes you haven't
written yet.
Common in: Searching a list for the first matching item and
breaking immediately once found avoids wasting time scanning the rest of a potentially huge list.
Example
break is especially valuable in search-style loops — once the answer is found, there's no reason to keep
looping.
python — interactive shell
1>>> names = ["Alice", "Bob", "Cynthia"]2>>>for name in names:3...if name ==
"Bob":4...print("Found Bob!")5...break6...7Found Bob!
As soon as "Bob" is found, break exits the loop
immediately — the remaining name, "Cynthia", is never even checked.
Practice Exercises
Exercise 1 — Skip Multiples of 3:
Print numbers 1 through 10, but skip any multiple of 3.
💡 Use continue inside an if number % 3 == 0: check to
skip that pass.
python
1for number inrange(1, 11):2if number % 3 == 0:3continue4print(number)
Exercise 2 — Stop at First Negative:
Given numbers = [4, 9, -2, 6, 8], print each number until you reach the first
negative one, then stop.
💡 Check each number with if number < 0: and break
as soon as it's true.
python
1numbers = [4, 9, -2, 6, 8]23for number in numbers:4if number < 0:5break6print(number)
Exercise 3 — Placeholder Function:
Write a function named coming_soon that takes no arguments and does nothing yet
— its body should just be pass.
💡 pass is exactly what belongs inside a function body you plan to finish
writing later.
python
1defcoming_soon():2pass
Quick Quiz
3 questions · Break, Continue, Pass
1. What does break do inside a loop?
Correct! break exits the loop immediately — nothing after it inside that loop runs again.
Not quite — break stops the loop completely; that's continue's job to skip just one pass.
2. What does continue do inside a loop?
Correct! continue jumps straight to the next iteration, skipping whatever code comes after it in the
current pass.
Not quite — continue skips only the rest of the current pass; it doesn't exit the loop like break does.
3. What is the pass statement used for?
Correct! pass is a no-op — it lets you write syntactically valid, empty function bodies, classes, or
branches while you're still working on them.
Not quite — pass does nothing at all; it's just a placeholder for code you haven't written yet.
A list is an ordered, mutable collection that can store multiple
values — including duplicates and mixed types — in a single variable, written with square brackets.
fruits[0] accesses the first item using its index — Python
counts from 0, so fruits[0] is "Apple", not "Banana".
fruits.append("Mango") adds a new item to the end of the list in place —
unlike strings, lists can be modified freely after they're created.
How to Use & Where is Used
How to use: Wrap comma-separated values in square brackets to
create a list, then access or modify items using their index — list[0] is the
first item, list[-1] is the last.
Where is used: Student records and shopping carts, game
inventories, and data analysis or machine learning pipelines.
Common in: A shopping cart holding item names, or a task manager
holding a growing list of to-do items, are both natural fits for a Python list.
Example
Lists are the most commonly used collection type in Python — comfort with them pays off across almost
every project you write.
len(fruits) returns the number of items in the list, and fruits[-1] uses a negative index to grab the last item without needing
to know the list's length.
Practice Exercises
Exercise 1 — Build a List:
Create a list called colors containing "Red", "Green", and "Blue", then print it.
💡 Wrap the three strings in square brackets, separated by commas.
python
1colors = ["Red", "Green", "Blue"]2print(colors)
Exercise 2 — Add and Access:
Given numbers = [10, 20, 30], append 40 to the list,
then print the last item.
💡 Use .append() to add to the end, then numbers[-1]
to grab the last item.
A tuple is an ordered, immutable collection of values — once
created, its contents can never be changed, added to, or removed, and it's written with parentheses instead
of square brackets.
colors[1] accesses the item at index 1 — counting from
0, that's "Green", the second item in the tuple.
len(colors) returns 3, the total number of items — the
same len() function works on lists, strings, and tuples alike.
How to Use & Where is Used
How to use: Wrap comma-separated values in parentheses to create a
tuple — you can read items by index just like a list, but you can never modify, add, or remove them
afterward.
Where is used: Coordinates and fixed configuration values,
database records and function return values, and any data that must not change after creation.
Common in: GPS coordinates (latitude, longitude) or an RGB color
value are natural tuples — their individual values are always meant to travel together and never change
independently.
Example
Use a tuple any time you want to signal, just through the data type itself, that a value should be
treated as fixed.
python — interactive shell
1>>> point = (4, 7)2>>> point[0]344>>> point[0] = 105Traceback (most recent call last):6TypeError: 'tuple' object does not support item
assignment
Trying to change a tuple's contents raises a TypeError — this immutability is the
whole point of choosing a tuple over a list.
Practice Exercises
Exercise 1 — Build a Tuple:
Create a tuple called weekend containing "Saturday"
and "Sunday", then print it.
💡 Wrap the two strings in parentheses, separated by a comma.
python
1weekend = ("Saturday", "Sunday")2print(weekend)
Exercise 2 — Access by Index:
Given dimensions = (1920, 1080), print the width (first item) and height (second
item) on separate lines.
A set is an unordered collection of unique values, written with
curly braces — Python automatically discards duplicates and gives no guaranteed order.
numbers.add(6) inserts a new value. numbers.add(3) does
nothing visible — 3 is already in the set, and a set can never contain the same
value twice.
numbers.remove(2) deletes 2 from the set. Because sets
are unordered, don't expect print(numbers) to show items in the order you added
them.
How to Use & Where is Used
How to use: Wrap comma-separated values in curly braces to create
a set, then use .add() and .remove() to change it —
never index it with set[0].
Where is used: Removing duplicates from a collection, fast
membership testing, and mathematical set operations like union, intersection, and difference.
Common in: Removing duplicate tags from a list of user-submitted
tags, or quickly checking whether a username already exists in a large collection.
Example
set(some_list) is the fastest, most idiomatic way to de-duplicate a list in
Python.
python — interactive shell
1>>> numbers = {1, 2, 3}2>>> numbers[0]3Traceback (most recent call last):4TypeError: 'set' object is not
subscriptable
Sets have no order and no index, so numbers[0] raises a TypeError — use the in operator (e.g. 1 in
numbers) to check membership instead.
Practice Exercises
Exercise 1 — Build a Set:
Create a set called pets containing "Dog", "Cat", and "Dog" again, then print it.
💡 Sets automatically drop duplicate values, no matter how many times you add them.
python
1pets = {"Dog", "Cat", "Dog"}2print(pets)
Exercise 2 — Membership Test:
Given allowed = {"admin", "editor", "viewer"}, check whether "guest" is in allowed and print the result.
A dictionary is a collection of key-value pairs — instead of a
numeric index, you look up a value using the key it's stored under, inside curly braces with a colon between
each key and value.
student["name"] looks up the value stored under the key "name", which is "Jit".
student["age"] = 18 updates the existing "age" key in
place — dictionaries are mutable, just like lists.
How to Use & Where is Used
How to use: Create with {key: value,
...}, then read or update a value with dict[key] — use .get(key, default) when the key might not exist.
Where is used: Representing structured records (a user profile, a
product), configuration settings, and fast lookups by name or ID.
Common in: A user profile with fields like name, email, and age is
a textbook dictionary — the same shape as a JSON object.
Example
student.get("email", "Not Provided") safely returns a fallback value instead
of crashing when a key is missing.
greeting="Hello" gives that parameter a default value — if the
caller doesn't supply one, Python uses "Hello" automatically, which is exactly
what happens on line 4.
On line 5, passing a second value — "Welcome" — overrides the default, so greeting becomes "Welcome" for that call only.
A function can also hand back more than one value at once: return a, b actually
packages them into a single tuple, which you can unpack directly, e.g. x, y =
get_coordinates().
Beyond a fixed default, Python has two special parameter forms for when you don't know in advance how many
arguments will arrive: *args and **kwargs — covered in
the example below.
How to Use & Where is Used
How to use: Give a parameter a default with name=value in the signature to make it optional, or prefix a parameter with * or ** to collect an unknown number of extra
arguments.
Where is used: Functions where most calls use the same value but
occasionally need to override it — logging levels, retry counts, request timeouts, and flexible
configuration options.
Common in: A connect(timeout=30) style
function that almost always uses its default, but lets advanced callers override it when they need
to.
Example
*args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dictionary.
*args gathers any number of extra positional arguments into a tuple, so total() works no matter how many numbers are passed in. The double-star version, **kwargs, works the same way for keyword arguments — it collects any number of name=value pairs into a dictionary inside the function, which is how many real Python
libraries accept flexible configuration options.
Practice Exercises
Exercise 1 — Default Greeting:
Write a function greet(name, punctuation="!") that returns name followed by punctuation. Call it once with just a
name, and once overriding the punctuation with "?".
💡 Give punctuation a default value directly in the function signature.
python
1defgreet(name, punctuation="!"):2return name + punctuation34print(greet("Jit"))5print(greet("Jit", "?"))
Exercise 2 — Sum Any Numbers:
Write a function total(*args) that returns the sum of however many numbers are
passed in. Call it with 2 numbers, then with 5.
💡 *args collects every positional argument into a tuple you can pass straight
to sum().
Exercise 3 — Flexible Config:
Write a function show_config(**kwargs) that prints each keyword argument it
receives on its own line, as key: value. Call it with mode="dark" and retries=3.
💡 **kwargs is a dictionary inside the function — loop over it with .items().
python
1defshow_config(**kwargs):2for key, value in kwargs.items():3print(key + ":", value)45show_config(mode="dark", retries=3)
Quick Quiz
3 questions · Functions
1. What does giving a parameter a default value, like
def greet(name, greeting="Hello"), actually do?
Correct! A default value makes that parameter optional — the caller can leave it out and Python fills it
in automatically.
Not quite — a default value makes the parameter optional, not required.
2. What does *args collect inside a function?
Correct! *args gathers any number of extra positional arguments into a tuple.
Not quite — *args collects any number of positional arguments into a tuple.
3. What does **kwargs collect inside a function?
Correct! **kwargs gathers any number of keyword arguments into a dictionary.
Not quite — **kwargs collects keyword arguments into a dictionary inside the
function.
Quiz complete!
Chapter 4
23. Parameters & Args
Positional and keyword arguments, and the difference between a parameter and an argument.
def greet(name, age): defines two parameters — named
placeholders that only exist inside the function's own definition.
greet("Jit", 17) passes two argumentspositionally:
"Jit" binds to name and 17
binds to age, purely because of the order they're written in.
A parameter is the name used in the definition; an argument is the actual
value supplied at the call site — the two terms describe the same slot at two different moments.
How to Use & Where is Used
How to use: List parameter names in the function's parentheses,
then pass matching values by position, or by name using keyword=value, when you
call it.
Where is used: Any function that needs to operate on
caller-provided data — practically every real function you will ever write.
Common in: A create_user(username, email,
password) function needs different arguments every single time it's called, once per new
user.
Example
greet(age=17, name="Jit") works identically to positional order, because
keyword arguments are matched by name, not position.
Because the arguments are matched by name instead of position, greet(age=17, name="Jit") gives the exact same result as greet("Jit",
17) — keyword arguments free you from having to remember the exact parameter order.
Practice Exercises
Exercise 1 — Positional Call:
Write a function describe(item, price) that prints item and price on separate labeled lines, then call it
positionally with "Book" and 12.
💡 The first value you pass binds to the first parameter, purely by order.
Exercise 2 — Keyword Call:
Call the same describe(item, price) function from Exercise 1, but this time pass
price before item, using keyword arguments.
💡 Use price=..., item=... — keyword arguments don't care about order.
python
1describe(price=12, item="Book")
Exercise 3 — Spot the Bug:
For def greet(name, age):, the call greet(17, "Jit")
runs without crashing, but prints the wrong thing. Rewrite the call so "Jit"
correctly binds to name and 17 to age.
💡 Either swap the order back, or use keyword arguments to be explicit.
key=lambda p: p["age"] tells sorted() to compare people by
their "age" value — no separate named function required just to describe the sort
order.
Practice Exercises
Exercise 1 — Basic Lambda:
Write a lambda called double that returns twice its input, then call it with
9.
💡 lambda x: x * 2
python
1double = lambda x: x * 22print(double(9))
Exercise 2 — Filter with Lambda:
Given nums = [1, 2, 3, 4, 5, 6], use filter() with a
lambda to keep only the even numbers, then print the result as a list.
💡 x % 2 == 0 checks evenness; wrap filter() in list() to see the results.
Correct! It's a small, anonymous function — no name, no def, just a single
expression.
Not quite — lambda x: x * x creates a small anonymous function that returns
x * x.
2. Which keyword is required inside a lambda's body to send back a value?
Correct! A lambda has no return statement — its single expression's value is
returned automatically.
Not quite — a lambda never uses return; the expression's result is returned
automatically.
3. Lambdas are most commonly used for...
Correct! Lambdas shine as short, inline functions passed straight into another function.
Not quite — lambdas are best for short, throwaway logic passed directly into functions like sorted() or map().
Quiz complete!
Chapter 4
25. Modules
Organising code across multiple files.
Code
python
1# math_utils.py2defadd(a,
b):3return a + b45# main.py6import math_utils78print(math_utils.add(2, 3))
Explanation
A module is simply a .py file containing Python code —
functions, variables, classes — that can be reused from other files.
import math_utils loads math_utils.py as a module
object; math_utils.add(2, 3) reaches the function through the module's name.
Python runs the imported file once, top to bottom, the first time it's imported — after that, its functions
and variables are simply available through the module name.
How to Use & Where is Used
How to use: Save reusable code in its own .py file, then write import filename (no .py extension) in another file in the same folder to reach everything inside it via
filename.something.
Where is used: Splitting a large program into logical,
maintainable pieces — one file per responsibility — is a core habit in every real-world project.
Common in: A project with separate files like database.py, utils.py, and main.py, each handling one concern.
Example
from module import name imports just one name directly, so you can call it
without the module prefix.
python — interactive shell
1>>>from math_utils import add2>>> add(2, 3)35
from math_utils import add pulls just that one function into the current file, so
it's called directly as add(2, 3) instead of math_utils.add(2,
3).
Practice Exercises
Exercise 1 — Import a Module:
A file string_utils.py contains def shout(text): return
text.upper() + "!". Write the import line and a call that prints shout("hello").
💡 import string_utils, then reach the function through string_utils.shout(...).
Exercise 3 — Alias an Import:
Import the built-in math module under the shorter alias m, then print the square root of 81.
💡 import math as m, then m.sqrt(...).
python
1import math as m2print(m.sqrt(81))
Quick Quiz
3 questions · Modules
1. What is a Python module?
Correct! A module is just a .py file whose code you can reuse from other files.
Not quite — a module is a .py file containing reusable code.
2. What does from math_utils import add let you do differently from
import math_utils?
Correct! Importing a specific name lets you call it directly, without the module prefix.
Not quite — from module import name lets you call that name directly, with no
prefix.
3. What does import math as m do?
Correct! as m gives the imported module a shorter local alias, m.
Not quite — import math as m just gives the module a shorter alias to use
locally.
Quiz complete!
Chapter 4
26. Import Statement
Loading modules and packages.
Code
python
1import math2from math import
pi3from math import
sqrt as square_root45print(math.pi)6print(pi)7print(square_root(16))
Explanation
import math loads the whole module — every name inside it is reached through
math.name.
from math import pi pulls just that one name straight into the current file, so a
bare pi works with no prefix.
from math import sqrt as square_root combines both ideas: it imports one name
and gives it a new local name in a single line.
How to Use & Where is Used
How to use: Choose import module, from module import name, or from module import name as
alias — whichever keeps the call sites clearest.
Where is used: The Python Standard Library ships dozens of
ready-made modules — math, random, datetime, os — and pip lets you
install thousands more written by other people.
Common in:import random for generating
random values, or from datetime import datetime for working with dates and
times.
Example
Avoid from module import * in real projects — it pulls in every public name at
once, making it unclear where a name came from.
python — interactive shell
1>>>from math import *2>>> sqrt(25)35.04>>> pi53.141592653589793
from math import * pulls in every public name from math at
once — convenient here, but in a larger file it can silently overwrite names you've already defined, which is
why most Python style guides recommend avoiding it.
Practice Exercises
Exercise 1 — Import the Whole Module:
Import the random module and use random.randint(1, 10)
to print a random number between 1 and 10.
💡 import random, then random.randint(low, high).
python
1import random2print(random.randint(1, 10))
Exercise 2 — Import One Name:
Import just the randint function from random directly,
then call it the same way, without the module prefix.
💡 from random import randint
python
1from random import randint2print(randint(1, 10))
Exercise 3 — Import with an Alias:
Import the datetime class from the datetime module
under the alias dt, then print the current date and time using dt.now().
💡 from datetime import datetime as dt
python
1from datetime import datetime as dt2print(dt.now())
Quick Quiz
3 questions · Import Statement
1. What's the main risk of using from module import *?
Correct! Star imports make it unclear where any given name came from, and can silently overwrite existing
ones.
Not quite — the real risk is unclear name origins and silent overwrites, not speed or legality.
2. What does from math import sqrt as square_root do?
Correct! It imports just sqrt and gives it a new local name, square_root.
Not quite — it imports only sqrt, under the local alias square_root.
3. Where do third-party Python packages (not part of the Standard Library) typically
come from?
Correct! pip is the standard tool for installing third-party packages written by
the wider Python community.
Not quite — third-party packages are typically installed separately with pip.
A class is a blueprint for creating objects; an
object is one specific instance of that blueprint, holding its own data.
__init__ is the constructor — it runs automatically the moment
Player("Jit", 100) executes, and self refers to this
particular object being built.
self.name = name stores the passed-in value directly onto that object, so player.show_info() can read it back afterward.
How to Use & Where is Used
How to use: Define a class with class
Name:, add __init__(self, ...) to set up its data, then create real
objects by calling the class like a function, e.g. Player("Jit", 100).
Where is used: Modeling real-world entities that combine data and
behavior together — a Player, an Order, a Customer.
Common in: An e-commerce system modeling Product, Customer, and
Order as classes, each combining related data and behavior in one place.
Example
Every attribute set with self.xxx = ... inside __init__ becomes part of that specific object — different objects can hold completely
different values at the same time.
Exercise 3 — Two Independent Objects:
Create two separate Book objects with different titles and authors, and print
both titles to show they don't interfere with each other.
💡 Each object created from the class keeps its own separate copy of the attributes.
__init__ is a special method — the constructor — that Python
runs automatically the instant Car("Toyota", "Supra") executes, before anything
else touches the object.
self.brand = brand and self.model = model store the two
passed-in values onto the new object; car.show() simply reads them back afterward.
Because the constructor runs automatically, an object can never exist in a half-set-up state — it starts
life already holding exactly the values you require.
How to Use & Where is Used
How to use: Define def __init__(self,
...): inside a class to require and store any data the object genuinely needs from the moment
it's created.
Where is used: Any class needing guaranteed initial values —
nearly every class you will write.
Common in: A BankAccount class whose constructor requires an owner
name and starting balance, making it impossible to create an account with missing required
information.
Example
Constructor parameters can have default values too, just like any other function — making some arguments
optional at creation time.
Giving model a default value means Car("Honda") still
works even without one — it simply falls back to "Unknown", exactly like a default
value on any other function.
Practice Exercises
Exercise 1 — Basic Constructor:
Define a class Laptop whose constructor takes brand
and stores it on self, then create one and print its brand.
Exercise 3 — Default Value:
Give ram_gb a default of 8 in the constructor from
Exercise 2, then create a Laptop passing only a brand, and print its ram_gb.
class Dog(Animal): — the parentheses mean Dog inherits from
Animal, automatically reusing everything Animal defines.
dog.speak() works even though speak() is only
physically written inside Animal, because Dog gains
everything its parent has.
dog.bark() calls the method defined directly on Dog
itself — a subclass can freely add its own methods on top of what it inherits.
How to Use & Where is Used
How to use: Write class Child(Parent):
so Child automatically gains everything Parent
defines, then add or override methods directly on Child.
Where is used: Modeling genuine "is-a" relationships (a Dog IS AN
Animal) and sharing common behavior across a family of related classes.
Common in: A UI framework where Button, TextBox, and Checkbox all
inherit shared positioning and rendering logic from a common Widget base class.
Example
Python supports multiple inheritance too — class Dog(Animal,
Pet): — unlike languages such as Java or C#.
python — interactive shell
1>>>class Pet:2...defis_friendly(self):3...returnTrue4...5>>>class Dog(Animal, Pet):6...defbark(self):7...print("Woof!")8...9>>> dog = Dog()10>>> dog.is_friendly()11True
class Dog(Animal, Pet) inherits from both classes at once — Dog gets speak() from Animal and
is_friendly() from Pet simultaneously. This is called
multiple inheritance.
Practice Exercises
Exercise 1 — Basic Inheritance:
Define a class Vehicle with a method move() that
prints "Moving...", then a class Car(Vehicle) with its
own method honk() that prints "Beep!". Create a Car and call both methods.
💡 class Car(Vehicle): — the parentheses do the inheriting.
Exercise 2 — Multiple Subclasses:
Define a second class Bike(Vehicle) (reusing Vehicle
from Exercise 1) with its own method ring_bell() that prints "Ring ring!". Create a Bike and call move() and ring_bell().
💡 Bike inherits move() from Vehicle the same way Car did.
Exercise 3 — Spot the Relationship:
A Cat genuinely "is-a" kind of Animal. Write that
relationship as actual code, giving Animal a speak()
method and Cat its own meow() method.
💡 Inheritance should model a genuine "is-a" relationship — a Cat IS AN Animal.
Correct! The parentheses mean Dog inherits from, and automatically gains, everything Animal defines.
Not quite — class Dog(Animal): means Dog inherits from Animal.
2. Why can dog.speak() be called even though speak() is not
defined inside Dog?
Correct! A subclass automatically inherits every method its parent class defines.
Not quite — Dog automatically inherits every method defined on Animal, its parent class.
3. Does Python support inheriting from more than one class at once?
Correct! Python fully supports multiple inheritance, listing several parent classes in the parentheses.
Not quite — Python does support multiple inheritance, e.g. class Dog(Animal,
Pet):.
Quiz complete!
Chapter 5
30. Polymorphism
One interface, many implementations.
Code
python
1class Bird:23defsound(self):4print("Chirp")567class Cat:89defsound(self):10print("Meow")111213animals = [Bird(), Cat()]1415for animal in
animals:16 animal.sound()
Explanation
Polymorphism means different objects can respond to the same method call in their own way
— "one interface, many behaviors." Bird and Cat are
completely unrelated classes, yet both define a .sound() method.
for animal in animals: animal.sound() calls .sound() on
each object without knowing or caring which class it is — this works because of duck
typing: if an object has the method you're calling, it works, regardless of class or ancestry.
Unlike inheritance (Chapter 29), polymorphism here doesn't require Bird and Cat to share any common parent class at all — Python checks for the method at runtime,
not compile time.
How to Use & Where is Used
How to use: Give unrelated classes a method with the same name,
then call that method on each object in a loop — Python doesn't care which class it belongs to, only that
the method exists.
Where is used: Writing generic code that works with many different
object types, and any collection of varied objects sharing a common method name.
Common in: A game engine calling .render() on a list of many
different unrelated game object types, as long as each one happens to implement .render().
Example
Because Python checks method calls at runtime, a typo in a method name won't be caught until that exact
line runs — thorough testing matters more than in statically-typed languages.
python — interactive shell
1>>>class Circle:2...defarea(self):3...return3.144...5>>>class Square:6...defarea(self):7...return48...9>>> shapes = [Circle(),
Square()]10>>>for shape in shapes:11...print(shape.area())12...133.14144
Circle and Square share no common parent, yet the same
loop calls .area() on both — each object responds in its own way to the identical
method call.
Practice Exercises
Exercise 1 — Shared Method Name:
Define classes Car and Boat, each with a method travel() that prints a different message. Put one of each in a list and call .travel() on both without checking their types.
💡 Give both classes a method with the exact same name — Python doesn't need them to share a parent class.
python
1class Car:23deftravel(self):4print("Driving on
the road")567class Boat:89deftravel(self):10print("Sailing
on water")111213vehicles = [Car(), Boat()]1415for vehicle in vehicles:16 vehicle.travel()
Exercise 2 — Add a Third Class:
Add a class Fish (with a .sound() method that prints
"Blub") to the animals list from the lesson, alongside
Bird and Cat, and confirm the same loop still works
for all three.
💡 As long as Fish also defines a .sound() method, it
slots right into the same loop — no other changes needed.
python
1class Fish:23defsound(self):4print("Blub")567animals = [Bird(), Cat(), Fish()]89for animal in animals:10 animal.sound()
Exercise 3 — Shape Areas:
Define classes Rectangle (width, height set in __init__) and Circle (radius set in __init__),
each with an area() method. Put one of each in a list and print each area in a
loop.
💡 Rectangle area is width * height; circle area is 3.14 *
radius * radius.
Encapsulation means bundling data with the methods that operate on it, and restricting direct access to
that data from outside the class.
self.__balance uses a double leading underscore, which triggers
Python's "name mangling" — internally renaming it to _BankAccount__balance, making
it much harder to access accidentally from outside the class.
deposit() and get_balance() are the public gateway for
outside code to change or read the balance — nothing outside the class can set __balance to an arbitrary value directly.
How to Use & Where is Used
How to use: Prefix an attribute with a double underscore (or a
single one, by convention) to signal it's internal, then expose controlled public methods like deposit() to read or change it safely.
Where is used: Any class protecting its internal state from
invalid changes — banking systems, game stats, and inventory counts.
Common in: A BankAccount class exposing deposit() and withdraw()
methods instead of a public balance attribute, so external code can never set the balance to an arbitrary
value directly.
Example
Python's privacy is convention-based rather than strictly enforced — a single leading underscore
(_balance) signals "internal use only" and is actually more common in idiomatic Python than double
underscores.
The double underscore isn't true enforced privacy — it's still reachable via its mangled name, _BankAccount__balance. It's a safety net that discourages accidental access, not a hard
security boundary.
Practice Exercises
Exercise 1 — Basic Encapsulation:
Define a class Wallet with a private __cash attribute
starting at 0, a method add_cash(amount) that increases it, and a method get_cash() that returns it. Create a wallet, add 200, and print the balance.
💡 Use self.__cash (double underscore) inside __init__, and only change it through add_cash().
Exercise 2 — Validated Withdrawal:
Add a withdraw(amount) method to BankAccount that only
subtracts the amount if there's enough balance, otherwise prints "Insufficient
funds". Test it by depositing 100 then trying to withdraw 200.
💡 Check amount > self.__balance inside withdraw()
before subtracting anything.
Exercise 3 — Single Underscore Convention:
Define a class Employee with a single-underscore attribute _salary set in __init__, and a method get_salary() that returns it. Create an employee with salary 50000 and print it
using the method.
💡 A single underscore (_salary) is the more common, idiomatic Python convention
for "internal use only."
1. What does Python's "name mangling" do to an attribute like __balance?
Correct! Name mangling renames a double-underscore attribute to _ClassName__attribute internally,
discouraging accidental access from outside.
Not quite — name mangling renames the attribute internally to _ClassName__balance; it doesn't delete or
lock it.
2. Is a double-underscore attribute truly impossible to access from outside the
class?
Correct! Name mangling is a safety net, not true enforced privacy — the mangled name is still reachable
directly.
Not quite — a double-underscore attribute can still be accessed via its mangled name; it's a deterrent,
not an absolute lock.
3. Why is get_balance() considered better practice than a public
balance attribute?
Correct! A method gives you a controlled gateway — you can add validation, logging, or change the internal
representation later without breaking outside code.
Not quite — the real benefit is control: a method lets you validate, log, or change internals later
without breaking any code that calls it.
Quiz complete!
Chapter 6
32. File Handling
Reading from and writing to files.
Code
python
1withopen("notes.txt", "w") as file:2 file.write("Hello, Python!")34withopen("notes.txt", "r") as file:5 content = file.read()67print(content)
Explanation
open("notes.txt", "w") opens (creating it if needed) notes.txt in write mode — "w" replaces the
file's entire contents.
The with statement is a context manager — it automatically
closes the file afterward, even if an error happens partway through.
Opening the same file again in "r" (read) mode and calling .read() loads the whole file back as a single string.
How to Use & Where is Used
How to use:open(filename, mode) —
common modes are "r" (read), "w" (write, overwrites),
and "a" (append) — always wrapped in a with block so
the file closes automatically.
Where is used: Saving user data between program runs, reading
configuration, and processing logs or datasets stored on disk.
Common in: A program that reads a list of usernames from a .txt file, or appends a new line to a running log file every time it's
called.
Example
"a" (append) mode adds to the end of the file instead of erasing what's
already there.
python — interactive shell
1>>>withopen("log.txt", "a") as file:2... file.write("New
entry\n")3...4>>>withopen("log.txt", "r") as file:5...print(file.read())6...7New entry
"a" mode adds to the end of the file instead of erasing what's
already there — useful for logs that should keep growing over time.
Practice Exercises
Exercise 1 — Write to a File:
Open a file called "greeting.txt" in write mode and write the text "Hi there!" into it.
💡 open("greeting.txt", "w") as file: then file.write(...).
python
1withopen("greeting.txt", "w")
as file:2 file.write("Hi there!")
Exercise 2 — Read a File:
Open "greeting.txt" in read mode, read its entire contents into a variable, and
print that variable.
💡 open(..., "r") as file: then content = file.read().
python
1withopen("greeting.txt", "r")
as file:2 content = file.read()34print(content)
Exercise 3 — Append to a File:
Open "greeting.txt" in append mode and add " See you
soon!", without erasing what's already there.
💡 "a" mode adds to the end instead of overwriting.
python
1withopen("greeting.txt", "a")
as file:2 file.write(" See you
soon!")
Quick Quiz
3 questions · File Handling
1. What does the with statement do when working with files?
Correct! with guarantees the file gets closed, error or not.
Not quite — with automatically closes the file afterward, even if an error
occurs.
2. What's the difference between "w" mode and "a" mode?
Correct! "w" overwrites; "a" appends without erasing.
Not quite — "w" overwrites the file, while "a" adds to
the end.
3. What does file.read() return?
Correct! .read() loads the whole file back as one string.
Not quite — .read() returns the entire file's contents as a single string.
Quiz complete!
Chapter 6
33. Exceptions
Catching and handling runtime errors.
Code
python
1try:2 number = int("abc")3except ValueError:4print("That's not a
valid number!")56print("Program
continues...")
Explanation
Code inside try: runs normally until something goes wrong — int("abc") raises a ValueError because "abc" can't be converted to a number.
except ValueError: catches that specific error and runs its block instead of
letting the program crash.
Because the exception was caught, "Program continues..." still runs — without
try/except, the program would stop immediately at the
error.
How to Use & Where is Used
How to use: Wrap risky code in try:,
catch the specific error type you expect in except:, and optionally add finally: for code that always runs.
Where is used: Anywhere a runtime error is a realistic possibility
— user input, file access, network requests, or external data that might be missing or malformed.
Common in: Validating user input in a loop, retrying a failed
network request, or safely cleaning up a resource in a finally block no matter
what happened.
Example
Catch specific exception types like ValueError or ZeroDivisionError rather than a bare except:, so unrelated
bugs aren't silently hidden.
python — interactive shell
1>>>try:2... result = 10 / 03...except
ZeroDivisionError:4...print("Can't divide by zero!")5...finally:6...print("Done trying.")7...8Can't divide by zero!9Done trying.
finally runs no matter what — whether the try block
succeeded, failed, or was caught — making it the right place for cleanup code like closing a file or
connection.
Practice Exercises
Exercise 1 — Catch a ValueError:
Write code that tries to convert the string "hello" to an integer inside a try block, and prints "Invalid number!" if it fails.
💡 except ValueError:
python
1try:2 number = int("hello")3except ValueError:4print("Invalid
number!")
Exercise 2 — Catch a ZeroDivisionError:
Write code that tries to divide 10 by 0 inside a try block, and prints "Cannot divide by zero!" if it
fails.
💡 except ZeroDivisionError:
python
1try:2 result = 10 / 03except ZeroDivisionError:4print("Cannot
divide by zero!")
Exercise 3 — Use finally:
Write a try/except for dividing 10 by 0, and add a
finally block that prints "Attempt finished" no matter
what happens.
💡 finally: always runs, whether or not an exception occurred.
python
1try:2 result = 10 / 03except ZeroDivisionError:4print("Cannot
divide by zero!")5finally:6print("Attempt
finished")
Quick Quiz
3 questions · Exceptions
1. What happens to code inside a try block if no error occurs?
Correct! With no error, the try block just runs normally and every except is skipped.
Not quite — with no error, the try block runs normally and the except blocks are simply skipped.
2. What is the finally block used for?
Correct! finally always runs, error or not.
Not quite — finally always runs, whether or not an exception occurred.
3. Why is it better to catch a specific exception like ValueError rather
than a bare except:?
Correct! A specific except type avoids silently swallowing unrelated bugs.
Not quite — a specific except only catches the error you expect, keeping other bugs visible.
Quiz complete!
Chapter 6
34. Regular Expressions
Pattern matching with the re module.
Code
python
1import re23text = "My phone number is
123-456-7890"4match = re.search(r"\d{3}-\d{3}-\d{4}",
text)56print(match.group())
Explanation
import re loads Python's built-in regular expressions module,
used for pattern matching inside text.
r"\d{3}-\d{3}-\d{4}" is a raw string pattern: \d means
"any digit", and {3} means "exactly 3 of the previous thing" — so this pattern
looks for three digits, a dash, three digits, a dash, four digits.
re.search() scans the text for the first place that pattern matches, and .group() returns the actual matched text.
How to Use & Where is Used
How to use: Write a pattern as a raw string r"...", then use re.search() for the first match, re.findall() for every match, or re.sub() to replace
matches.
Where is used: Validating input formats (emails, phone numbers,
postal codes), extracting specific pieces of text from a larger document, and flexible
search-and-replace.
Common in: Checking whether a signup form's email field looks like
a real email address before accepting it.
Example
Always check that a re.search() result isn't None
before calling .group() on it — no match returns None.
python — interactive shell
1>>>import re2>>> text = "Call 111-222-3333 or 444-555-6666"3>>> re.findall(r"\d{3}-\d{3}-\d{4}", text)4['111-222-3333', '444-555-6666']
re.findall() returns every match in the text as a list, instead of
stopping at the first one like re.search() does.
Practice Exercises
Exercise 1 — Find a Match:
Given text = "My email is jit@example.com", use re.search() with the pattern r"\w+@\w+\.\w+" to find the
email address, then print match.group().
💡 re.search(pattern, text) returns a match object, or None if nothing matched.
python
1import re23text = "My email is
jit@example.com"4match = re.search(r"\w+@\w+\.\w+",
text)5print(match.group())
Exercise 2 — Find All Matches:
Given text = "Order 1, Order 2, Order 3", use re.findall() with the pattern r"\d+" to get every number
as a list, then print it.
💡 \d+ means "one or more digits".
python
1import re23text = "Order 1, Order 2, Order
3"4numbers = re.findall(r"\d+",
text)5print(numbers)
Exercise 3 — Replace a Match:
Given text = "I love cats", use re.sub() to replace
"cats" with "dogs", and print the result.
💡 re.sub(pattern, replacement, text)
python
1import re23text = "I love cats"4result = re.sub(r"cats", "dogs", text)5print(result)
Quick Quiz
3 questions · Regular Expressions
1. What does \d{3} match in a regular expression?
Correct! \d means "any digit" and {3} means "exactly
three of it".
Not quite — \d{3} matches exactly three digits.
2. What does re.findall() return?
Correct! re.findall() returns every match as a list.
Not quite — re.findall() returns a list of every match found, not just the
first.
3. What should you check before calling .group() on the result of
re.search()?
Correct! A failed re.search() returns None, and
calling .group() on that crashes.
Not quite — always check the result isn't None before calling .group().
The datetime module is Python's built-in toolkit for working with dates and
times. datetime.now() returns the current date and time as a datetime object.
.strftime(format) turns a datetime object into a
readable string. Format codes control the output: %Y is a 4-digit
year, %B is the full month name, %d is the day of month.
timedelta represents a span of time — days, hours, minutes. Adding or subtracting
a timedelta from a datetime shifts it forward or
backward, which is how you do simple date arithmetic in Python.
How to Use & Where is Used
How to use: Get the current moment with datetime.now(), format it into text with .strftime(), and
shift it forward or backward with timedelta.
Where is used: Timestamping logs, calculating someone's age,
scheduling reminders, and figuring out how many days remain until a deadline.
Common in: Showing "posted 3 days ago" style timestamps and
"member since" dates on websites.
Example
Subtracting one datetime from another gives you a timedelta, whose .days attribute is the whole number of days
between them.
Subtracting today - birthday gives a timedelta, and .days converts that gap into a plain number of days.
Practice Exercises
Exercise 1 — Format Today's Date:
Use datetime.now() and .strftime() to print today's
date in the format "23 August 2026" (day, full month name, year).
Correct! datetime.now() gives you the current date and time as a datetime object.
Not quite — datetime.now() returns both the current date and
time.
2. What does .strftime() do?
Correct! .strftime(format) turns a datetime into a
string using format codes like %Y and %B.
Not quite — .strftime() formats a date into a string, it
doesn't parse one.
3. What type of object do you get from subtracting two datetime objects?
Correct! Subtracting two datetime objects gives a timedelta representing the gap between them.
Not quite — the result is a timedelta, which you can read with .days.
Quiz complete!
Chapter 6
36. Generators
Lazy sequences with yield.
Code
python
1defcount_up_to(limit):2 n = 13while n <= limit:4yield n5 n += 167for number in
count_up_to(5):8print(number)
Explanation
A function that contains yield instead of return
becomes a generator function. Calling it doesn't run the body right away — it hands back a
generator object that produces values one at a time, only when asked.
Each time the loop asks for the next value, the function runs until it hits yield, hands that value back, and then pauses — remembering exactly
where it left off. On the next request, it picks up right after that yield instead
of starting over.
You can also build a lightweight generator inline with a generator expression, which looks
like a list comprehension but uses parentheses: (x * x for x in range(10)).
Because values are produced one at a time instead of all at once, generators use far less memory than
building a full list — especially useful for huge or infinite sequences.
How to Use & Where is Used
How to use: Put yield inside a function
to make it a generator, then iterate with a for loop or pull values one at a
time with next().
Where is used: Reading huge files line by line without loading the
whole file into memory, streaming data pipelines, and producing infinite sequences.
Common in: Processing large log files or datasets that are too big
to fit in memory all at once.
Example
Once a generator has produced a value, it doesn't go back — each call to next() moves it forward, and it can only be consumed once.
python — interactive shell
1>>> squares = (x * x for x in range(5))2>>> next(squares)304>>> next(squares)516>>> list(squares)7[4, 9, 16]
The first two next() calls consume 0 and 1, so list(squares) only sees what's left: 4, 9, 16.
Practice Exercises
Exercise 1 — Even Number Generator:
Write a generator function even_numbers(limit) that yields every even number
from 2 up to limit (inclusive), then print each one with a for loop.
💡 Start n at 2 and increase it by 2 each time through the loop.
python
1defeven_numbers(limit):2 n = 23while n <= limit:4yield n5 n += 267for num in
even_numbers(10):8print(num)
Exercise 2 — Cubes Generator Expression:
Create a generator expression that produces the cube of every number from 1 to 5, convert it to a list,
and print it.
💡 Use x ** 3 inside (... for x in range(1, 6)).
python
1cubes = (x ** 3for x in range(1, 6))2print(list(cubes))
Exercise 3 — Countdown Generator:
Write a generator function countdown(n) that yields n,
then n - 1, all the way down to 1.
💡 Loop while n >= 1, yielding n then decreasing
it.
python
1defcountdown(n):2while n >= 1:3yield n4 n -= 156for num in
countdown(5):7print(num)
Quick Quiz
3 questions · Generators
1. Which keyword turns a normal function into a generator function?
Correct! A function containing yield becomes a generator function.
Not quite — it's yield, not return, that makes a
function a generator.
2. What happens when you call a generator function?
Correct! Calling a generator function returns a generator object right away — the body only runs as values
are requested.
Not quite — the body doesn't run until you start pulling values from the generator.
3. Why are generators memory-efficient?
Correct! Generators produce values lazily, one at a time, instead of holding a full sequence in memory.
Not quite — the memory savings come from producing values one at a time, on demand.
In Python, functions are first-class objects — they can be passed around like any other
value: stored in variables, passed as arguments, and returned from other functions. That's what makes
decorators possible.
A decorator is a function that takes another function, wraps it in a new function with
extra behaviour, and returns that wrapper. The inner wrapper() calls the original
func and can run code before or after it.
The @log_call line above greet is just shorthand for
greet = log_call(greet) — Python rebinds the name greet
to the wrapped version automatically.
*args and **kwargs in wrapper() let it accept and forward any arguments the original
function needs, whatever they turn out to be.
Advanced note: real-world decorators often add @functools.wraps(func)
above wrapper to preserve the original function's name and docstring — optional
for now, but good practice later.
How to Use & Where is Used
How to use: Write a function that takes func, define an inner wrapper() that calls it with added
behaviour, return wrapper, then apply it with @decorator_name above the function you want to change.
Where is used: Logging function calls, timing how long code takes
to run, checking permissions before a function runs, and caching results.
Common in: Web frameworks like Flask, where a decorator turns a
plain function into a route handler for a URL.
Example
A decorator can measure or change behaviour around a function without ever touching that function's own
code.
timer measures how long slow_add takes to run, without
slow_add itself containing any timing code.
Practice Exercises
Exercise 1 — Uppercase Decorator:
Write a decorator shout that makes a function's returned string uppercase. Apply
it to greet(name), which returns f"hello {name}", and
print greet("sam").
💡 Call func(*args, **kwargs) first, save the result, then call .upper() on it before returning.
python
1defshout(func):2defwrapper(*args, **kwargs):3 result = func(*args, **kwargs)4return result.upper()5return wrapper67@shout8defgreet(name):9returnf"hello {name}"1011print(greet("sam"))
Exercise 2 — Call Counter Decorator:
Write a decorator count_calls that keeps a counter and prints how many times the
decorated function has been called, each time it's called.
💡 A plain int variable inside count_calls works, but
a small dict avoids scoping issues when updating it inside wrapper.
Exercise 3 — Repeat Decorator:
Write a decorator repeat that calls the decorated function
twice every time it's invoked. Apply it to a function that prints "Hello!".
💡 Just call func(*args, **kwargs) twice inside wrapper.
This is a number guessing game — one of the classic first projects, and a good check that
loops, conditions, and user input all click together. random.randint(1, 100) picks
a whole number from 1 to 100, both ends included.
The while guess != secret loop keeps asking for a new guess until it matches,
printing "Too low!" or "Too high!" along the way to
guide the player.
Mini projects like this exist to make you combine ideas from earlier lessons instead of
practicing them one at a time. Don't just run the starter code — try extending it: limit the number of
attempts, add a difficulty level that changes the range, or track the best score across games.
A few more project ideas to build the same way: a password generator and an
expense tracker — both shown in the exercises below as starting points for you to extend.
How to Use & Where is Used
How to use: Get the basic version of a project working first, run
it, then add one small feature at a time instead of trying to build everything at once.
Where is used: Practicing lessons 1 through 37 together in code
you write yourself, rather than reading about them in isolation.
Common in: Coding bootcamps and self-taught paths, which lean on
small self-contained projects like these to build confidence before tackling bigger builds.
Example
Here's a second project idea to try the same pattern with: a simple to-do list that
runs in the terminal.
python
1tasks = []23whileTrue:4 action = input("Add, List, or Quit?
").lower()5if action == "add":6 tasks.append(input("Task: "))7elif action == "list":8for i, task in
enumerate(tasks, 1):9print(f"{i}. {task}")10elif action == "quit":11break
This loop keeps running until the user quits, storing tasks in a list — extend it by adding a way to mark a
task done or save the list to a file.
Practice Exercises
Exercise 1 — Password Generator:
Use the starter code below to generate a random 12-character password. Extend it so the password is
guaranteed to contain at least one digit and one symbol.
💡 Generate the password normally first, then check with an if whether it
contains a digit — if not, swap in a random one.
python
1import random2import string34defgenerate_password(length=12):5 chars = string.ascii_letters + string.digits + "!@#$%"6return"".join(random.choice(chars) for _ in range(length))78print(generate_password())
Exercise 2 — Expense Tracker:
Use the starter code below to log a few expenses and print the total. Extend it so it also prints the
total per category (e.g. food, transport).
💡 Add a "category" key to each expense dict, then filter the list before
summing.
python
1expenses = []23defadd_expense(name, amount):4 expenses.append({"name": name,
"amount": amount})56add_expense("Coffee", 4.50)7add_expense("Bus ticket", 2.75)89total = sum(e["amount"] for e in expenses)10print(f"Total spent: ${total:.2f}")
Exercise 3 — Extend the Number Guessing Game:
Add an attempt counter to the number guessing game from the Code section above, and print how many guesses
it took to win.
💡 Start a counter at 0 before the loop and increase it by 1 on every pass through the loop.
python
1import random23secret = random.randint(1, 100)4guess = None5attempts = 067while guess != secret:8 guess = int(input("Guess a number
(1-100): "))9 attempts += 110if guess < secret:11print("Too
low!")12elif guess > secret:13print("Too
high!")1415print(f"You got it in {attempts} attempts!")
Quick Quiz
3 questions · Mini Projects
1. What does random.randint(1, 100) return?
Correct! random.randint(a, b) returns a random whole number, including both
a and b.
Not quite — it returns one random integer inside the given range, both ends included.
2. In the to-do list project, why is while True combined with
break?
Correct! The loop repeats until break runs, which happens when the user asks to
quit.
Not quite — break is what lets the user exit an otherwise endless while True loop.
3. What's a good way to grow a beginner mini-project?
Correct! Small, tested additions are far easier to debug than one giant untested change.
Not quite — growing a project one small, tested step at a time keeps it manageable.
Quiz complete!
Chapter 7
39. Capstone Project
A full project bringing it all together.
Code
python — finance_tracker.py
1class FinanceTracker:2def__init__(self):3 self.transactions = []45defadd_transaction(self, description, amount, category):6 self.transactions.append({7"description": description,8"amount": amount,9"category": category,10 })1112deftotal_balance(self):13return sum(t["amount"] for t in
self.transactions)1415defby_category(self, category):16return [t for t
in self.transactions if t["category"] == category]
Explanation
The capstone project combines almost everything from this course into one build:
variables, functions, loops, OOP,
file handling, and exceptions all working together instead of in isolated
lessons.
Suggested project: a Personal Finance Tracker. A FinanceTracker
class stores every transaction as a dictionary, then offers methods to compute a running balance and filter
by category — the same pattern real budgeting apps use.
Suggested file structure: keep the FinanceTracker class in finance_tracker.py, put a menu loop that talks to it in main.py, and save transactions to data.json so they survive
between runs.
Core features: add a transaction, view the total balance, list transactions by category,
and save/load transactions from a file. Stretch goals: a monthly summary, simple budget
warnings when a category goes over a limit, and exporting transactions to CSV.
How to Use & Where is Used
How to use: Build and test FinanceTracker on its own first — try calling its methods directly in the shell —
then wrap a menu loop around it, and add file saving last.
Where is used: This mirrors how real applications are structured —
a data model (the class) kept separate from the interface (the menu loop) that talks to it.
Common in: Almost every real piece of software separates "data and
logic" from "input and output" — not just finance apps.
Example
Storing income as positive amounts and expenses as negative amounts means total_balance() can just add everything together — no separate income/expense math
needed.
Exercise 2 — Handle Bad Input:
Write a loop that asks the user for an amount and uses try/except to catch a
ValueError if they type something that isn't a number, printing a friendly
message instead of crashing.
💡 Wrap float(raw_amount) in a try block, and break out of the loop only once it succeeds.
python
1whileTrue:2 raw_amount = input("Enter amount:
")3try:4 amount = float(raw_amount)5break6except ValueError:7print("That's not
a valid number, try again.")89print(f"Amount accepted: {amount}")
Exercise 3 — Running Totals (Stretch Goal):
Write a generator function running_totals(transactions) that yields the balance
so far after each transaction is added, one at a time.
💡 Keep a running_total variable, add each transaction's amount to it, then
yield it inside the loop.
python
1defrunning_totals(transactions):2 running_total = 03for t in
transactions:4 running_total += t["amount"]5yield running_total67transactions = [8 {"amount": 1500},9 {"amount": -85.40},10 {"amount": -20},11]1213for total in running_totals(transactions):14print(total)
Quick Quiz
3 questions · Capstone Project
1. In the FinanceTracker example, why are expenses often stored as
negative numbers?
Correct! Signed amounts let a simple sum() work as the balance, with no extra
branching needed.
Not quite — it's a design choice that keeps total_balance() a simple addition.
2. Which module would you use to save the tracker's transactions so they aren't lost
when the program closes?
Correct! json.dump() writes Python data structures like lists and dicts straight
to a file.
Not quite — the json module is built for reading and writing structured data
like this.
3. What's the benefit of keeping FinanceTracker (data + logic) separate
from a menu loop (input/output)?
Correct! Separating data/logic from input/output is a pattern used across almost all real software, not
just this project.
Not quite — the split makes the class reusable and testable on its own, independent of the menu.