CS50 Python Complete Book
CS50 Python Complete Book
Preface
"The most important thing I can do as a teacher is make you believe you can do this. Because you can."
To the Reader
This book was written for one person: someone who opened a Python file for the first time and thought, "I'm not sure I'm
smart enough for this." You are. Every concept in this book — no matter how abstract it sounds — can be understood
deeply, completely, and permanently. That is this book's only promise.
Inspired by Harvard's legendary CS50 course and the teaching philosophy of Professor David J. Malan, this book does not
just show you what Python does. It shows you why every keyword exists, why every rule is the way it is, and what is actually
happening inside your computer when you press Enter. That kind of understanding does not fade. You can return to this
book ten years from now and pick up exactly where you left off.
• Intuition First — A real-life analogy to anchor the concept before any code appears.
• Deep Explanation — What is happening in memory, in the CPU, in Python's brain.
• Syntax Breakdown — Every keyword, every symbol, explained word by word.
• Worked Examples — From the simplest possible case to sophisticated real-world programs.
• Common Mistakes — The errors every beginner (and many experts) make, and exactly why they happen.
• Practice Problems — Three levels: Easy, Medium, and Challenging, each with full solutions.
• Chapter Summary — A crisp revision card you can use for quick review.
A Note on Style
This book speaks in the second person — you. It is written as if a professor is sitting across from you, explaining things on a
whiteboard, occasionally cracking a joke, and always making sure you understood the last thing before moving to the next.
Code is formatted in a monospace font and presented with explanations that walk through it line by line — sometimes
character by character. ASCII diagrams show how data moves. Callout boxes highlight important warnings and tips.
Prerequisites
Absolutely none. This book assumes you have never written a single line of code. If you have, you will move quickly through
the early chapters and find the later ones challenging and rewarding. Either way, there is something here for you.
Now, let's begin. Chapter 1 is waiting. And it's not as scary as you think.
CS50 Python — A Complete Course Companion CS50 Python
CHAPTER
Table of Contents
Preface —
Chapter 2 — Conditionals
The if Statement
if / else
elif Chains
Comparison & Logical Operators
Nested Conditionals
Pythonic Conditions
The match Statement
Chapter 3 — Loops
The while Loop
The for Loop
range()
break, continue, pass
Nested Loops
Loop Patterns
Chapter 4 — Functions
Defining Functions
Parameters & Arguments
Return Values
Scope
Default Arguments
Variable Arguments
Lambda Functions
—3—
CS50 Python — A Complete Course Companion CS50 Python
Recursion
Chapter 5 — Exceptions
What Are Exceptions?
try / except
else & finally
Raising Exceptions
Custom Exception Classes
—4—
CS50 Python — A Complete Course Companion CS50 Python
CHAPTER 1
Analo Imagine a giant warehouse full of labeled boxes. Each box can hold one
gy thing — a number, a name, a list. You, the programmer, decide what goes
in each box and what label it gets. That warehouse is your computer's RAM.
Those labeled boxes are variables.
A variable is a named reference to a location in computer memory that stores a value. The
name stays the same; the value can change at any time. That combination — a stable name
pointing to a changeable value — is one of the most powerful ideas in all of programming.
Formal Definition
In Python, a variable is created the moment you assign a value to it using the equals sign (=).
There is no separate 'declaration' step as in some other languages. You simply write:
name = "Alice"
age = 21
gpa = 3.85
1. Python creates a value object somewhere in memory (e.g., the string "Alice" at address
0x7f3a).
2. Python creates a name binding — an entry in a lookup table that maps the label "name"
to that address.
—5—
CS50 Python — A Complete Course Companion CS50 Python
3. Every time you use the variable name in your code, Python follows the pointer to retrieve
the value.
This model is called reference semantics. Variables in Python are not boxes containing
values; they are labels pointing to values. This distinction matters enormously when you reach
functions and object-oriented programming.
• Use snake_case for variable names: words separated by underscores, all lowercase.
Example: student_name, total_price_usd.
• Make names descriptive. n is cryptic; number_of_students is self-documenting.
• Avoid single-letter names except for short loops or mathematical variables.
• Constants (values that never change) use ALL_CAPS: MAX_RETRIES = 3.
—6—
CS50 Python — A Complete Course Companion CS50 Python
Analo Not all boxes are created equal. A box for eggs has compartments. A box
gy for shoes is rectangular and firm. A box for liquids is sealed. In Python,
every piece of data has a TYPE — and that type determines what you can
do with it and how it behaves.
Python has five data types you will use constantly. Each is described in detail below.
first_name = "Harry"
last_name = 'Potter' # single quotes also work
full_name = "Harry" + " " + "Potter" # concatenation with +
greeting = f"Hello, {first_name}!" # f-string (modern, preferred)
The f-string (formatted string literal) is Python's most elegant way to embed variables inside
text. The f prefix tells Python: "look inside this string for curly braces, and substitute any
variable names you find."
f"Hello, {first_name}!"
| | |
| | Literal text: '!'
| Python looks up 'first_name' and inserts its value
—7—
CS50 Python — A Complete Course Companion CS50 Python
name = "alice"
print([Link]()) # "ALICE" — all uppercase
print([Link]()) # "Alice" — first letter capitalized
print([Link]()) # "alice" — all lowercase
print(len(name)) # 5 — number of characters
print(name[0]) # "a" — first character (index 0)
print(name[-1]) # "e" — last character (negative index)
print([Link]()) # removes whitespace from both ends
print([Link]('a','@')) # "@lice" — replace substrings
print("alice" in name) # True — substring check
score = 100
temperature = -5
students = 0
The modulo operator (%) deserves special attention. It gives you the remainder of a division.
If you have 100 candies and 3 children, each child gets 33 candies (100 // 3 = 33), and 1 candy
remains (100 % 3 = 1). Modulo is used everywhere: checking if a number is even (n % 2 == 0),
cycling through a list, implementing clocks and calendars.
—8—
CS50 Python — A Complete Course Companion CS50 Python
price = 9.99
pi = 3.14159
is_raining = True
has_passed_exam = False
—9—
CS50 Python — A Complete Course Companion CS50 Python
Com = and == are completely different. A single = means assignment: "store this
mon value in this variable." Double == means comparison: "are these two values
Mista equal?" Writing if x = 5 is a SyntaxError. You want if x == 5.
ke
result = None
print(result) # None
print(type(result)) # <class 'NoneType'>
name = "Alice"
age = 21
height = 5.7
active = True
— 10 —
CS50 Python — A Complete Course Companion CS50 Python
Com int() does not round — it truncates. int(9.9) gives 9, not 10. int(3.1) gives 3.
mon If you want rounding, use round() first: int(round(9.9)) gives 10.
Mista
ke
— 11 —
CS50 Python — A Complete Course Companion CS50 Python
Com The most common beginner error with input(): forgetting it always returns a
mon string. age = input('Age: ') gives you the string '25', not the number 25.
Mista Writing age > 18 then causes a TypeError because Python cannot compare
— 12 —
CS50 Python — A Complete Course Companion CS50 Python
Practice Problems
Easy
1. Ask the user for their name and age. Print: "Hello [name], you are [age] years old."
2. Create two integers a = 17 and b = 5. Print their sum, difference, product, integer
quotient, and remainder.
3. Convert the string "3.14" to a float and multiply by 2. Print the result.
Medium
1. Ask the user for a temperature in Celsius. Convert to Fahrenheit using F = (C * 9/5) + 32.
Display the result rounded to 1 decimal place.
— 13 —
CS50 Python — A Complete Course Companion CS50 Python
2. Ask the user for their first and last name separately. Combine them and print the full
name with proper capitalization regardless of how the user typed it.
3. Ask the user for a number of seconds. Convert and display it as hours, minutes, and
seconds (e.g., 3661 seconds = 1 hour, 1 minute, 1 second).
Challenging
1. Ask the user to enter two numbers. Without any if statement, compute and print: their
sum, their average as a float, whether the first is larger (a boolean), and the remainder of
dividing the first by the second.
2. Write a program that asks for a 10-digit phone number as a string and reformats it to
(XXX) XXX-XXXX format using string slicing.
3. Ask the user for a sentence. Print: the sentence in uppercase, the number of characters
(including spaces), the number of words, and whether the sentence ends with a question
mark.
Solutions
# Easy 1
name = input("What is your name? ")
age = input("How old are you? ")
print(f"Hello {name}, you are {age} years old.")
# Easy 2
a, b = 17, 5
print(f"Sum: {a+b}, Diff: {a-b}, Product: {a*b}")
print(f"Quotient: {a//b}, Remainder: {a%b}")
# Easy 3
result = float("3.14") * 2
print(result) # 6.28
# Medium 1
celsius = float(input("Temperature in Celsius: "))
fahrenheit = (celsius * 9/5) + 32
print(f"{celsius}C = {round(fahrenheit, 1)}F")
# Medium 3
total_secs = int(input("Enter seconds: "))
hours = total_secs // 3600
— 14 —
CS50 Python — A Complete Course Companion CS50 Python
# Hard 2
phone = input("Enter 10-digit phone number: ")
formatted = f"({phone[:3]}) {phone[3:6]}-{phone[6:]}"
print(formatted)
# Hard 3
sentence = input("Enter a sentence: ")
print([Link]())
print(f"Characters: {len(sentence)}")
print(f"Words: {len([Link]())}")
print(f"Ends with ?: {[Link]('?')}")
Chapter Summary
• Python variables use reference semantics — they point to objects, not contain them.
• Five core types: str (text), int (whole numbers), float (decimals), bool (True/False),
NoneType (no value).
• input() always returns a string — convert with int() or float() when you need
numbers.
• int() truncates (cuts off) the decimal part; it does not round.
• Compound operators (+=, -=, *=) provide shorthand for common reassignment
patterns.
— 15 —
CS50 Python — A Complete Course Companion CS50 Python
CHAPTER 2
Conditionals
"A program without conditionals is just a recipe — it does the same thing every
time. Conditionals are what give programs the ability to think."
Analo Imagine a theme park entrance. The guard checks your height against a
gy sign: 'You must be at least 120 cm to ride.' If you are tall enough — you go
in. If not — you don't. That moment of checking and choosing a path is
exactly what a conditional does in code.
Without conditionals, every run of your program would produce identical output regardless of
the input. Conditionals let your program decide — and that decision-making capability is the
foundation of everything useful that computers do.
age = 18
Anatomy of an if Statement
if age >= 18 :
| | | | |
| | | | Colon: MANDATORY. Signals block start.
| | | Value to compare against
| | Comparison operator (greater than or equal to)
| Variable being checked
— 16 —
CS50 Python — A Complete Course Companion CS50 Python
Python uses indentation (whitespace at the start of a line) to define blocks of code. This is not
a style choice — it is mandatory syntax. Every line inside an if block must be indented by the
same amount (conventionally 4 spaces).
Execution Trace
age = 15
if age >= 18: Step 1: Evaluate 'age >= 18'
print("Vote") Step 2: 15 >= 18 is False
Step 3: Skip the block entirely
print("Done") Step 4: This runs regardless
age = 15
The else block has no condition of its own. It is the catch-all that handles every case the if did
not. Together, if and else form a complete binary fork: one path or the other, always exactly
one.
— 17 —
CS50 Python — A Complete Course Companion CS50 Python
Analo A cinema has different ticket prices: under 5 is free, 5-17 is a child ticket,
gy 18-64 is adult, 65+ is senior. You cannot express four outcomes with just if
and else. You need elif — 'else if' — to chain multiple conditions.
if age < 5:
print("Free entry!")
elif age < 18:
print("Child ticket: $6")
elif age < 65:
print("Adult ticket: $12")
else:
print("Senior ticket: $8")
age = 10
Com Order matters enormously in elif chains. If you put a more general condition
mon before a specific one, the specific one may never be reached. Always put
Mista the MOST SPECIFIC condition first. Example: if score >= 90 must come
ke before if score >= 60, or every score of 90+ would match the second
condition and never reach the first.
— 18 —
CS50 Python — A Complete Course Companion CS50 Python
== Equal to 5 == 5 True
age = 25
has_id = True
is_student = True
is_senior = False
if is_student or is_senior:
print("Discount applied.")
— 19 —
CS50 Python — A Complete Course Companion CS50 Python
is_raining = False
if not is_raining:
print("Good weather for a walk!")
age = 20
is_vip = False
has_invitation = True
— 20 —
CS50 Python — A Complete Course Companion CS50 Python
match language:
case "python":
print("Great choice for beginners!")
case "javascript":
print("The language of the web!")
case "c" | "c++": # The | means OR between cases
print("You like living dangerously.")
case _: # _ is the wildcard (like else)
print("I don't know that language.")
— 21 —
CS50 Python — A Complete Course Companion CS50 Python
Nested conditionals are appropriate when a second decision only makes sense after the first
one has already been resolved. However, avoid nesting more than 2-3 levels deep — deeply
nested code is difficult to read and debug. Often, deep nesting can be replaced with logical
operators or early returns.
— 22 —
CS50 Python — A Complete Course Companion CS50 Python
Practice Problems
Easy
1. Ask for a number. Print 'positive', 'negative', or 'zero'.
2. Ask for a username and password. If username is 'admin' and password is 'cs50', print
'Access granted.' Otherwise print 'Access denied.'
3. Ask for a temperature in Celsius. Print 'Freezing' (below 0), 'Cold' (0-20), 'Warm' (21-35),
or 'Hot' (above 35).
Medium
1. Ask for two numbers and an operator (+, -, *, /). Perform the calculation. Handle division
by zero gracefully.
— 23 —
CS50 Python — A Complete Course Companion CS50 Python
2. A cinema charges different prices by age group (see Section 2.4). If it is Tuesday, apply a
20% discount to all paying customers. Ask for age and day, print the final price.
3. Ask the user for a year. Determine if it is a leap year. (Leap year rules: divisible by 4,
except centuries unless also divisible by 400.)
Challenging
1. Build a simple login with exactly 3 attempts. If the correct password ('python50') is
entered, print 'Welcome!' and stop. After 3 failures, print 'Account locked.'
2. Ask for three side lengths. Determine if they form a valid triangle. If valid, classify it as
equilateral, isosceles, or scalene.
3. Create a BMI calculator. Ask for weight (kg) and height (m). Compute BMI. Classify as
Underweight (<18.5), Normal (18.5-24.9), Overweight (25-29.9), or Obese (30+). Print the
classification and a one-sentence health note for each category.
Solutions
# Easy 1
n = float(input("Enter a number: "))
if n > 0: print("Positive")
elif n < 0: print("Negative")
else: print("Zero")
# Easy 3
temp = float(input("Temperature: "))
if temp < 0: print("Freezing")
elif temp <= 20: print("Cold")
elif temp <= 35: print("Warm")
else: print("Hot")
# Medium 1
a = float(input("First number: "))
op = input("Operator: ")
b = float(input("Second number: "))
if op == "+": print(a + b)
elif op == "-": print(a - b)
elif op == "*": print(a * b)
elif op == "/":
if b == 0: print("Error: Division by zero")
else: print(a / b)
— 24 —
CS50 Python — A Complete Course Companion CS50 Python
# Hard 2 — Triangle
a = float(input("Side 1: "))
b = float(input("Side 2: "))
c = float(input("Side 3: "))
if a + b > c and a + c > b and b + c > a:
if a == b == c: print("Equilateral")
elif a == b or b == c or a == c: print("Isosceles")
else: print("Scalene")
else:
print("Not a valid triangle.")
Chapter Summary
• else provides the fallback — exactly one branch (if or else) always executes.
• elif chains multiple conditions; Python checks top-to-bottom and stops at the first
True.
• Comparison operators: ==, !=, >, <, >=, <= — always return a boolean.
• Logical operators: and (both must be True), or (at least one), not (reverses).
— 25 —
CS50 Python — A Complete Course Companion CS50 Python
— 26 —
CS50 Python — A Complete Course Companion CS50 Python
CHAPTER 3
Loops
"If you find yourself writing the same lines of code three times, a loop is trying to
be born."
Analo Imagine you're a factory worker stamping 10,000 envelopes. You don't write
gy 'stamp envelope' 10,000 times on your to-do list. You write 'stamp one
envelope' and repeat it until all 10,000 are done. That repetition is exactly
what a loop does.
Loops are one of the most powerful ideas in programming. They let you execute a block of
code repeatedly — either a fixed number of times, or until some condition changes. Without
loops, your code would grow linearly with your problem. With loops, a program of 10 lines can
process a million items.
count = 1
print("Loop finished.")
Execution Trace
count = 1
— 27 —
CS50 Python — A Complete Course Companion CS50 Python
|
Check: 1 <= 5? True -> print 'Count is 1' -> count = 2
Check: 2 <= 5? True -> print 'Count is 2' -> count = 3
Check: 3 <= 5? True -> print 'Count is 3' -> count = 4
Check: 4 <= 5? True -> print 'Count is 4' -> count = 5
Check: 5 <= 5? True -> print 'Count is 5' -> count = 6
Check: 6 <= 5? False -> EXIT LOOP
|
print 'Loop finished.'
Com Infinite loops: if you forget to update the loop variable (count += 1), the
mon condition never becomes False and the loop runs forever — crashing your
Mista program. Always make sure something inside the loop makes progress
# Output:
— 28 —
CS50 Python — A Complete Course Companion CS50 Python
# apple
# banana
# cherry
Word-by-Word Breakdown
The loop variable (fruit) is automatically assigned the next item from the sequence at the
start of each iteration. You do not manage an index or counter — Python does it for you.
# range(stop) — 0 to stop-1
for i in range(5):
print(i) # 0, 1, 2, 3, 4
# Counting down
for i in range(10, 0, -1):
print(i) # 10, 9, 8, 7, ... 1
— 29 —
CS50 Python — A Complete Course Companion CS50 Python
xclusi
ve
word = "Python"
for letter in word:
print(letter)
# Count vowels
vowels = 0
for letter in word:
if [Link]() in "aeiou":
vowels += 1
print(f"Vowels: {vowels}")
# Output:
— 30 —
CS50 Python — A Complete Course Companion CS50 Python
# 0: apple
# 1: banana
# 2: cherry
for n in numbers:
if n % 2 == 0:
print(f"First even: {n}")
break # Stop as soon as we find it
# Output: 1 3 5 7 9
for i in range(5):
— 31 —
CS50 Python — A Complete Course Companion CS50 Python
# Output:
# 1 2 3
# 2 4 6
# 3 6 9
Warning: nested loops can become slow quickly. A loop inside a loop runs O(n²) operations.
Three nested loops run O(n³). For large data, look for alternative approaches.
— 32 —
CS50 Python — A Complete Course Companion CS50 Python
for n in numbers:
total += n
print(f"Sum: {total}") # 150
— 33 —
CS50 Python — A Complete Course Companion CS50 Python
if n % 2 == 0:
[Link](n)
Practice Problems
Easy
1. Use a for loop to print the numbers 1 through 20.
2. Use a while loop to ask the user for a positive number. Keep asking until they comply.
3. Loop through the string 'Mississippi' and count how many times the letter 's' appears.
Medium
1. Print a right-angled triangle of asterisks with n rows (ask user for n). Row 1 has 1 star,
row 2 has 2, etc.
2. Write a program that finds all prime numbers between 2 and 100 using nested loops.
3. Ask the user for 5 exam scores. Calculate and display the average, highest, and lowest
score.
Challenging
— 34 —
CS50 Python — A Complete Course Companion CS50 Python
1. Write a number guessing game: generate a random number 1-100 (use [Link]).
Let the user guess repeatedly. Tell them 'too high' or 'too low' after each guess. Count and
display the number of guesses when they get it right.
2. Create a program that prints a diamond shape of asterisks. Ask the user for the half-width
n. (e.g., n=3 produces a diamond 5 rows tall.)
3. Given a list of words, use loops to build a dictionary counting how many times each
unique word appears. Print the results sorted by count (most common first).
Solutions
# Easy 1
for i in range(1, 21):
print(i)
# Easy 3
count = 0
for char in 'Mississippi':
if char == 's':
count += 1
print(f"s appears {count} times")
# Medium 1 — Triangle
n = int(input("Number of rows: "))
for i in range(1, n + 1):
print('*' * i)
# Medium 2 — Primes
for n in range(2, 101):
is_prime = True
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
is_prime = False
break
if is_prime:
print(n)
— 35 —
CS50 Python — A Complete Course Companion CS50 Python
while True:
guess = int(input("Guess (1-100): "))
guesses += 1
if guess < secret: print("Too low!")
elif guess > secret: print("Too high!")
else:
print(f"Correct in {guesses} guesses!")
break
Chapter Summary
• for loops iterate over any sequence (list, string, range) — Python handles the
counter automatically.
• break exits the entire loop; continue skips to the next iteration; pass does nothing.
• Nested loops run in O(n²) time — use carefully with large data.
• List comprehensions ([x**2 for x in range(5)]) are the Pythonic way to build lists from
loops.
— 36 —
CS50 Python — A Complete Course Companion CS50 Python
CHAPTER 4
Functions
"A function is a promise: give me this input, and I will give you back that output.
Every time."
Analo Imagine a coffee machine. You don't need to know how it heats water,
gy grinds beans, and pressurizes steam. You press a button and get espresso.
A function is that button — it hides complexity behind a simple interface.
Press the button, get the result.
1. Reusability — Write code once, call it a thousand times without rewriting it.
2. Abstraction — Hide implementation details. Other code only needs to know what a
function does, not how it does it.
3. Organization — Break large problems into small, testable, independently-working
pieces.
def greet(name):
"""Print a personalized greeting."""
message = f"Hello, {name}! Welcome to Python."
print(message)
— 37 —
CS50 Python — A Complete Course Companion CS50 Python
| | | |
| | | Colon: block follows
| | Parameter: input the function expects
| Function name (snake_case by convention)
Keyword 'define': tells Python a function is being created
Defining a function does NOT run it. It only teaches Python what to do when the function is
called. The function body runs only when you explicitly call the function with its name followed
by parentheses.
# Multiple parameters
def describe_pet(name, species, age):
print(f"{name} is a {age}-year-old {species}.")
describe_pet("Buddy", "dog", 3)
# Output: Buddy is a 3-year-old dog.
— 38 —
CS50 Python — A Complete Course Companion CS50 Python
def square(n):
return n * n # Sends n*n back to the caller
def is_valid_age(age):
if age < 0:
return False # Exit immediately for negative ages
if age > 150:
return False # Exit immediately for unrealistic ages
return True # Only reached if both checks passed
print(is_valid_age(25)) # True
print(is_valid_age(-1)) # False
print(is_valid_age(200)) # False
— 39 —
CS50 Python — A Complete Course Companion CS50 Python
def my_function():
y = 20 # Local variable — visible only inside my_function
print(x) # 10 — can READ global variables
print(y) # 20
my_function()
print(x) # 10 — still works
print(y) # NameError! y doesn't exist outside the function
increment()
print(count) # 1
Globa Avoid using global variables whenever possible. They make code hard to
l Vari understand and test because any function can change them at any time.
ables Prefer passing values as arguments and returning results — this is the
sually
a Des
ign
Smell
— 40 —
CS50 Python — A Complete Course Companion CS50 Python
A parameter can have a default value. If the caller doesn't provide that argument, the default is
used. This makes functions more flexible without requiring every argument to be specified on
every call.
create_user("Alice", 30)
create_user(age=25, name="Bob", role="admin") # order doesn't matter
ke
print(add_all(1, 2, 3)) # 6
print(add_all(10, 20, 30, 40)) # 100
— 41 —
CS50 Python — A Complete Course Companion CS50 Python
# Regular function
def square(n):
return n * n
# Equivalent lambda
square = lambda n: n * n
print(square(5)) # 25
numbers = [1, 2, 3, 4, 5, 6]
evens = list(filter(lambda n: n % 2 == 0, numbers))
print(evens) # [2, 4, 6]
Analo Russian matryoshka dolls: you open a doll and find a smaller doll inside.
gy You open that one and find an even smaller one. At some point you reach
the smallest doll — the one that doesn't open. That's the base case.
Recursion is a function solving a problem by solving a smaller version of
itself, until it reaches a case small enough to solve directly.
— 42 —
CS50 Python — A Complete Course Companion CS50 Python
print(factorial(5)) # 120
The Every recursive function MUST have a base case — a condition that stops
Base the recursion. Without it, the function calls itself forever until Python hits its
Case recursion limit (default: 1000 calls) and raises a RecursionError.
Is Not
Optio
nal
# Fix:
def add(a, b):
return a + b
— 43 —
CS50 Python — A Complete Course Companion CS50 Python
Practice Problems
Easy
1. Write a function is_even(n) that returns True if n is even, False otherwise.
2. Write a function celsius_to_fahrenheit(c) that converts Celsius to Fahrenheit.
3. Write a function count_vowels(s) that counts and returns the number of vowels in a string.
Medium
1. Write a function is_palindrome(s) that returns True if a string reads the same forwards
and backwards (case-insensitive).
2. Write a function fibonacci(n) that returns the n-th Fibonacci number using recursion.
3. Write a function calculator(a, b, op='+') that performs the specified operation and returns
the result. Handle division by zero.
Challenging
1. Write a function flatten(lst) that takes a nested list (lists inside lists of any depth) and
returns a single flat list using recursion. Example: flatten([1,[2,[3,4]],5]) -> [1,2,3,4,5].
2. Write a function memoize(func) that takes a function and returns a version of it that
caches its results so repeated calls with the same argument are instant.
— 44 —
CS50 Python — A Complete Course Companion CS50 Python
Solutions
# Easy 1
def is_even(n):
return n % 2 == 0
# Easy 3
def count_vowels(s):
return sum(1 for char in s if [Link]() in 'aeiou')
# Medium 1
def is_palindrome(s):
s = [Link]()
return s == s[::-1]
# Medium 2
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
# Hard 1 — flatten
def flatten(lst):
result = []
for item in lst:
if isinstance(item, list):
[Link](flatten(item))
else:
[Link](item)
return result
— 45 —
CS50 Python — A Complete Course Companion CS50 Python
result = ''
count = 1
for i in range(1, len(s)):
if s[i] == s[i-1]:
count += 1
else:
result += str(count) + s[i-1]
count = 1
result += str(count) + s[-1]
return result
print(run_length_encode('aaabbbcc')) # '3a3b2c'
Chapter Summary
• Functions are reusable blocks of code defined with def and called by name.
• Parameters are placeholders in the definition; arguments are the actual values
passed.
• return sends a value back to the caller and exits the function immediately.
• Local variables exist only inside their function; global variables exist everywhere.
• Recursive functions call themselves with a smaller problem and must have a base
case.
— 46 —
CS50 Python — A Complete Course Companion CS50 Python
CHAPTER 5
Exceptions
"The question is not whether your program will encounter errors. It will. The
question is whether your program handles them gracefully — or crashes and
leaves the user bewildered."
An exception is an event that disrupts the normal flow of a program. When Python encounters
something it cannot handle — dividing by zero, accessing a file that doesn't exist, converting
'hello' to an integer — it raises an exception. Without handling, the program crashes and prints
a traceback. With handling, you control what happens next.
— 47 —
CS50 Python — A Complete Course Companion CS50 Python
try:
number = int(input("Enter a number: "))
result = 100 / number
print(f"100 / {number} = {result}")
except ValueError:
print("That wasn't a valid number!")
except ZeroDivisionError:
print("You can't divide by zero!")
How It Works
try:
risky_code()
except Exception as e: # Catches any exception
print(f"Something went wrong: {e}")
excep signals.
t:
— 48 —
CS50 Python — A Complete Course Companion CS50 Python
try:
number = int(input("Enter a number: "))
result = 100 / number
except ValueError:
print("Invalid input.")
except ZeroDivisionError:
print("Cannot divide by zero.")
else:
# Runs ONLY if no exception occurred in try
print(f"Success! Result is {result}")
finally:
# ALWAYS runs — exception or not
print("This always executes.")
The else clause is the 'success path' — it only runs if the try block completed without raising
any exception. The finally clause runs no matter what — even if an exception was raised
and not caught, even if there's a return statement in the except block. Use finally for cleanup:
closing files, releasing resources.
def set_age(age):
if not isinstance(age, int):
raise TypeError("Age must be an integer")
if age < 0 or age > 150:
raise ValueError(f"Age {age} is not realistic")
return age
try:
set_age(-5)
except ValueError as e:
print(f"Error: {e}")
# Output: Error: Age -5 is not realistic
— 49 —
CS50 Python — A Complete Course Companion CS50 Python
# Use it
def withdraw(balance, amount):
if amount > balance:
raise InsufficientFundsError(amount, balance)
return balance - amount
try:
new_balance = withdraw(100, 150)
except InsufficientFundsError as e:
print(f"Transaction failed: {e}")
def get_int(prompt):
"""Keep asking until the user enters a valid integer."""
while True:
try:
return int(input(prompt))
except ValueError:
print("Please enter a whole number.")
— 50 —
CS50 Python — A Complete Course Companion CS50 Python
# Fix: keep try blocks as short as possible — only the risky part
# Better:
index = [Link](target) if target in items else -1
Practice Problems
— 51 —
CS50 Python — A Complete Course Companion CS50 Python
Easy
1. Write a program that asks for two numbers and divides the first by the second. Handle
both ValueError (non-numeric input) and ZeroDivisionError.
2. Write a function safe_open(filename) that opens a file and returns its contents. If the file
doesn't exist, return None.
3. Modify the get_int() pattern to also ensure the integer is positive.
Medium
1. Write a function safe_convert(value, to_type) that attempts to convert a value to the given
type (int, float, str) and returns (True, converted) on success or (False, None) on failure.
2. Create a custom exception AgeError and a function validate_age(age) that raises it for
negative or > 150 values. Write a test program.
3. Write a robust CSV reader function that reads a file line by line. Skip lines that cause
errors and continue processing the rest.
Challenging
1. Build a simple bank account class (no OOP needed — use a dictionary) with deposit,
withdraw, and get_balance functions. Define custom exceptions InsufficientFundsError and
NegativeAmountError.
2. Create a retry decorator (a function that wraps another function) that re-calls the function
up to n times if it raises an exception, then gives up.
3. Write a config file parser that reads key=value pairs from a file. Handle all possible errors:
file not found, malformed lines, duplicate keys. Return a dictionary of valid key-value pairs.
Solutions
# Easy 1
try:
a = float(input("First: "))
b = float(input("Second: "))
print(a / b)
except ValueError:
print("Please enter numbers only.")
except ZeroDivisionError:
print("Cannot divide by zero.")
# Easy 2
— 52 —
CS50 Python — A Complete Course Companion CS50 Python
def safe_open(filename):
try:
with open(filename) as f:
return [Link]()
except FileNotFoundError:
return None
# Medium 1
def safe_convert(value, to_type):
try:
return True, to_type(value)
except (ValueError, TypeError):
return False, None
def create_account(initial=0):
return {'balance': initial}
Chapter Summary
• try wraps risky code; except catches specific exceptions that occur.
• else runs only when no exception was raised; finally always runs.
— 53 —
CS50 Python — A Complete Course Companion CS50 Python
• Custom exceptions inherit from Exception and make error handling precise.
• Keep try blocks short — only wrap the code that might raise the exception.
— 54 —
CS50 Python — A Complete Course Companion CS50 Python
CHAPTER 6
File Handling
"A program that forgets everything when it ends is a program that never truly
learned. Files give your programs a memory that outlasts any single run."
Analo Your brain stores short-term memories for as long as you're awake. But
gy important information — phone numbers, recipes, ideas — gets written
down. A Python program's variables exist only while the program runs. Files
are your program's notebook — persistent storage that survives between
runs.
The with statement is a context manager. It guarantees the file is closed when the block exits
— whether by reaching the end normally or by an exception being raised. Always use with
open(...).
File Modes
— 55 —
CS50 Python — A Complete Course Companion CS50 Python
— 56 —
CS50 Python — A Complete Course Companion CS50 Python
import csv
— 57 —
CS50 Python — A Complete Course Companion CS50 Python
[Link]()
[Link](students)
import json
print(loaded['name']) # Alice
print(type(loaded)) # <class 'dict'>
Python JSON
------- ----
dict <-> object {}
list <-> array []
str <-> string ""
— 58 —
CS50 Python — A Complete Course Companion CS50 Python
Practice Problems
Easy
— 59 —
CS50 Python — A Complete Course Companion CS50 Python
1. Write a program that asks the user to enter 5 names and saves them to '[Link]', one
per line.
2. Write a program that reads '[Link]' and prints each name in uppercase.
3. Write a program that appends the current date and time to '[Link]' each time it runs.
Medium
1. Create a contacts CSV file with columns name, phone, email. Write a program that reads
it and lets the user search by name.
2. Write a program that reads a text file and counts: total words, unique words, and the 5
most common words.
3. Write a simple student grade book: read from JSON on startup, allow adding students
and grades, save back to JSON on exit.
Challenging
1. Build a CSV to JSON converter: read any CSV file and convert it to a JSON array of
objects, where each row becomes an object with column headers as keys.
2. Write a log file analyzer: read a server log where each line is 'timestamp | level |
message'. Count entries by level (INFO, WARNING, ERROR) and show the 5 most recent
errors.
3. Implement a simple file-based database with functions: insert(table, record), find(table,
key, value), and delete(table, key, value). Store everything as JSON.
Solutions
# Easy 1
with open('[Link]', 'w') as f:
for _ in range(5):
name = input('Enter name: ')
[Link](name + '\n')
# Easy 2
with open('[Link]', 'r') as f:
for line in f:
print([Link]().upper())
— 60 —
CS50 Python — A Complete Course Companion CS50 Python
Chapter Summary
• Always use 'with open(...)' — it guarantees the file is closed even if an error occurs.
• File modes: 'r' (read), 'w' (write/overwrite), 'a' (append), 'x' (exclusive create).
• read() gets the whole file; readline() gets one line; iterating the file object is most
efficient.
• write() does not add newlines — add \n explicitly or use print(..., file=f).
• The csv module handles all CSV edge cases — never split on commas manually.
• DictReader and DictWriter make CSV feel like working with dictionaries.
• [Link]() writes Python objects to JSON files; [Link]() reads them back.
• Always handle FileNotFoundError when opening files that might not exist.
— 61 —
CS50 Python — A Complete Course Companion CS50 Python
CHAPTER 7
"Don't reinvent the wheel. Stand on the shoulders of giants. Every time you
import a library, you inherit decades of work."
Analo When you build a house, you don't mine the ore and smelt the steel for your
gy nails. You go to a hardware store. A library is Python's hardware store:
thousands of pre-built components for sorting, math, networking, web
scraping, machine learning, and everything in between.
— 62 —
CS50 Python — A Complete Course Companion CS50 Python
import random
numbers = [1, 2, 3, 4, 5]
[Link](numbers) # Shuffles list in place
print(numbers)
import sys
import os
— 63 —
CS50 Python — A Complete Course Companion CS50 Python
now = [Link]()
today = [Link]()
# Date arithmetic
tomorrow = today + timedelta(days=1)
last_week = today - timedelta(weeks=1)
— 64 —
CS50 Python — A Complete Course Companion CS50 Python
# File: [Link]
def square(n):
return n * n
def cube(n):
return n * n * n
PI = 3.14159
print(square(4)) # 16
— 65 —
CS50 Python — A Complete Course Companion CS50 Python
print(cube(3)) # 27
print(PI) # 3.14159
# File: [Link]
def square(n):
return n * n
Practice Problems
Easy
1. Use the random module to simulate rolling two six-sided dice 10 times. Print each result
and count how many times you rolled doubles.
2. Use the datetime module to print today's date in the format 'Day, Month DD YYYY' (e.g.,
'Monday, January 15 2024').
3. Use the os module to list all .py files in the current directory.
Medium
1. Write a word frequency counter using [Link]. Read from a text file and
display the top 10 most common words, excluding common stop words (the, a, is, etc.).
2. Create a module called [Link] with functions for area and perimeter of circle,
rectangle, and triangle. Import and test it in a [Link] file.
3. Build a simple dice statistics program: roll a die n times (user chooses n). Use Counter to
display how many times each face appeared and the percentage.
— 66 —
CS50 Python — A Complete Course Companion CS50 Python
Challenging
1. Use the requests library to fetch data from a public API (e.g.,
[Link] for Bitcoin price). Parse the JSON
response and display a formatted summary.
2. Create a command-line tool using [Link]. The user runs it as 'python [Link] add 5 3',
'python [Link] multiply 4 6', etc. Parse the arguments and perform the requested operation.
3. Build a file organizer: scan a directory, group files by extension, create subdirectories for
each group, and move files into them.
Chapter Summary
• import module loads a module; from module import name imports specific names.
• Use import module as alias for long or commonly aliased names (numpy as np).
• The Python standard library has modules for almost everything: math, random, os,
sys, datetime, json, csv.
• Any .py file is a module — save functions there and import them anywhere.
— 67 —
CS50 Python — A Complete Course Companion CS50 Python
CHAPTER 8
Regular Expressions
"Some people, when confronted with a problem, think: 'I know, I'll use regular
expressions.' Now they have two problems. — Jamie Zawinski. (The point:
learn them well and they become one very powerful solution.)"
Analo Imagine you're searching for all phone numbers in a 1000-page document.
gy You can't search for a specific number — you need to search for the
PATTERN: three digits, a dash, three digits, a dash, four digits. A regular
expression is a precise description of a pattern in text.
A regular expression (regex) is a sequence of characters that defines a search pattern. They
are used for: validating input (is this a valid email?), searching text (find all dates), extracting
data (pull all URLs from HTML), and transforming text (replace all phone formats with a
standard format).
import re
— 68 —
CS50 Python — A Complete Course Companion CS50 Python
Notice the r prefix before pattern strings: r'pattern'. This is a raw string — backslashes
are treated literally, not as escape sequences. Always use raw strings for regular expressions
to avoid unexpected behavior.
Character Classes [ ]
# [abc] matches a, b, or c
[Link](r'[aeiou]', 'hello world') # ['e', 'o', 'o']
Special Sequences
— 69 —
CS50 Python — A Complete Course Companion CS50 Python
\s Whitespace [ \t\n\r]
\S Non-whitespace [^ \t\n\r]
— 70 —
CS50 Python — A Complete Course Companion CS50 Python
import re
# Email validation
def is_valid_email(email):
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return bool([Link](pattern, email))
print(is_valid_email('alice@[Link]')) # True
print(is_valid_email('not_an_email')) # False
# URL extraction
html = '<a href="[Link]
urls = [Link](r'https?://[^"]+', html)
print(urls) # ['[Link]
— 71 —
CS50 Python — A Complete Course Companion CS50 Python
Practice Problems
Easy
1. Write a function that checks if a string contains only alphabetic characters (no digits or
spaces).
2. Write a function that extracts all numbers (integers and decimals) from a string.
3. Write a function that checks if a string is a valid US zip code (5 digits, or 5+4 format:
12345-6789).
Medium
1. Write a function that validates a password: at least 8 characters, at least one uppercase
letter, at least one digit, and at least one special character (!@#$%^&*).
2. Write a function that finds all HTML tags in a string and returns a list of tag names without
the angle brackets. Example: 'Hello World' -> ['p', 'b', '/b'].
3. Write a program that reads a text file and finds all email addresses in it.
Challenging
1. Write a function that parses a log line of the format '[2024-01-15 14:23:01] ERROR
[Link] -- Connection refused' and returns a dictionary with keys: date, time, level, file,
line, message.
2. Write a function that takes a Markdown string and converts it to plain text by removing all
Markdown formatting (headers #, bold **, italic *, links [text](url)).
3. Build a simple template engine: given a template string with {{variable}} placeholders and
a dictionary of values, replace all placeholders with their values using [Link].
Chapter Summary
— 72 —
CS50 Python — A Complete Course Companion CS50 Python
• [Link]() finds first match anywhere; [Link]() only at start; [Link]() returns all.
• Character classes [abc], special sequences (\d \w \s), and quantifiers (* + ? {n,m})
are the core vocabulary.
— 73 —
CS50 Python — A Complete Course Companion CS50 Python
CHAPTER 9
Object-Oriented Programming
Analo Imagine designing a video game with 100 different types of characters —
gy warriors, mages, archers. Each character has health, a name, attack power.
Without OOP, you'd have 300 separate variables just for health values. With
OOP, you define a Character blueprint once. Then you stamp out as many
characters as you need — each one is its own independent object with its
own data.
As programs grow larger, the procedural approach (functions and variables at the top level)
becomes hard to manage. OOP organizes code into objects — self-contained units that
combine data (attributes) and behavior (methods). This makes large programs comprehensible,
maintainable, and extensible.
— 74 —
CS50 Python — A Complete Course Companion CS50 Python
| |
OBJECT OBJECT
name='Buddy' name='Max'
breed='Lab' breed='Poodle'
age=3 age=5
(each has its own data, same blueprint)
class Dog:
"""Represents a dog."""
def bark(self):
"""Make the dog bark."""
print(f"{[Link]} says: Woof!")
def describe(self):
"""Print a description of the dog."""
print(f"{[Link]} is a {[Link]}-year-old {[Link]}.")
— 75 —
CS50 Python — A Complete Course Companion CS50 Python
The self parameter is a reference to the object being created. Python passes it automatically
— you never pass it manually when calling methods. It is how an object refers to its own data.
9.5 Methods
Methods are functions defined inside a class. They always receive self as their first parameter
— this is how they access the object's attributes.
class BankAccount:
def __init__(self, owner, balance=0):
[Link] = owner
[Link] = balance
[Link] = []
— 76 —
CS50 Python — A Complete Course Companion CS50 Python
def get_balance(self):
return [Link]
def print_statement(self):
print(f'Account: {[Link]}')
for entry in [Link]:
print(f' {entry}')
print(f' Balance: ${[Link]}')
Analo Every mammal breathes air and nurses its young. A dog is a mammal, so it
gy gets those capabilities for free. But a dog also barks, which other mammals
don't. Inheritance lets a child class inherit all the attributes and methods of a
parent class and then add or override what it needs.
class Animal:
def __init__(self, name, sound):
[Link] = name
[Link] = sound
def speak(self):
print(f'{[Link]} says {[Link]}!')
def __str__(self):
return f'Animal: {[Link]}'
— 77 —
CS50 Python — A Complete Course Companion CS50 Python
class Cat(Animal):
def __init__(self, name):
super().__init__(name, 'Meow')
def purr(self):
print(f'{[Link]} purrs...')
Method Overriding
class Shape:
def area(self):
return 0 # Default implementation
class Circle(Shape):
def __init__(self, radius):
[Link] = radius
class Rectangle(Shape):
def __init__(self, width, height):
[Link] = width
[Link] = height
— 78 —
CS50 Python — A Complete Course Companion CS50 Python
The last example demonstrates polymorphism — the ability to call the same method
(area()) on different object types and get the correct behavior for each. Python figures out
which version to call based on the object's actual type at runtime.
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
# Called by print() and str()
return f'Vector({self.x}, {self.y})'
def __repr__(self):
# Called in interactive shell and by repr()
return f'Vector(x={self.x}, y={self.y})'
def __len__(self):
# Called by len()
import math
return int([Link](self.x**2 + self.y**2))
v1 = Vector(2, 3)
v2 = Vector(1, 4)
— 79 —
CS50 Python — A Complete Course Companion CS50 Python
class Temperature:
def __init__(self, celsius):
self._celsius = celsius # _ prefix = 'private by convention'
@property
def celsius(self):
return self._celsius
@[Link]
def celsius(self, value):
if value < -273.15:
raise ValueError('Temperature below absolute zero!')
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9/5 + 32
t = Temperature(25)
print([Link]) # 25 — uses getter
print([Link]) # 77.0 — computed property
[Link] = 100 # Uses setter (with validation)
[Link] = -300 # ValueError!
— 80 —
CS50 Python — A Complete Course Companion CS50 Python
d = Dog()
[Link]() # TypeError: bark() takes 0 args, 1 given
class Dog:
def bark(self): # Correct
print('Woof')
Practice Problems
Easy
1. Create a Rectangle class with width and height attributes and methods area() and
perimeter(). Test with several instances.
2. Create a Student class with name and grades (a list). Add methods add_grade(g),
average(), and highest_grade().
3. Create a Counter class that starts at 0 and has methods increment(), decrement(),
reset(), and value().
— 81 —
CS50 Python — A Complete Course Companion CS50 Python
Medium
1. Create a Book class and a Library class. Library has a list of Books. Add methods
add_book(), remove_book(), find_by_author(), and list_all().
2. Create a Shape hierarchy: base class Shape with an abstract area() method, then Circle,
Rectangle, and Triangle as subclasses. Each implements area(). Write a function
total_area(shapes) that sums areas of any mix of shapes.
3. Create a Stack class (LIFO data structure) using a list internally. Add push(), pop(),
peek() (view top without removing), is_empty(), and size().
Challenging
1. Implement a simple linked list with a Node class and a LinkedList class. LinkedList should
support append(), prepend(), delete(value), find(value), and __str__() for printing.
2. Build a simple ORM (Object-Relational Mapper) backed by a dictionary: a Model base
class with save(), delete(), find_by_id() class methods, and a way to define fields. Create
User and Product as subclasses.
3. Design a card game: create Card, Deck, Hand, and Player classes. Deck should shuffle
and deal cards. Implement a simple game like War where two players draw cards and the
higher card wins.
Solutions
# Easy 1 — Rectangle
class Rectangle:
def __init__(self, width, height):
[Link] = width
[Link] = height
# Easy 3 — Counter
class Counter:
def __init__(self): self._count = 0
def increment(self): self._count += 1
def decrement(self): self._count -= 1
def reset(self): self._count = 0
def value(self): return self._count
— 82 —
CS50 Python — A Complete Course Companion CS50 Python
# Medium 3 — Stack
class Stack:
def __init__(self): self._items = []
def push(self, item): self._items.append(item)
def pop(self):
if self.is_empty(): raise IndexError('Stack is empty')
return self._items.pop()
def peek(self):
if self.is_empty(): raise IndexError('Stack is empty')
return self._items[-1]
def is_empty(self): return len(self._items) == 0
def size(self): return len(self._items)
Chapter Summary
• self refers to the object itself; all instance methods must accept it as first parameter.
• Instance attributes belong to one object; class attributes are shared by all instances.
• super() calls the parent class's method — essential in __init__ of child classes.
• Polymorphism lets different object types respond correctly to the same method call.
• Special methods (__str__, __add__, __eq__, etc.) make objects integrate with
Python syntax.
• Encapsulation uses _ prefix and @property to protect and validate attribute access.
— 83 —
CS50 Python — A Complete Course Companion CS50 Python
CHAPTER
— 84 —
CS50 Python — A Complete Course Companion CS50 Python
You have the foundation. Now go build something. The Python community, Stack Overflow, the
documentation at [Link], and this book will always be here when you need them.
— 85 —