Chapter 1

1. Introduction

What Python is and why it matters.

python
1print("Hello, World!") # the classic first program 2print("Welcome to Python.")

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: 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.
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>>> print("Hello, World!") 2Hello, World! 3>>> 2 + 2 44

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.

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.
python
1print("Pizza") 2print("Hiking") 3print("Old movies")

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

3 questions · Introduction

1. Who created Python?

2. What does it mean that Python is a "high-level" language?

3. Which of these is not a typical use case for Python?

Quiz complete!
Chapter 1

2. Installation

Setting up Python on your machine.

terminal
1$ python3 --version 2Python 3.12.4

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: 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.
Typing python3 alone (no filename after it) drops you into the interactive shell — a good way to sanity-check your install.
terminal
1$ python3 2Python 3.12.4 (main, default) 3>>> exit()

Once you see the >>> prompt, Python is installed and working. Type exit() and press Enter to leave the shell.

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$ python3 2>>> 1 + 1 32 4>>> exit()

3 questions · Installation

1. Where should you download Python from?

2. What does pip do?

3. Which command checks your installed Python version on macOS/Linux?

Quiz complete!
Chapter 1

3. Your First Program

Writing and running your first script.

hello.py
1name = "Cynpho" 2print("Hello,", name) 3print("This is my first Python program.")

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: 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.
This is a terminal session, not the Python shell — the $ is a normal command-line prompt.
terminal
1$ python3 hello.py 2Hello, Cynpho 3This 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.

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

3 questions · Your First Program

1. What file extension do Python scripts use?

2. Which terminal command runs a file called app.py?

3. What does print("Python", "is", "fun") output?

Quiz complete!
Chapter 1

4. Syntax & Comments

Python syntax rules and how to write comments.

python
1# This is a single-line comment 2print("Python reads this line") # ignored by Python 3""" 4This is a multi-line comment. 5Python skips this whole block too. 6""" 7print("...but not this line")

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: 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.
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 code 2Line one 3>>> 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.

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 message 2print("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.
python
1""" 2This script demonstrates 3basic comment syntax. 4""" 5print("Script started")

Exercise 3 — Spot the Mistake:
This line has been indented for no reason and will fail to run:     print("Hi"). Rewrite it correctly.

💡 Only indent a line when it's genuinely inside a block. A standalone statement starts at column zero.
python
1print("Hi")

3 questions · Syntax & Comments

1. Does Python execute the text inside a comment?

2. What is a docstring?

3. Why does indentation matter in Python?

Quiz complete!
Chapter 1

5. Variables

Naming and storing data in Python.

python
1name = "Maya" 2age = 25 3is_online = True 4 5print(name, age, is_online)

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: 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.
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.
python — interactive shell
1>>> score = 10 2>>> score = score + 5 3>>> score 415 5>>> score = "high score" 6>>> score 7'high score'

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.

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.
python
1name = "Alex" 2age = 22 3language = "Python" 4print(name, age, language)

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, 3 2print(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 digit 2# total_2 -> valid 3# total-2 -> invalid, "-" is the subtraction operator 4# class -> invalid, it's a reserved keyword

3 questions · Variables

1. Which symbol assigns a value to a variable in Python?

2. Which of these is a valid Python variable name?

3. What happens if you reassign a variable to a value of a different type?

Quiz complete!
Chapter 1

6. Data Types

int, float, str, bool, and None.

python
1name = "Jit" 2age = 17 3price = 99.99 4is_student = True 5nothing = None 6 7print(type(name)) 8print(type(age)) 9print(type(price)) 10print(type(is_student)) 11print(type(nothing))

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: 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.
type() always prints its answer as <class '...'> — the part inside the quotes is the type's actual name.
python — interactive shell
1>>> type(3.14) 2<class 'float'> 3>>> type("hi") 4<class 'str'> 5>>> type(None) 6<class 'NoneType'>

Notice None gets its own type rather than being treated as False or 0 — it specifically means "nothing has been assigned here."

Exercise 1 — Check the Type:
Create a variable holding a whole number, then print its type.

💡 A whole number with no decimal point is automatically an int.
python
1quantity = 12 2print(type(quantity)) # <class 'int'>

Exercise 2 — Build a Profile:
Create four variables — a str name, an int age, a float balance, and a bool is_active — and print each one's type.

💡 Give balance a decimal point so Python treats it as a float.
python
1name = "Riya" 2age = 21 3balance = 450.75 4is_active = True 5print(type(name), type(age), type(balance), type(is_active))

Exercise 3 — What's the Type?:
Predict the type of value = 10 / 2 before running it. Were you right?

💡 The / operator always performs true division in Python 3 — check what that returns, even for a clean division.
python
1value = 10 / 2 2print(value, type(value)) # 5.0 <class 'float'>

3 questions · Data Types

1. What does the built-in type() function do?

2. What is the key difference between int and float?

3. What are the only two possible values of a bool?

Quiz complete!
Chapter 1

7. Functions in Python

Functions are blocks of reusable code that perform a specific task.

python
1def greet(name): 2 message = f"Hello, {name}!" # f-string — embeds name inside string 3 return message # sends value back to caller 4 5result = greet("Cynpho") # call the function 6print(result) # Hello, Cynpho!

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: 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.
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>>> def add(a, b): 2... return a + b 3... 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.

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
1def square(n): 2 return n ** 2 3 4print(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"):
python
1def greet(name, greeting="Hello"): 2 print(f"{greeting}, {name}!") 3 4greet("Alice") # Hello, Alice! 5greet("Bob", "Hi") # Hi, Bob!

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(...).
python
1def min_max(nums): 2 return min(nums), max(nums) 3 4lo, hi = min_max([3, 1, 8, 4, 9, 2]) 5print(lo, hi) # 1 9

3 questions · Functions in Python

1. What keyword is used to define a function in Python?

2. What does the return statement do inside a function?

3. What will this code print?
def double(n): return n * 2
print(double(6))

Quiz complete!
Chapter 1

8. User Input

Reading data from the user with input().

python
1name = input("Enter your name: ") 2 3print("Hello,", name)

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: 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.
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: Alex 3>>> print("Hello,", name) 4Hello, Alex

name now holds the string "Alex" — exactly what was typed, nothing more.

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.
python
1name = input("Name: ") 2color = input("Favorite color: ") 3print(name, "likes", color)

Exercise 3 — Remember the Type:
Read the user's age with input(), then print its type. What type do you get, even though it "looks like" a number?

💡 input() returns a string no matter what the user types.
python
1age = input("Enter your age: ") 2print(type(age)) # <class 'str'> — always a string

3 questions · User Input

1. What data type does input() always return?

2. How would you safely read a number from the user?

3. Why should user input always be validated?

Quiz complete!
Chapter 1

9. Type Conversion

Converting between different data types.

python
1age = "17" 2age = int(age) 3 4print(age) 5print(type(age)) 6 7number = 10 8number = float(number) 9 10print(number)

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: 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.
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") 242 3>>> 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.

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") + 5 2print(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 = 17 2print("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.
python
1# int("3.5") fails: int() can't parse a decimal point 2value = int(float("3.5")) 3print(value) # 3

3 questions · Type Conversion

1. What's the difference between implicit and explicit type conversion?

2. What does input() always return, before any conversion?

3. What exception is raised when a conversion fails, like int("abc")?

Quiz complete!
Chapter 1

10. Operators

Arithmetic, comparison, and logical operators.

python
1a = 10 2b = 3 3 4print(a + b) # addition 5print(a - b) # subtraction 6print(a * b) # multiplication 7print(a / b) # true division 8print(a // b) # floor division 9print(a % b) # modulus (remainder) 10print(a ** b) # exponent

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 operatorsand, 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: 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.
Wrap comparisons in parentheses when combining them with and/or — it makes the intent obvious at a glance.
python — interactive shell
1>>> 10 / 3 23.3333333333333335 3>>> 10 % 3 41 5>>> (10 > 3) and (3 > 0) 6True

10 % 3 is 1 because 3 fits into 10 three times with 1 left over — that leftover is the modulus.

Exercise 1 — Basic Arithmetic:
Calculate the total price of 3 items at $12.50 each and print it.

💡 Multiply the quantity by the price with *.
python
1total = 3 * 12.50 2print(total) # 37.5

Exercise 2 — Comparison Check:
Given a = 20 and b = 17, print whether a is greater than b.

💡 A comparison expression evaluates directly to True or False — you can print it just like any other value.
python
1a = 20 2b = 17 3print(a > b) # True

Exercise 3 — Combine Logic:
Given number = 42, check whether it's between 1 and 100 (inclusive) using and.

💡 You need two comparisons joined by and: one checking the lower bound, one checking the upper bound.
python
1number = 42 2print((number >= 1) and (number <= 100)) # True

3 questions · Operators

1. What is the difference between / and //?

2. What does the modulus operator % compute?

3. What does and require in order to return True?

Quiz complete!
Chapter 2

11. If Statements

Running code only when a condition is true.

python
1age = 18 2 3if age >= 18: 4 print("Adult")

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: 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.
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 = 15 2>>> 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.

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 = 35 2 3if temperature > 30: 4 print("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.
python
1password = "cynpho123" 2 3if password == "cynpho123": 4 print("Access granted")

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 = 55 2 3if cart_total >= 50: 4 print("You get free shipping!")

3 questions · If Statements

1. What data type must the condition in an if statement evaluate to?

2. What is the difference between = and == in Python?

3. What happens if you forget the colon after an if condition?

Quiz complete!
Chapter 2

12. If…Else

Choosing between two code paths.

python
1age = 15 2 3if age >= 18: 4 print("Adult") 5else: 6 print("Minor")

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: 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.
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 = 20 2>>> 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.

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 = 7 2 3if number % 2 == 0: 4 print("Even") 5else: 6 print("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 = 35 2 3if marks >= 40: 4 print("Pass") 5else: 6 print("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 = 10 2 3if age < 12: 4 print("Child ticket") 5else: 6 print("Adult ticket")

3 questions · If…Else

1. Can both the if block and the else block run in the same execution?

2. When does the else block execute?

3. Why is if-else important for real-world applications?

Quiz complete!
Chapter 2

13. Elif Ladder

Handling multiple conditions in sequence.

python
1marks = 82 2 3if marks >= 90: 4 print("Grade A") 5elif marks >= 80: 6 print("Grade B") 7elif marks >= 70: 8 print("Grade C") 9else: 10 print("Fail")

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: 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.
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 = 95 2>>> 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.

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 ==.
python
1light = "yellow" 2 3if light == "green": 4 print("Go") 5elif light == "yellow": 6 print("Slow down") 7elif light == "red": 8 print("Stop")

Exercise 2 — Discount Tiers:
Given amount = 120, print "20% off" if the amount is 100 or more, "10% off" if it's 50 or more, otherwise "No discount".

💡 Check the biggest threshold first — once a branch matches, the rest are skipped automatically.
python
1amount = 120 2 3if amount >= 100: 4 print("20% off") 5elif amount >= 50: 6 print("10% off") 7else: 8 print("No discount")

Exercise 3 — BMI Category:
Given bmi = 27.5, print "Underweight" (<18.5), "Normal" (<25), "Overweight" (<30), or "Obese" (>=30).

💡 Four outcomes need three comparisons — an if, two elifs, and a final else.
python
1bmi = 27.5 2 3if bmi < 18.5: 4 print("Underweight") 5elif bmi < 25: 6 print("Normal") 7elif bmi < 30: 8 print("Overweight") 9else: 10 print("Obese")

3 questions · Elif Ladder

1. Does Python check every elif block, or stop at the first True one?

2. How many elif blocks can a single if chain have?

3. Why does condition ORDER matter in an if/elif chain?

Quiz complete!
Chapter 2

14. Nested If

Conditions inside conditions.

python
1age = 20 2has_id = True 3 4if age >= 18: 5 if has_id: 6 print("Access Granted")

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: 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.
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 = 16 2>>> has_id = True 3>>> 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.

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.
python
1password_ok = True 2otp_ok = False 3 4if password_ok: 5 if otp_ok: 6 print("Login successful")

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 = 15 2has_parent = False 3 4if age >= 18: 5 print("Can watch") 6elif age >= 13: 7 if has_parent: 8 print("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.
python
1income = 45000 2credit_score = 720 3 4if income >= 30000: 5 if credit_score >= 700: 6 print("Loan approved")

3 questions · Nested If

1. What is a nested if statement?

2. When is nesting genuinely necessary instead of just using and?

3. How can excessive nesting be avoided in real code?

Quiz complete!
Chapter 2

15. For Loops

Iterating over sequences.

python
1for i in range(1, 6): 2 print(i)

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: 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.
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 in range(3): 2... print(i * i) 3... 40 51 64

range(3) produces 0, 1, 2 — for each one, the loop prints its square, giving 0, 1, and 4.

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 in range(5, 0, -1): 2 print(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 = 0 3 4for n in numbers: 5 total = total + n 6 7print(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 in range(1, 11): 2 if n % 2 == 0: 3 print(n)

3 questions · For Loops

1. What values does range(1, 6) actually produce?

2. What is an "iterable" in Python?

3. When would you prefer a for loop over a while loop?

Quiz complete!
Chapter 2

16. While Loops

Repeating while a condition holds.

python
1count = 1 2 3while count <= 5: 4 print(count) 5 count += 1

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: 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.
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.
python — interactive shell
1>>> count = 1 2>>> while count <= 3: 3... print(count) 4... count += 1 5... 61 72 83

Each pass prints count and then increments it — once count reaches 4, the condition count <= 3 becomes False and the loop stops.

Exercise 1 — Print 1 to 10:
Use a while loop to print the numbers 1 through 10.

💡 Start a counter at 1, print it, then increment it inside the loop until the condition fails.
python
1count = 1 2 3while count <= 10: 4 print(count) 5 count += 1

Exercise 2 — Countdown Launch:
Print a countdown from 5 to 1, then print "Liftoff!" using a while loop.

💡 Decrement the counter instead of incrementing it, and print the final message after the loop ends.
python
1count = 5 2 3while count >= 1: 4 print(count) 5 count -= 1 6 7print("Liftoff!")

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 = 0 2n = 1 3 4while total < 20: 5 total += n 6 n += 1 7 8print(total)

3 questions · While Loops

1. When is the while loop's condition checked — before or after each pass?

2. What would happen if count += 1 were removed from this loop?

3. When would you choose a while loop over a for loop?

Quiz complete!
Chapter 2

17. Break, Continue, Pass

Controlling loop execution.

python
1for i in range(1, 11): 2 3 if i == 5: 4 continue 5 6 if i == 9: 7 break 8 9 print(i)

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: 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.
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... break 6... 7Found Bob!

As soon as "Bob" is found, break exits the loop immediately — the remaining name, "Cynthia", is never even checked.

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 in range(1, 11): 2 if number % 3 == 0: 3 continue 4 print(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] 2 3for number in numbers: 4 if number < 0: 5 break 6 print(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
1def coming_soon(): 2 pass

3 questions · Break, Continue, Pass

1. What does break do inside a loop?

2. What does continue do inside a loop?

3. What is the pass statement used for?

Quiz complete!
Chapter 3

18. Lists

Ordered, mutable sequences of items.

python
1fruits = ["Apple", "Banana", "Orange"] 2 3print(fruits) 4print(fruits[0]) 5 6fruits.append("Mango") 7 8print(fruits)

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: 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.
Lists are the most commonly used collection type in Python — comfort with them pays off across almost every project you write.
python — interactive shell
1>>> fruits = ["Apple", "Banana", "Orange"] 2>>> len(fruits) 33 4>>> fruits[-1] 5'Orange'

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.

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.
python
1numbers = [10, 20, 30] 2numbers.append(40) 3 4print(numbers[-1])

Exercise 3 — Remove an Item:
Given tasks = ["Email", "Meeting", "Report"], remove "Meeting" from the list and print the result.

💡 Lists have a .remove() method that deletes the first matching value.
python
1tasks = ["Email", "Meeting", "Report"] 2tasks.remove("Meeting") 3 4print(tasks)

3 questions · Lists

1. Are Python lists mutable or immutable?

2. Can a single list store values of different data types?

3. What does list.append() do?

Quiz complete!
Chapter 3

19. Tuples

Ordered, immutable sequences.

python
1colors = ("Red", "Green", "Blue") 2 3print(colors) 4print(colors[1]) 5print(len(colors))

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: 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.
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] 34 4>>> point[0] = 10 5Traceback (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.

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.

💡 Index 0 is the width, index 1 is the height.
python
1dimensions = (1920, 1080) 2 3print(dimensions[0]) 4print(dimensions[1])

Exercise 3 — Single-Item Tuple:
Create a tuple containing only the number 7, then print its type using type().

💡 A single-item tuple needs a trailing comma — (7) alone is just the number 7 in parentheses, not a tuple.
python
1single = (7,) 2print(type(single))

3 questions · Tuples

1. What is the key difference between a list and a tuple?

2. Why are tuples immutable by design?

3. When would you deliberately choose a tuple over a list?

Quiz complete!
Chapter 3

20. Sets

Unordered collections of unique items.

python
1numbers = {1, 2, 3, 4, 5} 2 3numbers.add(6) 4numbers.add(3) 5 6numbers.remove(2) 7 8print(numbers)

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: 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.
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.

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.

💡 Use the in operator: "value" in some_set.
python
1allowed = {"admin", "editor", "viewer"} 2 3print("guest" in allowed)

Exercise 3 — Deduplicate a List:
Given nums = [1, 2, 2, 3, 3, 3], convert it to a set to remove the duplicates, then print it.

💡 Pass the list directly into set().
python
1nums = [1, 2, 2, 3, 3, 3] 2unique = set(nums) 3 4print(unique)

3 questions · Sets

1. What makes a set different from a list?

2. What happens if you call .remove() on a value that isn't in the set?

3. How would you safely remove a value that might not be present in a set?

Quiz complete!
Chapter 3

21. Dictionaries

Key-value data stores.

python
1student = { 2 "name": "Jit", 3 "age": 17, 4 "country": "Bangladesh" 5} 6 7print(student["name"]) 8 9student["age"] = 18 10 11print(student)

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: 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.
student.get("email", "Not Provided") safely returns a fallback value instead of crashing when a key is missing.
python — interactive shell
1>>> student = {"name": "Jit", "age": 17} 2>>> student["email"] 3Traceback (most recent call last): 4KeyError: 'email' 5>>> student.get("email", "Not Provided") 6'Not Provided'

Accessing a missing key directly with [] raises a KeyError.get() avoids the crash by returning a default value instead.

Exercise 1 — Build a Dictionary:
Create a dictionary called car with keys "brand" set to "Toyota" and "year" set to 2022, then print it.

💡 Curly braces, with each key and value separated by a colon.
python
1car = {"brand": "Toyota", "year": 2022} 2print(car)

Exercise 2 — Update a Value:
Given car = {"brand": "Toyota", "year": 2022}, update "year" to 2024 and print the dictionary.

💡 Assign directly to the key, like car["year"] = 2024.
python
1car = {"brand": "Toyota", "year": 2022} 2 3car["year"] = 2024 4 5print(car)

Exercise 3 — Safe Lookup:
Given car = {"brand": "Toyota"}, safely print the "color" key using .get() with a fallback of "Unknown" if it's missing.

💡 .get(key, default) never raises a KeyError.
python
1car = {"brand": "Toyota"} 2 3print(car.get("color", "Unknown"))

3 questions · Dictionaries

1. What exception is raised by accessing a missing dictionary key directly?

2. What method lets you safely access a key that might not exist?

3. Since which Python version do dictionaries guarantee insertion order?

Quiz complete!
Chapter 4

22. Functions

Deep dive — default arguments, *args, and **kwargs.

python
1def greet(name, greeting="Hello"): 2 return greeting + ", " + name + "!" 3 4print(greet("Jit")) 5print(greet("Jit", "Welcome"))

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: 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.
*args collects extra positional arguments into a tuple; **kwargs collects extra keyword arguments into a dictionary.
python — interactive shell
1>>> def total(*args): 2... return sum(args) 3... 4>>> total(1, 2, 3) 56 6>>> total(10, 20, 30, 40) 7100

*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.

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
1def greet(name, punctuation="!"): 2 return name + punctuation 3 4print(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().
python
1def total(*args): 2 return sum(args) 3 4print(total(1, 2)) 5print(total(1, 2, 3, 4, 5))

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
1def show_config(**kwargs): 2 for key, value in kwargs.items(): 3 print(key + ":", value) 4 5show_config(mode="dark", retries=3)

3 questions · Functions

1. What does giving a parameter a default value, like def greet(name, greeting="Hello"), actually do?

2. What does *args collect inside a function?

3. What does **kwargs collect inside a function?

Quiz complete!
Chapter 4

23. Parameters & Args

Positional and keyword arguments, and the difference between a parameter and an argument.

python
1def greet(name, age): 2 print("Name:", name) 3 print("Age:", age) 4 5greet("Jit", 17)

def greet(name, age): defines two parameters — named placeholders that only exist inside the function's own definition.

greet("Jit", 17) passes two arguments positionally: "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: 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.
greet(age=17, name="Jit") works identically to positional order, because keyword arguments are matched by name, not position.
python — interactive shell
1>>> def greet(name, age): 2... print("Name:", name) 3... print("Age:", age) 4... 5>>> greet(age=17, name="Jit") 6Name: Jit 7Age: 17

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.

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.
python
1def describe(item, price): 2 print("Item:", item) 3 print("Price:", price) 4 5describe("Book", 12)

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.
python
1greet("Jit", 17) 2# or, explicitly: 3greet(name="Jit", age=17)

3 questions · Parameters & Args

1. What is the difference between a parameter and an argument?

2. For def greet(name, age):, what happens if you call greet(17, "Jit") instead of greet("Jit", 17)?

3. How do keyword arguments help avoid ordering mistakes?

Quiz complete!
Chapter 4

24. Lambda Functions

Anonymous single-expression functions.

python
1square = lambda x: x * x 2 3print(square(5)) 4 5numbers = [1, 2, 3, 4, 5] 6squared = list(map(lambda x: x * x, numbers)) 7 8print(squared)

lambda creates a small, anonymous function in a single expression — no def, no name, and no return keyword needed.

square = lambda x: x * x is equivalent to writing def square(x): return x * x — whatever comes after the colon is automatically returned.

map(lambda x: x * x, numbers) applies that lambda to every item in numbers, and list() collects the results back into a list.

How to use: Write lambda parameters: expression — the expression's result is returned automatically, with no separate return statement.
Where is used: Short, throwaway functions passed directly into another function, especially as the key= argument to sorted(), map(), or filter().
Common in: Sorting a list of dictionaries by one field, e.g. sorted(people, key=lambda p: p["age"]).
Reach for lambda only for genuinely simple, one-line logic — anything longer or reused elsewhere reads better as a normal def function.
python — interactive shell
1>>> people = [{"name": "Sam", "age": 30}, {"name": "Ada", "age": 25}] 2>>> sorted(people, key=lambda p: p["age"]) 3[{'name': 'Ada', 'age': 25}, {'name': 'Sam', 'age': 30}]

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.

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 * 2 2print(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.
python
1nums = [1, 2, 3, 4, 5, 6] 2evens = list(filter(lambda x: x % 2 == 0, nums)) 3 4print(evens)

Exercise 3 — Sort by Key:
Given words = ["banana", "kiwi", "apple", "fig"], sort them shortest-first using sorted() with a lambda key.

💡 key=lambda w: len(w) sorts by each word's length.
python
1words = ["banana", "kiwi", "apple", "fig"] 2 3print(sorted(words, key=lambda w: len(w)))

3 questions · Lambda Functions

1. What does lambda x: x * x create?

2. Which keyword is required inside a lambda's body to send back a value?

3. Lambdas are most commonly used for...

Quiz complete!
Chapter 4

25. Modules

Organising code across multiple files.

python
1# math_utils.py 2def add(a, b): 3 return a + b 4 5# main.py 6import math_utils 7 8print(math_utils.add(2, 3))

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: 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.
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 add 2>>> 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).

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(...).
python
1import string_utils 2print(string_utils.shout("hello"))

Exercise 2 — Import a Specific Name:
Rewrite Exercise 1 to import only the shout function directly, so you can call it without the module prefix.

💡 from string_utils import shout
python
1from string_utils import shout 2print(shout("hello"))

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 m 2print(m.sqrt(81))

3 questions · Modules

1. What is a Python module?

2. What does from math_utils import add let you do differently from import math_utils?

3. What does import math as m do?

Quiz complete!
Chapter 4

26. Import Statement

Loading modules and packages.

python
1import math 2from math import pi 3from math import sqrt as square_root 4 5print(math.pi) 6print(pi) 7print(square_root(16))

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: 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.
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.0 4>>> pi 53.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.

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 random 2print(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 randint 2print(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 dt 2print(dt.now())

3 questions · Import Statement

1. What's the main risk of using from module import *?

2. What does from math import sqrt as square_root do?

3. Where do third-party Python packages (not part of the Standard Library) typically come from?

Quiz complete!
Chapter 5

27. Classes & Objects

The blueprint (class) and instances (objects).

python
1class Player: 2 3 def __init__(self, name, health): 4 self.name = name 5 self.health = health 6 7 def show_info(self): 8 print("Name:", self.name) 9 print("Health:", self.health) 10 11 12player = Player("Jit", 100) 13 14player.show_info()

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: 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.
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.
python — interactive shell
1>>> p1 = Player("Jit", 100) 2>>> p2 = Player("Ada", 80) 3>>> p1.name 4'Jit' 5>>> p2.name 6'Ada'

p1 and p2 are two separate objects built from the same Player blueprint — each stores its own name and health completely independently of the other.

Exercise 1 — Build a Class:
Define a class Book whose constructor takes title and author, stores them on self, then create a Book and print its title.

💡 def __init__(self, title, author): then self.title = title.
python
1class Book: 2 def __init__(self, title, author): 3 self.title = title 4 self.author = author 5 6b = Book("Dune", "Frank Herbert") 7print(b.title)

Exercise 2 — Add a Method:
Add a method describe() to the Book class from Exercise 1 that prints the title and author together, then call it.

💡 Methods take self as their first parameter, same as __init__.
python
1class Book: 2 def __init__(self, title, author): 3 self.title = title 4 self.author = author 5 6 def describe(self): 7 print(self.title, "by", self.author) 8 9b = Book("Dune", "Frank Herbert") 10b.describe()

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.
python
1b1 = Book("Dune", "Frank Herbert") 2b2 = Book("1984", "George Orwell") 3 4print(b1.title) 5print(b2.title)

3 questions · Classes & Objects

1. What is the purpose of the self parameter in a class's methods?

2. What is the difference between a class and an object?

3. When is __init__ automatically called?

Quiz complete!
Chapter 5

28. Constructors

__init__ and object initialisation.

python
1class Car: 2 3 def __init__(self, brand, model): 4 self.brand = brand 5 self.model = model 6 7 def show(self): 8 print(self.brand, self.model) 9 10 11car = Car("Toyota", "Supra") 12 13car.show()

__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: 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.
Constructor parameters can have default values too, just like any other function — making some arguments optional at creation time.
python — interactive shell
1>>> class Car: 2... def __init__(self, brand, model="Unknown"): 3... self.brand = brand 4... self.model = model 5... 6>>> c = Car("Honda") 7>>> c.model 8'Unknown'

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.

Exercise 1 — Basic Constructor:
Define a class Laptop whose constructor takes brand and stores it on self, then create one and print its brand.

💡 def __init__(self, brand): self.brand = brand
python
1class Laptop: 2 def __init__(self, brand): 3 self.brand = brand 4 5laptop = Laptop("Dell") 6print(laptop.brand)

Exercise 2 — Constructor with Two Values:
Extend Laptop to also accept and store ram_gb, then create one and print both values.

💡 Add a second parameter to __init__ and store it the same way.
python
1class Laptop: 2 def __init__(self, brand, ram_gb): 3 self.brand = brand 4 self.ram_gb = ram_gb 5 6laptop = Laptop("Dell", 16) 7print(laptop.brand) 8print(laptop.ram_gb)

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.

💡 def __init__(self, brand, ram_gb=8):
python
1class Laptop: 2 def __init__(self, brand, ram_gb=8): 3 self.brand = brand 4 self.ram_gb = ram_gb 5 6laptop = Laptop("Dell") 7print(laptop.ram_gb)

3 questions · Constructors

1. What is the exact name Python looks for to treat a method as a constructor?

2. What happens if __init__ is misspelled, such as with one underscore?

3. Can __init__ return a value other than None?

Quiz complete!
Chapter 5

29. Inheritance

Extending existing classes.

python
1class Animal: 2 3 def speak(self): 4 print("Animal Sound") 5 6 7class Dog(Animal): 8 9 def bark(self): 10 print("Woof!") 11 12 13dog = Dog() 14 15dog.speak() 16dog.bark()

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: 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.
Python supports multiple inheritance too — class Dog(Animal, Pet): — unlike languages such as Java or C#.
python — interactive shell
1>>> class Pet: 2... def is_friendly(self): 3... return True 4... 5>>> class Dog(Animal, Pet): 6... def bark(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.

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.
python
1class Vehicle: 2 def move(self): 3 print("Moving...") 4 5class Car(Vehicle): 6 def honk(self): 7 print("Beep!") 8 9car = Car() 10car.move() 11car.honk()

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.
python
1class Bike(Vehicle): 2 def ring_bell(self): 3 print("Ring ring!") 4 5bike = Bike() 6bike.move() 7bike.ring_bell()

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.
python
1class Animal: 2 def speak(self): 3 print("Animal Sound") 4 5class Cat(Animal): 6 def meow(self): 7 print("Meow!") 8 9cat = Cat() 10cat.speak() 11cat.meow()

3 questions · Inheritance

1. What does class Dog(Animal): mean?

2. Why can dog.speak() be called even though speak() is not defined inside Dog?

3. Does Python support inheriting from more than one class at once?

Quiz complete!
Chapter 5

30. Polymorphism

One interface, many implementations.

python
1class Bird: 2 3 def sound(self): 4 print("Chirp") 5 6 7class Cat: 8 9 def sound(self): 10 print("Meow") 11 12 13animals = [Bird(), Cat()] 14 15for animal in animals: 16 animal.sound()

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: 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().
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... def area(self): 3... return 3.14 4... 5>>> class Square: 6... def area(self): 7... return 4 8... 9>>> shapes = [Circle(), Square()] 10>>> for shape in shapes: 11... print(shape.area()) 12... 133.14 144

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.

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: 2 3 def travel(self): 4 print("Driving on the road") 5 6 7class Boat: 8 9 def travel(self): 10 print("Sailing on water") 11 12 13vehicles = [Car(), Boat()] 14 15for 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: 2 3 def sound(self): 4 print("Blub") 5 6 7animals = [Bird(), Cat(), Fish()] 8 9for 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.
python
1class Rectangle: 2 3 def __init__(self, width, height): 4 self.width = width 5 self.height = height 6 7 def area(self): 8 return self.width * self.height 9 10 11class Circle: 12 13 def __init__(self, radius): 14 self.radius = radius 15 16 def area(self): 17 return 3.14 * self.radius * self.radius 18 19 20shapes = [Rectangle(4, 5), Circle(3)] 21 22for shape in shapes: 23 print(shape.area())

3 questions · Polymorphism

1. What is "duck typing", and how does it relate to polymorphism in Python?

2. Do Bird and Cat need a common base class for this code to work?

3. What happens if one object in the list is missing the .sound() method entirely?

Quiz complete!
Chapter 5

31. Encapsulation

Hiding internal state with access control.

python
1class BankAccount: 2 3 def __init__(self): 4 self.__balance = 0 5 6 def deposit(self, amount): 7 self.__balance += amount 8 9 def get_balance(self): 10 return self.__balance 11 12 13account = BankAccount() 14 15account.deposit(500) 16 17print(account.get_balance())

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: 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.
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.
python — interactive shell
1>>> account = BankAccount() 2>>> account.deposit(500) 3>>> account.get_balance() 4500 5>>> account._BankAccount__balance 6500

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.

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().
python
1class Wallet: 2 3 def __init__(self): 4 self.__cash = 0 5 6 def add_cash(self, amount): 7 self.__cash += amount 8 9 def get_cash(self): 10 return self.__cash 11 12 13wallet = Wallet() 14 15wallet.add_cash(200) 16 17print(wallet.get_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.
python
1class BankAccount: 2 3 def __init__(self): 4 self.__balance = 0 5 6 def deposit(self, amount): 7 self.__balance += amount 8 9 def withdraw(self, amount): 10 if amount > self.__balance: 11 print("Insufficient funds") 12 else: 13 self.__balance -= amount 14 15 def get_balance(self): 16 return self.__balance 17 18 19account = BankAccount() 20 21account.deposit(100) 22account.withdraw(200) 23 24print(account.get_balance())

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."
python
1class Employee: 2 3 def __init__(self, salary): 4 self._salary = salary 5 6 def get_salary(self): 7 return self._salary 8 9 10employee = Employee(50000) 11 12print(employee.get_salary())

3 questions · Encapsulation

1. What does Python's "name mangling" do to an attribute like __balance?

2. Is a double-underscore attribute truly impossible to access from outside the class?

3. Why is get_balance() considered better practice than a public balance attribute?

Quiz complete!
Chapter 6

32. File Handling

Reading from and writing to files.

python
1with open("notes.txt", "w") as file: 2 file.write("Hello, Python!") 3 4with open("notes.txt", "r") as file: 5 content = file.read() 6 7print(content)

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: 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.
"a" (append) mode adds to the end of the file instead of erasing what's already there.
python — interactive shell
1>>> with open("log.txt", "a") as file: 2... file.write("New entry\n") 3... 4>>> with open("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.

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
1with open("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
1with open("greeting.txt", "r") as file: 2 content = file.read() 3 4print(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
1with open("greeting.txt", "a") as file: 2 file.write(" See you soon!")

3 questions · File Handling

1. What does the with statement do when working with files?

2. What's the difference between "w" mode and "a" mode?

3. What does file.read() return?

Quiz complete!
Chapter 6

33. Exceptions

Catching and handling runtime errors.

python
1try: 2 number = int("abc") 3except ValueError: 4 print("That's not a valid number!") 5 6print("Program continues...")

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: 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.
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 / 0 3... 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.

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: 4 print("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 / 0 3except ZeroDivisionError: 4 print("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 / 0 3except ZeroDivisionError: 4 print("Cannot divide by zero!") 5finally: 6 print("Attempt finished")

3 questions · Exceptions

1. What happens to code inside a try block if no error occurs?

2. What is the finally block used for?

3. Why is it better to catch a specific exception like ValueError rather than a bare except:?

Quiz complete!
Chapter 6

34. Regular Expressions

Pattern matching with the re module.

python
1import re 2 3text = "My phone number is 123-456-7890" 4match = re.search(r"\d{3}-\d{3}-\d{4}", text) 5 6print(match.group())

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: 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.
Always check that a re.search() result isn't None before calling .group() on it — no match returns None.
python — interactive shell
1>>> import re 2>>> 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.

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 re 2 3text = "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 re 2 3text = "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 re 2 3text = "I love cats" 4result = re.sub(r"cats", "dogs", text) 5print(result)

3 questions · Regular Expressions

1. What does \d{3} match in a regular expression?

2. What does re.findall() return?

3. What should you check before calling .group() on the result of re.search()?

Quiz complete!