Recipes for people learning to code
249 commands and snippets from the free lessons, each with what it does. Open the lesson and every one runs in your browser, with a preview of what it produces; change it and run it again.
Your first program Free
A program is a list of instructions
print("Hello, world")
print("Index Ai Academy is running your code")
Comments
# Weekly report for the spring campaign
print("Spring campaign")
print("Clicks:", 1540) # from the ads dashboard, Monday to Sunday
# print("Conversions:", 61)
Variables Free
campaign metrics
sends = 24000
clicks = 1920
purchases = 132
click_through_rate = clicks / sends
conversion_rate = purchases / clicks
print("CTR:", click_through_rate)
print("Conversion rate:", conversion_rate)
From Variables · run it in the lesson
A name for a value
campaign = "Spring Sale"
budget = 5000
print(campaign)
print(budget)
From Variables · run it in the lesson
Changing a variable
clicks = 120
clicks = clicks + 30 # take the current value, add 30, store the result
print(clicks)
From Variables · run it in the lesson
Types: what kind of value it is
print(type("Spring Sale"))
print(type(5000))
print(type(0.035))
print(type(True))
From Variables · run it in the lesson
Numbers and arithmetic Free
ROAS and CPA from exported text
spend_text = "2390.75"
revenue_text = "3290"
conversions_text = "47"
spend = float(spend_text)
revenue = float(revenue_text)
conversions = int(conversions_text)
roas = revenue / spend
cpa = spend / conversions
print("ROAS:", round(roas, 2))
print("CPA:", round(cpa, 2))
The arithmetic operators
spend = 1250.50
clicks = 500
print("Cost per click:", spend / clicks)
print("Spend doubled:", spend * 2)
Order of operations
revenue = 15840
cost = 3120.5
wrong_margin = revenue - cost / revenue # divides first, then subtracts
margin = (revenue - cost) / revenue # what we actually meant
print(wrong_margin)
print(margin)
Powers are how you model compounding: growth that builds on itself
weekly_signups = 1000
growth = 1.05 # 5% more than last week: 1.05 times
print(weekly_signups * growth ** 4) # four weeks of 5% growth
round(number, digits) rounds to that many decimal places
ctr = 0.034782
print(round(ctr, 3))
print(round(ctr * 100, 1)) # as a percentage
print(round(1157.625))
Why 0.1 + 0.2 isn't 0.3
print(0.1 + 0.2)
Converting between text and numbers
budget_text = "5000" # text, perhaps read from a file
budget = int(budget_text) # now a number
print(budget + 100)
price = float("19.99")
print(price * 3)
print("Clicks: " + str(120)) # + joins text, so the number must become text first
Fractions, ratios and rates Free
A fraction is a division
part = 3
whole = 8
share = part / whole
print(share) # as a decimal
print(share * 100, "%") # as a percent
print(25 / 100) # 25% back as a decimal
Rates: making groups comparable
big_clicks = 300
big_sent = 10000
small_clicks = 120
small_sent = 2000
print("Big list:", big_clicks / big_sent)
print("Small list:", small_clicks / small_sent)
Percent change versus percentage points
old_rate = 4.0
new_rate = 5.0
points = new_rate - old_rate
percent_change = (new_rate - old_rate) / old_rate * 100
print(points, "percentage points")
print(percent_change, "% change")
Averages of rates can lie
monday = 9 / 10
tuesday = 10 / 100
print("Average of the rates:", (monday + tuesday) / 2)
print("Overall rate:", (9 + 10) / (10 + 100))
Ratios: scaling up and down
serves = 4
guests = 10
factor = guests / serves # 2.5 times the recipe
print(factor, 2 * factor, 1 * factor)
Negative numbers and distance from zero Free
The number line
print(-3 < -1) # True: -3 is further left
print(3 - 10) # 7 steps left of zero
print(-5 + 8) # start at -5, move 8 right
From Negative numbers and distance from zero · run it in the lesson
The sign rules
print(10 + -4, 10 - -4)
print(-3 * -4, -3 * 4, -12 / -3)
From Negative numbers and distance from zero · run it in the lesson
Distance from zero: abs()
print(abs(-7), abs(7), abs(0))
predicted = 120
actual = 104
error = actual - predicted
print(error, abs(error))
From Negative numbers and distance from zero · run it in the lesson
// and % with negative numbers
print(-7 // 2, -7 % 2) # -4 and 1, because -4 * 2 + 1 == -7
print((-7 // 2) * 2 + (-7 % 2)) # always gets back to -7
From Negative numbers and distance from zero · run it in the lesson
Powers, roots and powers of ten Free
A power is repeated multiplication
print(2 ** 5)
print(7 ** 2, 7 ** 3)
print(5 ** 1, 5 ** 0) # anything to the power 1 is itself; to the power 0 is 1
Powers of ten and e-notation
print(7e10)
print(3e-4)
print(1e6 == 1000000)
print(0.00001) # Python switches to e-notation for very small floats
Roots undo powers
print(25 ** 0.5)
print(2 ** 0.5) # the side of a square with area 2
print(8 ** (1/3)) # a cube root: 2 × 2 × 2 = 8
Doubling, halving and growth
print(1.10 ** 7) # about 1.95: nearly doubled
print(72 / 10) # the rule of 72's estimate
print(1000 * 0.5 ** 3) # halving three times
Formulas: order, substitution and rearranging Free
Which part comes first
print(2 + 3 * 4) # 3 * 4 first: 14
print((2 + 3) * 4) # brackets first: 20
print(2 * 3 ** 2) # the power first: 18
print(-3 ** 2) # the power before the minus sign: -9
From Formulas: order, substitution and rearranging · run it in the lesson
Letters stand for numbers
miles = 8
fare = 3.50 + 2.25 * miles
print(fare)
From Formulas: order, substitution and rearranging · run it in the lesson
Rearranging: finding the missing number
fare = 21.50
miles = (fare - 3.50) / 2.25
print(miles)
print(3.50 + 2.25 * miles) # substitute back: the fare again
From Formulas: order, substitution and rearranging · run it in the lesson
Strings and f-strings Free
tidy a subject line and report on it
raw_subject = " LAST CHANCE: 30% off ends tonight! "
subject = raw_subject.strip()
softer = subject.capitalize()
preview = subject[:20]
print(f"Cleaned: {softer}")
print(f"Length: {len(subject)} characters")
print(f"Preview: {preview}...")
print(f"Discount? {'%' in subject}")
Text in quotes
headline = "It's our biggest sale"
quote = 'She said "worth every penny"'
print(headline)
print(quote)
Campaign codes, SKUs and UTM values often pack several facts into one string
code = "SUMMER25-EMAIL"
season = code[:8]
channel = code[-5:]
print(season)
print(channel)
print(code[0], code[-1])
String methods
raw = " Spring SALE Launch "
clean = raw.strip().lower()
print(clean)
print(raw) # the original is unchanged
print(clean.replace("sale", "event"))
Splitting and joining
tags = "seo, email, ads".split(", ")
print(tags)
print(" | ".join(tags))
words = "write three short headlines".split() # no separator: split on any whitespace
print(words)
f-strings: text built from values
product = "Midweight Hoodie"
audience = "college students"
count = 3
prompt = f"Write {count} ad headlines for the {product}, aimed at {audience}."
print(prompt)
Special characters and multi-line text
print("Subject: Spring sale\nPreview: 30% off everything")
print("Name:\tDana")
Here's the fixed prompt
product = "Canvas Tote"
print(f'Rate the {product} caption. Answer as JSON, like {{"score": 4}}')
Methods give you a new string
name = " ada "
name.strip()
print(repr(name))
name = name.strip()
print(repr(name))
Decisions with if/else Free
flag a campaign that needs attention
name = "Retargeting - Display"
budget = 2000
spend = 1880
conversions = 88
pacing = spend / budget
cpa = spend / conversions
if pacing > 0.9 and cpa > 20:
print(f"{name}: pause and review (spent {pacing:.0%}, CPA {cpa:.2f})")
elif pacing > 0.9:
print(f"{name}: nearly out of budget")
else:
print(f"{name}: on track")
Comparisons give True or False
spend = 1890
budget = 2000
print(spend > budget)
print(spend <= budget)
print("active" == "Active") # text comparisons are case-sensitive
if: run code only when something is true
spend = 2150
budget = 2000
if spend > budget:
print("Over budget!")
print("Overspend:", spend - budget)
print("Check complete")
Comparing text
country = " us "
subject = "Last chance: free shipping"
print(country == "US")
print(country.strip().upper() == "US")
print("free" in subject.lower())
Classic mistakes
total = 0.1 + 0.2
print(total == 0.3)
print(round(total, 2) == 0.3)
print(abs(total - 0.3) < 0.000001) # abs(a - b) is how far apart a and b are: abs(3 - 5) is 2
Truthy and falsy values
print(bool(0), bool(7), bool(""), bool("hi"), bool(None))
name = ""
if not name:
print("Please type your name")
Build a rule one case at a time
order = 75
if order >= 100:
shipping = 0
elif order >= 50:
shipping = 5
else:
shipping = 10
print(shipping)
Lists Free
a week of signups
signups = [42, 38, 51, 47, 60, 22, 19] # Monday to Sunday
weekdays = signups[:5]
weekend = signups[5:]
print("Total:", sum(signups))
print("Best day:", max(signups))
print("Weekday average:", sum(weekdays) / len(weekdays))
print("Weekend average:", sum(weekend) / len(weekend))
print("High to low:", sorted(signups, reverse=True))
print("Order kept:", signups)
From Lists · run it in the lesson
Many values, in order
channels = ["search", "display", "video", "email", "social"]
daily_clicks = [412, 388, 501, 467, 590]
empty = []
print(channels)
print(len(channels)) # how many items
print(len(empty))
From Lists · run it in the lesson
As with strings, asking for a position that doesn't exist, such as channels[5] h
channels = ["search", "display", "video", "email", "social"]
print(channels[0])
print(channels[-1])
print(channels[1:3])
print(channels[:2])
From Lists · run it in the lesson
Changing a list
queue = ["Spring sale", "New arrivals"]
queue.append("Customer stories")
queue.insert(0, "Welcome email")
print(queue)
queue[1] = "Spring sale (final)"
queue.remove("New arrivals")
sent = queue.pop(0)
print("Sent:", sent)
print("Still queued:", queue)
From Lists · run it in the lesson
Asking questions about a list
daily_spend = [310.5, 288.0, 402.75, 365.2, 290.0]
print("Days:", len(daily_spend))
print("Total:", sum(daily_spend))
print("Lowest:", min(daily_spend), "Highest:", max(daily_spend))
print("Average:", round(sum(daily_spend) / len(daily_spend), 2)) # add them up, divide by how many
tags = ["seo", "email", "ads"]
print("email" in tags) # True or False
print(tags.count("seo")) # how many times it appears
print(tags.index("ads")) # where it first appears
From Lists · run it in the lesson
Sorting
scores = [72, 95, 64, 88]
ranked = sorted(scores, reverse=True)
print(ranked)
print(scores) # unchanged
scores.sort()
print(scores) # now changed
result = scores.sort()
print(result) # None: never write scores = scores.sort()
From Lists · run it in the lesson
Two names, one list
approved = ["Headline A", "Headline B"]
backup = approved # same list, two names
backup.append("Headline C")
print(approved) # it changed too
real_copy = approved.copy() # a separate list with the same items
real_copy.append("Headline D")
print(approved) # unchanged this time
print(real_copy)
From Lists · run it in the lesson
Lists inside lists
rows = [
["cmp_101", "search", 5000],
["cmp_102", "display", 2000],
]
print(rows[1])
print(rows[1][2])
From Lists · run it in the lesson
Copies of lists inside lists
grid = [["."] * 3] * 3
grid[0][0] = "X"
print(grid)
From Lists · run it in the lesson
Two more tools: + and slicing to -1
print([1] + [2, 3])
items = ["a", "b", "c", "d"]
print(items[:-1])
From Lists · run it in the lesson
Loops Free
a week of ad results
ads = [
["Headline A", 12000, 360],
["Headline B", 9500, 180],
["Headline C", 15000, 600],
["Headline D", 4000, 40],
]
total_clicks = 0
best_name = ""
best_ctr = 0
weak = []
for name, impressions, clicks in ads:
ctr = clicks / impressions
total_clicks += clicks
if ctr > best_ctr:
best_ctr = ctr
best_name = name
if ctr < 0.02:
weak.append(name)
print(f"Total clicks: {total_clicks:,}")
print(f"Best: {best_name} ({best_ctr:.1%})")
print(f"Under 2% CTR: {weak}")
From Loops · run it in the lesson
Do something for every item
channels = ["search", "display", "email"]
for channel in channels:
print("Checking", channel)
print("Done")
From Loops · run it in the lesson
Looping over rows
campaigns = [
["Spring Sale", 5000, 3120.5],
["Retargeting", 2000, 2150.0],
["Newsletter", 500, 120.0],
]
for name, budget, spend in campaigns:
remaining = budget - spend
print(f"{name}: {remaining:,.2f} left")
From Loops · run it in the lesson
Counting with range, numbering with enumerate, pairing with zip
for week in range(1, 4):
print("Week", week)
print(list(range(0, 100, 25))) # list() shows the numbers range produces
From Loops · run it in the lesson
while: repeat until something changes
budget = 1000
weeks = 0
while budget < 5000:
budget = budget * 1.4 # grow 40% a week
weeks += 1
print(weeks, "weeks to pass 5000; budget is now", round(budget))
From Loops · run it in the lesson
break and continue
campaigns = [["Search", 5000, 3120.5], ["Display", 2000, 2150], ["Email", 500, 620]]
for name, budget, spend in campaigns:
if spend > budget:
print("First campaign over budget:", name)
break
emails = ["ana@shop.com", "", "ben@shop.com", " ", "cy@shop.com"]
sent = 0
for email in emails:
if email.strip() == "":
continue # skip blanks
sent += 1
print("Sent to", sent, "addresses")
From Loops · run it in the lesson
Averages and spread Free
The mean: share it out equally
bills = [18, 25, 12, 30, 15]
mean = sum(bills) / len(bills)
print(mean)
The median: the middle value
times = [12, 3, 7, 30, 5]
in_order = sorted(times)
print(in_order)
middle = len(in_order) // 2
print(in_order[middle])
Spread: how far from the middle?
values = [38, 40, 42, 40]
mean = sum(values) / len(values)
total = 0
for v in values:
total = total + (v - mean) ** 2
std = (total / len(values)) ** 0.5
print(mean, round(std, 2))
Coordinates, slopes and reading a chart Free
Two numbers place a point
points = [(3, 2), (-3, 2), (3, -2), (-3, -2)]
for x, y in points:
print("across", x, "and up", y)
From Coordinates, slopes and reading a chart · run it in the lesson
Slope: rise over run
x1, y1 = 1, 3
x2, y2 = 4, 9
slope = (y2 - y1) / (x2 - x1)
print(slope) # up 6 over 3 steps: 2.0 per step
From Coordinates, slopes and reading a chart · run it in the lesson
Dictionaries Free
an LLM chat request
request = {
"model": "claude-opus-5-5",
"max_tokens": 300,
"messages": [
{"role": "user", "content": "Suggest a subject line for our spring sale."},
],
}
reply = {"role": "assistant", "content": "Spring into savings: 30% off this week"}
request["messages"].append(reply)
request["messages"].append({"role": "user", "content": "Make it shorter."})
print(len(request["messages"]), "messages")
print("Last speaker:", request["messages"][-1]["role"])
print("First answer:", request["messages"][1]["content"])
print("System prompt:", request.get("system", "(none)"))
From Dictionaries · run it in the lesson
Look things up by name
campaign = {
"name": "Spring Sale",
"channel": "search",
"budget": 5000,
"spend": 3120.5,
}
print(campaign["name"])
print(campaign["budget"] - campaign["spend"])
print(len(campaign)) # number of key-value pairs
From Dictionaries · run it in the lesson
Adding, changing and removing
campaign = {"name": "Spring Sale", "budget": 5000, "status": "paused"}
campaign["status"] = "active" # key exists: replace the value
campaign["clicks"] = 5470 # new key: add it
campaign["budget"] += 500 # update using the current value
del campaign["name"] # remove a key and its value
status = campaign.pop("status") # remove it and get the value back
print(campaign)
print(status)
campaign.update({"spend": 3120.5, "clicks": 5600}) # add or replace several at once
print(campaign)
From Dictionaries · run it in the lesson
When a key might be missing
lead = {"email": "dana@example.com", "source": "webinar"}
print("phone" in lead)
print(lead.get("phone"))
print(lead.get("phone", "unknown"))
print(lead.get("source", "unknown"))
From Dictionaries · run it in the lesson
Keys, values and items
spend = {"search": 3120.5, "display": 1890.0, "email": 120.0}
print(list(spend.keys()))
print(list(spend.values()))
print(list(spend.items()))
print("Total:", sum(spend.values()))
print("Channels A-Z:", sorted(spend)) # sorting a dict sorts its keys
From Dictionaries · run it in the lesson
Nested data
report = {
"account": "Index Demo Shop",
"campaigns": [
{"name": "Spring Sale", "tags": ["search", "seasonal"], "clicks": 5470},
{"name": "Retargeting", "tags": ["display"], "clicks": 2050},
],
}
print(report["campaigns"][1]["name"]) # 2nd campaign's name
print(report["campaigns"][0]["tags"][-1]) # 1st campaign's last tag
print(len(report["campaigns"]))
From Dictionaries · run it in the lesson
Reading API replies safely
full = {"content": "Spring into savings", "usage": {"input_tokens": 41, "output_tokens": 9}}
short = {"content": "Spring into savings"}
print(full.get("usage", {}).get("output_tokens", 0))
print(short.get("usage", {}).get("output_tokens", 0))
From Dictionaries · run it in the lesson
A pattern worth knowing: counting
words = ["sale", "boots", "sale", "coat", "sale", "boots"]
counts = {}
for word in words:
counts[word] = counts.get(word, 0) + 1
print(counts)
From Dictionaries · run it in the lesson
Walking through nested data
order = {"id": 7, "items": [{"sku": "TENT", "qty": 1}, {"sku": "MUG", "qty": 2}]}
print(order["items"]) # the list
print(order["items"][1]) # its second item
print(order["items"][1]["sku"])
From Dictionaries · run it in the lesson
Functions Free
scoring subject lines
def word_count(text):
return len(text.split())
def score_subject(subject):
"""Score a subject line from 0 to 3 using simple rules."""
score = 0
if word_count(subject) <= 7:
score += 1
if "%" in subject:
score += 1
if subject != subject.upper():
score += 1
return score
subjects = [
"Last chance: 30% off everything",
"OUR BIGGEST SALE OF THE YEAR STARTS NOW FOR EVERYONE",
"New arrivals you'll love",
]
for s in subjects:
print(score_subject(s), "-", s)
From Functions · run it in the lesson
Packaging steps under a name
def click_through_rate(clicks, impressions):
return clicks / impressions
print(click_through_rate(5470, 182340))
print(click_through_rate(2050, 410200))
From Functions · run it in the lesson
return versus print
def total_with_print(a, b):
print(a + b)
def total_with_return(a, b):
return a + b
x = total_with_print(2, 3)
y = total_with_return(2, 3)
print("x is", x)
print("y is", y)
From Functions · run it in the lesson
Default values and keyword arguments
def subject_line(product, discount=10, urgent=False):
line = f"{discount}% off {product}"
if urgent:
line = line + " - today only"
return line
print(subject_line("hoodies"))
print(subject_line("hoodies", 25))
print(subject_line("hoodies", urgent=True))
print(subject_line(discount=40, product="mugs"))
From Functions · run it in the lesson
Docstrings
def roas(revenue, spend):
"""Return on ad spend: revenue earned per unit of currency spent. Returns 0 if spend is 0."""
if spend == 0:
return 0
return revenue / spend
print(roas.__doc__)
From Functions · run it in the lesson
Defining isn't running
def double(n):
print(n * 2) # shows it, but gives nothing back
answer = double(5)
print(answer)
From Functions · run it in the lesson
While loops Free
climbing piles of crates
from robot import *
def turn_right():
turn_left()
turn_left()
turn_left()
def climb_over():
turn_left()
while not right_is_clear():
move()
turn_right()
move()
move()
turn_right()
while front_is_clear():
move()
turn_left()
world("stacks")
while not at_goal():
if front_is_clear():
move()
else:
climb_over()
From While loops · run it in the lesson
When you can't count the repeats
savings = 0
weeks = 0
while savings < 500:
savings = savings + 60
weeks = weeks + 1
print("weeks needed:", weeks)
print("saved:", savings)
From While loops · run it in the lesson
A robot in a stockroom
from robot import *
world("hall")
while not at_goal():
move()
print("arrived")
From While loops · run it in the lesson
Finding bugs when nothing crashes Free
labels in the wrong order
def label(n):
if n % 3 == 0:
return "Fizz"
elif n % 5 == 0:
return "Buzz"
elif n % 15 == 0:
return "FizzBuzz"
return str(n)
for n in [3, 5, 15, 7]:
print(n, label(n))
From Finding bugs when nothing crashes · run it in the lesson
When the answer is just wrong
orders = [120, 80, 95, 140, 60, 110, 75]
total = 0
for amount in orders:
total = total + amount
average = total / 6
print("average order:", round(average, 2))
From Finding bugs when nothing crashes · run it in the lesson
Step 2: look inside with labeled prints
stock = [12, 0, 7, 3]
low = []
for count in stock:
print(f"{count=}")
if count > 0 and count < 5:
low.append(count)
print(f"{low=}")
From Finding bugs when nothing crashes · run it in the lesson
Functions as values Free
a shop assistant's tools
STOCK = {"tent": 4, "mug": 0, "stove": 9}
PRICES = {"tent": 289, "mug": 14, "stove": 65}
def in_stock(item):
return STOCK.get(item, 0) > 0
def price_of(item):
return PRICES.get(item)
TOOLS = {"in_stock": in_stock, "price_of": price_of}
def call_tool(name, **inputs):
if name not in TOOLS:
return {"error": f"No tool called {name}"}
return TOOLS[name](**inputs)
print(call_tool("price_of", item="stove"))
print(call_tool("in_stock", item="mug"))
print(call_tool("refund", order=12))
A function is a value
def shout(text):
return text.upper() + "!"
speak = shout
print(speak("tents on sale"))
print(shout)
A table of functions
def add(a, b):
return a + b
def subtract(a, b):
return a - b
def multiply(a, b):
return a * b
OPERATIONS = {"+": add, "-": subtract, "*": multiply}
operation = OPERATIONS["*"]
print(operation(6, 7))
print(OPERATIONS["-"](10, 4))
Handing a function to another function: key=
products = [
{"name": "Ridgeline tent", "price": 289},
{"name": "Trail mug", "price": 14},
{"name": "Camp stove", "price": 65},
]
def price_of(product):
return product["price"]
cheapest_first = sorted(products, key=price_of)
for p in cheapest_first:
print(p["name"])
print(max(products, key=price_of)["name"])
Small functions on one line: lambda
products = [
{"name": "Ridgeline tent", "price": 289, "rating": 4.6},
{"name": "Trail mug", "price": 14, "rating": 4.8},
{"name": "Camp stove", "price": 65, "rating": 4.2},
]
by_rating = sorted(products, key=lambda p: p["rating"], reverse=True)
for p in by_rating:
print(p["name"])
map and filter, and why comprehensions usually win
prices = [289, 14, 65, 120]
with_tax = list(map(lambda p: round(p * 1.08, 2), prices))
under_100 = list(filter(lambda p: p < 100, prices))
print(with_tax)
print(under_100)
Any number of arguments: args and kwargs
def order_total(*prices, discount=0.0):
return round(sum(prices) * (1 - discount), 2)
print(order_total(289, 14))
print(order_total(289, 14, 65, discount=0.1))
def describe(name, **details):
parts = []
for key, value in details.items():
parts.append(f"{key}: {value}")
return name + " (" + ", ".join(parts) + ")"
print(describe("Ridgeline tent", weight="2.1 kg", sleeps=2))
Comprehensions Free
cleaning a lead list
raw_emails = [" Ana@Shop.com", "ben@mail.com ", "", "CARA@site.io", "not-an-email", "ben@mail.com"]
cleaned = [e.strip().lower() for e in raw_emails]
valid = [e for e in cleaned if "@" in e]
unique = sorted(dict.fromkeys(valid))
print(len(raw_emails), "raw ->", len(unique), "usable")
print(unique)
The loop you keep writing
emails = ["Ana@Shop.com", "BEN@mail.com", "cara@site.io"]
clean = []
for e in emails:
clean.append(e.lower())
print(clean)
Filtering with if
campaigns = ["Spring Sale - Search", "Retargeting - Display", "Competitor Terms - Search", "Newsletter Promo - Email"]
search = [name for name in campaigns if name.endswith("Search")]
print(search)
spend = [3120.5, 1890, 6400, 120, 0, 2390.75]
big = [s for s in spend if s >= 2000]
print(big)
Looping over a dictionary
budgets = {"search": 5000, "display": 2000, "video": 8000, "email": 500}
for channel in budgets:
print(channel, budgets[channel])
Dictionary comprehensions
products = ["tee", "hoodie", "mug"]
name_lengths = {p: len(p) for p in products}
print(name_lengths)
Comprehensions with sum, max and len
campaigns = [
{"name": "Spring Sale", "spend": 3120.5, "revenue": 15840},
{"name": "Retargeting", "spend": 1890, "revenue": 5236},
{"name": "Brand Video", "spend": 6400, "revenue": 2870},
]
total_spend = sum([c["spend"] for c in campaigns])
profitable = [c["name"] for c in campaigns if c["revenue"] > c["spend"]]
best_revenue = max([c["revenue"] for c in campaigns])
print(total_spend)
print(profitable)
print(best_revenue)
Where the if goes
prices = [5, 20, 50]
print([p for p in prices if p > 10]) # keep some items
print(["big" if p > 10 else "small" for p in prices]) # choose a value for every item
Sets: unique items and fast lookups Free
two numbers that add up to a target
def pair_for(prices, budget):
seen = set()
for p in prices:
if budget - p in seen:
return (budget - p, p)
seen.add(p)
return None
prices = [45, 170, 12, 39, 88, 61]
print(pair_for(prices, 100)) # 39 + 61
print(pair_for(prices, 1000)) # no pair
From Sets: unique items and fast lookups · run it in the lesson
A collection with no repeats
gear = {"tent", "stove", "lantern"}
print(gear, len(gear))
gear.add("stove") # already there: nothing happens
gear.add("rope")
print(gear, len(gear))
signups = ["ana@ex.org", "bo@ex.org", "ana@ex.org", "cy@ex.org", "bo@ex.org"]
print(len(signups), "sign-ups from", len(set(signups)), "people")
From Sets: unique items and fast lookups · run it in the lesson
Comparing collections
saturday = {"Ana", "Bo", "Cy", "Dee"}
sunday = {"Cy", "Dee", "Eli"}
print("both days:", sorted(saturday & sunday)) # intersection
print("either day:", sorted(saturday | sunday)) # union
print("Saturday only:", sorted(saturday - sunday)) # difference
print("just one day:", sorted(saturday ^ sunday)) # symmetric difference
print({"Cy", "Dee"} <= saturday) # subset: is every one of them on Saturday?
print(saturday.isdisjoint({"Fay"})) # no one in common
From Sets: unique items and fast lookups · run it in the lesson
Reconciling two lists
yesterday = ["TE-101", "ST-114", "la-102 ", "BO-120"]
today = ["TE-101", "LA-102", "PA-111", "ST-114", "TE-101"]
def clean(skus):
return {s.strip().upper() for s in skus}
before, after = clean(yesterday), clean(today)
print("added:", sorted(after - before))
print("removed:", sorted(before - after))
print("kept:", sorted(before & after))
From Sets: unique items and fast lookups · run it in the lesson
Errors and exceptions Free
messy survey scores
answers = ["8", "10", "seven", "", "9", "11", "6"]
def parse_score(text):
score = int(text)
if score < 0 or score > 10:
raise ValueError(f"{score} is outside 0-10")
return score
scores = []
problems = []
for a in answers:
try:
scores.append(parse_score(a))
except ValueError as e:
problems.append(repr(a) + ": " + str(e))
print("Valid scores:", scores)
print("Average:", round(sum(scores) / len(scores), 2))
for p in problems:
print("Skipped", p)
Handling errors with try and except
raw_values = ["120", "95", "n/a", "300", ""]
total = 0
skipped = 0
for raw in raw_values:
try:
total += int(raw)
except ValueError:
skipped += 1
print("Total clicks:", total)
print("Skipped:", skipped)
else and finally
def parse_budget(text):
try:
amount = float(text)
except ValueError:
print("Not a number:", text)
return None
else:
print("Parsed", amount)
return amount
finally:
print("Done checking", repr(text))
parse_budget("2500")
parse_budget("lots")
Don't catch everything
prices = [28, 64, 18]
try:
total = sum(prices)
average = total / len(prise) # typo: should be prices
except Exception:
average = 0
print(average)
Modules and imports Free
a week of orders
import statistics
from collections import Counter
from datetime import date
order_values = [64, 28, 92, 18, 45, 64, 130, 22]
order_sources = ["google", "email", "google", "instagram", "email", "google", "direct", "search"]
first_order = date(2026, 11, 23)
last_order = date(2026, 11, 29)
top_source, top_count = Counter(order_sources).most_common(1)[0]
print("Orders:", len(order_values))
print("Average order value:", round(statistics.mean(order_values), 2))
print("Median order value:", statistics.median(order_values))
print("Top source:", top_source, f"({top_count} orders)")
print("Days covered:", (last_order - first_order).days + 1)
Borrowing code with import
import math
print(math.sqrt(144))
print(math.ceil(4.2))
print(math.floor(4.8))
print(math.pi)
Three ways to import
import statistics
from math import floor, ceil
import datetime as dt
print(statistics.mean([12, 18, 21]))
print(floor(7.9), ceil(7.1))
print(dt.date(2026, 11, 27).strftime("%A"))
A short tour of the standard library
import random
random.seed(7)
subject_lines = ["Last chance", "New arrivals", "Your weekly picks"]
print(random.choice(subject_lines))
print(random.randint(1, 100))
The random module, in full
import random
random.seed(3)
print(random.randint(1, 6))
print(random.choice(["tent", "stove", "lamp"]))
deck = ["A", "K", "Q", "J"]
print(random.shuffle(deck), deck)
print(random.sample(range(1, 50), 3))
Reading the documentation
import textwrap
print([name for name in dir(textwrap) if not name.startswith("_")])
help(textwrap.shorten)
Classes and objects Free
Data and the functions that use it
class Campaign:
def __init__(self, name, spend, revenue):
self.name = name
self.spend = spend
self.revenue = revenue
def roas(self):
return self.revenue / self.spend
spring = Campaign("Spring Sale", 3120.5, 15840)
video = Campaign("Brand Video", 6400, 2870)
print(spring.name, round(spring.roas(), 2))
print(video.name, round(video.roas(), 2))
What self is
class Campaign:
def __init__(self, name, spend, revenue):
self.name = name
self.spend = spend
self.revenue = revenue
def roas(self):
return self.revenue / self.spend
spring = Campaign("Spring Sale", 3120.5, 15840)
print(spring.roas())
print(Campaign.roas(spring))
A readable printout with repr
class Campaign:
def __init__(self, name, spend):
self.name = name
self.spend = spend
def __repr__(self):
return f"Campaign(name='{self.name}', spend={self.spend})"
print(Campaign("Search", 120))
print([Campaign("Search", 120), Campaign("Email", 40)])
Inheritance and class attributes Free
three ways to ship
class Shipping:
name = "Standard"
def cost(self, weight):
return 5 + weight * 0.5
class Express(Shipping):
name = "Express"
def cost(self, weight):
return super().cost(weight) * 3
class Pickup(Shipping):
name = "Store pickup"
def cost(self, weight):
return 0
weight = 4
for option in [Shipping(), Express(), Pickup()]:
print(f"{option.name}: {option.cost(weight)}")
From Inheritance and class attributes · run it in the lesson
The shared-list trap
class Cart:
items = []
def add(self, item):
self.items.append(item)
ana = Cart()
ben = Cart()
ana.add("tent")
print(ben.items)
From Inheritance and class attributes · run it in the lesson
Subclassing classes you didn't write
class OutOfStock(Exception):
pass
def reserve(item, stock):
if stock.get(item, 0) == 0:
raise OutOfStock(f"{item} is sold out")
stock[item] = stock[item] - 1
stock = {"tent": 1}
try:
reserve("tent", stock)
reserve("tent", stock)
except OutOfStock as problem:
print("Sorry:", problem)
From Inheritance and class attributes · run it in the lesson
Dates, times and time zones Free
Dates, datetimes and durations
from datetime import date, datetime, timedelta
launch = date(2025, 3, 14)
print(launch, launch.year, launch.strftime("%A"))
order_placed = datetime(2025, 3, 14, 9, 12)
delivered = order_placed + timedelta(days=3, hours=4)
print(delivered)
print(delivered - order_placed)
print((delivered - order_placed).total_seconds() / 3600, "hours")
print(delivered > order_placed)
Dates as text
from datetime import datetime
placed = datetime(2025, 3, 14, 9, 12)
print(placed.strftime("%B %d, %Y at %I:%M %p"))
print(placed.strftime("%d/%m/%Y"))
typed = datetime.strptime("06/09/2024 14:30", "%m/%d/%Y %H:%M")
print(typed)
print(placed.isoformat())
print(datetime.fromisoformat("2025-03-14T09:12:00"))
Time zones: aware and naive
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
pacific = ZoneInfo("America/Los_Angeles")
mountain = ZoneInfo("America/Denver")
opens = datetime(2025, 3, 14, 10, 0, tzinfo=pacific)
print(opens)
print(opens.astimezone(mountain))
print(opens.astimezone(timezone.utc))
now_utc = datetime.now(timezone.utc)
print(now_utc.tzinfo, now_utc.utcoffset())
When the clocks change
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
pacific = ZoneInfo("America/Los_Angeles")
saturday = datetime(2025, 3, 8, 10, 0, tzinfo=pacific)
same_time_sunday = saturday + timedelta(days=1)
print(same_time_sunday, "wall clock: 10:00 again")
exactly_24h = (saturday.astimezone(timezone.utc) + timedelta(hours=24)).astimezone(pacific)
print(exactly_24h, "24 real hours later")
Storing and sending times
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
stamp = datetime(2025, 3, 14, 17, 12, tzinfo=timezone.utc).isoformat()
print(stamp)
back = datetime.fromisoformat(stamp)
print(back.astimezone(ZoneInfo("America/Denver")).strftime("%a %b %d, %I:%M %p %Z"))
Regular expressions Free
an order log into records
import re
LOG = """2025-03-14 09:12:05 ORDER #50231 store=portland sku=TE-101 qty=1 total=$200.00
2025-03-14 09:13:40 heartbeat ok
2025-03-14 09:20:11 ORDER #50232 store=online sku=FO-120 qty=3 total=$60.00
2025-03-14 09:31:52 ORDER #50233 store=denver sku=PA-109 qty=1 total=$1,095.00"""
LINE = re.compile(r"""
(?P<date>\d{4}-\d{2}-\d{2}) \s (?P<time>\d{2}:\d{2}:\d{2}) \s ORDER \s
\#(?P<order>\d+) \s store=(?P<store>\w+) \s sku=(?P<sku>[A-Z]{2}-\d{3}) \s
qty=(?P<qty>\d+) \s total=\$(?P<total>[\d,]+\.\d{2})
""", re.VERBOSE)
records = []
for line in LOG.splitlines():
m = LINE.fullmatch(line)
if not m:
continue
row = m.groupdict()
row["order"] = int(row["order"])
row["qty"] = int(row["qty"])
row["total"] = float(row["total"].replace(",", ""))
records.append(row)
print(len(records), "orders")
print(records[2])
print(sum(r["total"] for r in records))
A pattern for text
import re
line = "Order 50231: 2 tents, 1 stove, shipped in 3 days"
print(re.search(r"\d+", line).group())
print(re.findall(r"\d+", line))
The building blocks
import re
note = "Swap TE-101 for TE-104, and restock AP-122. Ignore te-999 and ABC-1234."
print(re.findall(r"[A-Z]{2}-\d{3}", note))
print(re.findall(r"\b[A-Z]{2}-\d{3}\b", note))
Groups: pulling out the pieces
import re
line = "2025-03-14 09:12 ORDER #50231 store=portland total=$200.00"
m = re.search(r"#(?P<order>\d+) store=(?P<store>\w+) total=\$(?P<total>[\d.]+)", line)
print(m.group("order"), m.group("store"))
print(m.groupdict())
for found in re.finditer(r"(\w+)=(\S+)", line):
print(found.group(1), "->", found.group(2))
Replace and split
import re
email = "Hi, my order #50231 hasn't arrived. Card ending 4242. Call me on 503-555-0134."
print(re.sub(r"\d{3}-\d{3}-(\d{4})", r"***-***-\1", email))
print(re.sub(r"\s+", " ", "too many\n spaces"))
tags = "hiking; camping ,budget; winter"
print(re.split(r"\s*[;,]\s*", tags))
Whole-string checks, and readable patterns
import re
for code in ["TE-101", "TE-101x", " TE-101", "te-101"]:
print(repr(code), bool(re.fullmatch(r"[A-Z]{2}-\d{3}", code)))
JSON Free
reading a model's structured reply
import json
model_reply = '{"sentiment": "negative", "score": 0.18, "topics": ["shipping", "refund"], "needs_followup": true}'
review = json.loads(model_reply)
if review["needs_followup"]:
print("Flag for the support team:", ", ".join(review["topics"]))
print(f"Sentiment: {review['sentiment']} (score {review['score']})")
From JSON · run it in the lesson
json.loads: from text to Python
import json
text = '{"campaign": "Spring Sale", "active": true, "budget": 5000, "channels": ["search", "email"], "owner": null}'
data = json.loads(text)
print(type(text))
print(type(data))
print(data["campaign"], data["active"], data["owner"])
print(data["channels"][1])
From JSON · run it in the lesson
json.dumps: from Python to text
import json
report = {"campaign": "Spring Sale", "roas": 5.08, "flags": [], "approved": False, "notes": None}
print(json.dumps(report))
print(json.dumps(report, indent=2))
From JSON · run it in the lesson
JSON isn't Python, even when it looks it
import json
data = {"active": True, "coupon": None}
print(str(data))
print(json.dumps(data))
From JSON · run it in the lesson
Files and CSV data Free
from export to answer
import csv
with open("email_export.csv", "w", newline="") as f:
f.write("subject,sends,opens\n")
f.write("Last chance: 30% off,24000,6240\n")
f.write("New arrivals you'll love,22000,4180\n")
f.write("Your weekly picks,23500,3290\n")
best_subject = ""
best_rate = 0
with open("email_export.csv", newline="") as f:
for row in csv.DictReader(f):
rate = int(row["opens"]) / int(row["sends"])
print(f"{rate:.1%} {row['subject']}")
if rate > best_rate:
best_rate = rate
best_subject = row["subject"]
print("Best:", best_subject)
Writing and reading a file
with open("notes.txt", "w") as f:
f.write("Q4 plan\n")
f.write("Raise search budget\n")
f.write("Test new subject lines\n")
with open("notes.txt") as f:
contents = f.read()
print(contents)
One line at a time
with open("subjects.txt", "w") as f:
f.write("Last chance: 30% off\n")
f.write("New arrivals you'll love\n")
f.write("Your weekly picks\n")
with open("subjects.txt") as f:
for line in f:
line = line.strip()
print(len(line), line)
Where is that file?
from pathlib import Path
print(Path.cwd())
Path("data").mkdir(exist_ok=True)
Path("data/orders.csv").write_text("id,total\n1,40\n")
print(Path("data/orders.csv").exists(), Path("data/orders.csv").resolve())
Paths with pathlib
from pathlib import Path
folder = Path("exports")
folder.mkdir(exist_ok=True)
report = folder / "summary.txt"
report.write_text("ROAS up 12% week over week\n")
print(report)
print(report.exists(), report.suffix, report.name)
print(report.read_text())
for p in folder.iterdir():
print("Found:", p.name)
NumPy arrays Free
a week of campaign data
import numpy as np
campaigns = ["Search", "Display", "Email"]
# rows: campaigns. columns: Mon to Sun
spend = np.array([
[450, 470, 430, 460, 480, 300, 280],
[270, 265, 280, 270, 275, 260, 270],
[20, 15, 25, 20, 20, 10, 10],
])
revenue = np.array([
[2300, 2500, 2100, 2400, 2600, 1500, 1300],
[740, 700, 800, 760, 750, 720, 766],
[1000, 900, 1200, 950, 1100, 700, 750],
])
weekly_roas = revenue.sum(axis=1) / spend.sum(axis=1)
daily_revenue = revenue.sum(axis=0)
search_daily_roas = revenue[0] / spend[0]
for name, roas in zip(campaigns, weekly_roas.round(2)):
print(name, roas)
print("Best day (0 = Monday):", daily_revenue.argmax())
print("Days Search beat ROAS 5:", (search_daily_roas > 5).sum())
From NumPy arrays · run it in the lesson
Lists can't do math on themselves
clicks = [5470, 2050, 3800]
print(clicks * 2)
print([c * 2 for c in clicks])
From NumPy arrays · run it in the lesson
Shape and dtype
import numpy as np
spend = np.array([3120.5, 1890, 6400, 120])
table = np.array([
[5470, 301],
[2050, 88],
[3800, 41],
])
print(spend.dtype, spend.shape)
print(table.dtype, table.shape, table.ndim)
From NumPy arrays · run it in the lesson
Indexing, slicing and masks
import numpy as np
table = np.array([
[5470, 301], # clicks, conversions for campaign 0
[2050, 88], # campaign 1
[3800, 41], # campaign 2
])
print(table[0]) # first row
print(table[0, 1]) # row 0, column 1
print(table[:, 0]) # every row, column 0: all the clicks
print(table[1:, 1]) # rows 1 onward, column 1
From NumPy arrays · run it in the lesson
Summaries and axes
import numpy as np
# rows: 3 campaigns. columns: clicks on Mon, Tue, Wed
daily = np.array([
[120, 135, 150],
[80, 60, 70],
[45, 50, 40],
])
print(daily.sum()) # everything
print(daily.sum(axis=0)) # down the rows: one total per day
print(daily.sum(axis=1)) # across the columns: one total per campaign
print(daily.mean(axis=1).round(1))
print(daily.sum(axis=1).argmax())
From NumPy arrays · run it in the lesson
Broadcasting
import numpy as np
prices = np.array([28, 64, 18]) # shape (3,)
print(prices * 1.2) # 1.2 is applied to every element
units = np.array([
[3, 1, 4], # day 1: tee, hoodie, mug
[2, 5, 0], # day 2
]) # shape (2, 3)
print(units * prices) # prices is stretched down both rows
From NumPy arrays · run it in the lesson
DataFrames with pandas Free
which categories grew?
import pandas as pd
catalog = pd.read_csv("https://api.gradient.test/v1/datasets/catalog.csv")
by_category = catalog.groupby("category")[["units_q1", "units_q2"]].sum()
by_category["growth_pct"] = ((by_category["units_q2"] / by_category["units_q1"] - 1) * 100).round(1)
print(by_category.sort_values("growth_pct", ascending=False))
A spreadsheet you can program
import pandas as pd
df = pd.DataFrame({
"name": ["Ridgeline tent", "Trail mug", "Camp stove", "Rain jacket"],
"category": ["tents", "camp kitchen", "camp kitchen", "apparel"],
"price": [289, 14, 65, 120],
"rating": [4.6, 4.8, 4.2, 4.5],
})
print(df)
print(df.shape)
print(list(df.columns))
Loading a CSV
import pandas as pd
with open("stock.csv", "w") as f:
f.write("sku,item,stock,price\nTE-101,1-person tent,14,195\nPA-108,daypack 18L,0,55\nAP-122,rain jacket,31,120\n")
df = pd.read_csv("stock.csv")
print(df.head())
print(df.describe())
Columns and rows
import pandas as pd
catalog = pd.read_csv("https://api.gradient.test/v1/datasets/catalog.csv")
cheap = catalog["price"] < 60
print(cheap.head(4).tolist())
print(catalog[cheap][["name", "price"]])
popular_tents = catalog[(catalog["category"] == "tents") & (catalog["rating"] >= 4.4)]
print(len(popular_tents), "well-rated tents")
New columns, sorting and the top few
import pandas as pd
catalog = pd.read_csv("https://api.gradient.test/v1/datasets/catalog.csv")
catalog["margin"] = catalog["price"] - catalog["cost"]
catalog["revenue_q2"] = catalog["price"] * catalog["units_q2"]
print(catalog.sort_values("margin", ascending=False)[["name", "margin"]].head(3))
print(catalog.nlargest(3, "revenue_q2")[["sku", "revenue_q2"]])
best = catalog["margin"].idxmax()
print(catalog.loc[best, "name"])
Counting and grouping
import pandas as pd
catalog = pd.read_csv("https://api.gradient.test/v1/datasets/catalog.csv")
print(catalog["category"].value_counts())
print(catalog.groupby("category")["price"].mean().round(1))
Missing values, and reading one cell
import pandas as pd
catalog = pd.read_csv("https://api.gradient.test/v1/datasets/catalog.csv")
print(catalog["rating"].isna().sum(), "products have no rating")
print(catalog[catalog["rating"].isna()][["sku", "name", "reviews"]])
rated = catalog.dropna(subset=["rating"])
print(len(rated), "rated products")
print(catalog.loc[0, "name"]) # row label 0, column "name"
print(catalog.iloc[-1]["sku"]) # the last row, by position
Charts with Matplotlib Free
a chart ready to share
import matplotlib.pyplot as plt
import pandas as pd
daily = pd.read_csv("https://api.gradient.test/v1/datasets/shoe-orders.csv")
daily["month"] = daily["date"].str[:7]
monthly = daily.groupby("month")["orders"].agg(["sum", "count"])
complete = monthly[monthly["count"] >= 28]
recent = complete.loc["2024-01":]
fig, ax = plt.subplots(figsize=(8, 3.5))
ax.plot(recent.index, recent["sum"], marker="o", label="orders")
ax.set_ylim(bottom=0)
ax.set_title("Trail-shoe orders peak every May to August")
ax.set_ylabel("orders per month")
ax.tick_params(axis="x", labelrotation=90)
ax.spines[["top", "right"]].set_visible(False)
fig.text(0.01, -0.02, "Source: practice shop online orders, complete months only.", fontsize=8)
print(recent["sum"].idxmax(), recent["sum"].max())
A figure and its axes
import matplotlib.pyplot as plt
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
orders = [405, 416, 579, 735, 846, 848]
fig, ax = plt.subplots(figsize=(6, 3.5))
ax.plot(months, orders, marker="o")
ax.set_title("Trail-shoe orders, first half of 2024")
ax.set_ylabel("orders")
ax.set_ylim(bottom=0)
Scales and baselines
import matplotlib.pyplot as plt
sellers = ["A", "B", "C", "D", "E", "F"]
units = [12, 48, 150, 900, 2400, 31000]
fig, (left, right) = plt.subplots(1, 2, figsize=(10, 3.2))
left.bar(sellers, units)
left.set_title("Linear: most bars vanish")
right.bar(sellers, units)
right.set_yscale("log")
right.set_title("Log scale: every seller visible")
Box plots: comparing spreads
import matplotlib.pyplot as plt
import pandas as pd
catalog = pd.read_csv("https://api.gradient.test/v1/datasets/catalog.csv")
names = sorted(catalog["category"].unique())
groups = [catalog[catalog["category"] == c]["price"] for c in names]
fig, ax = plt.subplots(figsize=(7, 3.5))
ax.boxplot(groups, tick_labels=names)
ax.set_title("Price by category")
ax.set_ylabel("price ($)")
Project: images are arrays Free
a contact sheet
import matplotlib.pyplot as plt
import numpy as np
def make_scene(h=120, w=160):
img = np.zeros((h, w, 3), dtype=np.uint8)
rows = np.arange(h)[:, None]
cols = np.arange(w)[None, :]
img[:, :] = (150, 200, 235)
img[h * 2 // 3:, :] = (70, 130, 60)
img[(rows - 25) ** 2 + (cols - 130) ** 2 <= 12 ** 2] = (250, 215, 80)
img[(rows >= 30) & (rows <= 100) & (abs(cols - 70) <= (rows - 30) * 0.8)] = (230, 120, 40)
return img
scene = make_scene()
gray = np.round(scene @ [0.299, 0.587, 0.114]).astype(np.uint8)
gray3 = np.stack([gray, gray, gray], axis=2)
dark = np.clip(scene.astype(int) - 70, 0, 255).astype(np.uint8)
mirrored = scene[:, ::-1]
top = np.hstack([scene, gray3])
bottom = np.hstack([dark, mirrored])
sheet = np.vstack([top, bottom])
print(sheet.shape)
fig, ax = plt.subplots(figsize=(6, 4.5))
ax.imshow(sheet)
ax.axis("off")
A picture is a grid of numbers
import matplotlib.pyplot as plt
import numpy as np
tiny = np.array([
[[255, 0, 0], [0, 255, 0], [0, 0, 255]],
[[255, 255, 255], [128, 128, 128], [0, 0, 0]],
], dtype=np.uint8)
print(tiny.shape, tiny.dtype)
print(tiny[0, 2]) # row 0, column 2: the blue pixel
fig, ax = plt.subplots(figsize=(3, 2))
ax.imshow(tiny)
ax.axis("off")
Flip, crop and gray
import numpy as np
img = np.arange(24, dtype=np.uint8).reshape(2, 4, 3) # a 2 x 4 image
print(img[::-1].shape) # upside down: rows reversed
print(img[:, ::-1].shape) # mirrored: columns reversed
print(img[:, 1:3].shape) # a crop: columns 1 and 2
print(img[:, :, 0]) # the red channel alone: a 2 x 4 grid
Brighter without wrapping around
import numpy as np
pixels = np.array([250, 100, 5], dtype=np.uint8)
print(pixels + 10) # wraps: 250 becomes 4
safe = np.clip(pixels.astype(int) + 10, 0, 255).astype(np.uint8)
print(safe) # stops at 255
print(np.clip(pixels.astype(int) - 20, 0, 255).astype(np.uint8)) # darker stops at 0
A color palette
import numpy as np
img = np.zeros((4, 4, 3), dtype=np.uint8)
img[:, :] = (150, 200, 235)
img[3, :] = (70, 130, 60)
img[0, 0] = (250, 215, 80)
pixels = img.reshape(-1, 3) # 16 rows of (r, g, b)
colors, counts = np.unique(pixels, axis=0, return_counts=True)
order = np.argsort(counts)[::-1] # most common first
for i in order:
print(tuple(int(v) for v in colors[i]), counts[i])
The terminal Free
Show which folder you're in
pwd
From The terminal · run it in the lesson
List what's here, hidden files included
ls -a
From The terminal · run it in the lesson
List a folder in long format, with sizes and dates
ls -l data
From The terminal · run it in the lesson
Print a file's contents
cat notes.txt
From The terminal · run it in the lesson
Move into a folder, then back up to its parent
cd data
pwd
cd ..
pwd
From The terminal · run it in the lesson
Make a folder and an empty file inside it
mkdir output
touch output/summary.txt
ls output
From The terminal · run it in the lesson
Copy a file, then rename the copy
cp notes.txt notes-draft.txt
mv notes-draft.txt notes-v2.txt
ls
From The terminal · run it in the lesson
Add a line to the end of a file
echo "- Ask finance about the email spend" >> notes.txt
cat notes.txt
From The terminal · run it in the lesson
Run a Python script from the terminal
python3 report.py
From The terminal · run it in the lesson
Check which Python runs when you type python3
python3 --version
which python3
From The terminal · run it in the lesson
Delete a file you no longer need
touch scratch.txt
ls
rm scratch.txt
ls
From The terminal · run it in the lesson
Jump home, then straight back to the project
cd ~
pwd
cd ~/projects/campaign-report
pwd
From The terminal · run it in the lesson
Version control with git Free
Start tracking a folder with git
git init
git status
Stage one file and see what's staged
git add index.html
git status
Make the first commit and read the log
git add .
git commit -m "Add landing page"
git log --oneline
See exactly what changed in a file
echo "a:hover { text-decoration: underline; }" >> styles.css
git diff
Throw away an edit you don't want
echo "oops" >> styles.css
git restore styles.css
git status
Try an idea on a branch
git switch -c new-headline
git branch
Go back to main
git switch main
git branch
Keep files out of git with .gitignore
echo ".env" > .gitignore
touch .env
git status
Read a commit's details
git log
Packages and environments Free
Check which Python you have
python3 --version
which python3
Create a virtual environment for this project
python3 -m venv .venv
ls -a
Activate it, and check that python now points inside it
source .venv/bin/activate
which python
Install a package into the environment
pip install requests
pip list
Install everything a project's requirements.txt lists
cat requirements.txt
pip install -r requirements.txt
pip list
Pin what's installed, so others can reproduce it
pip freeze
See what happens when pip runs outside an environment
pip3 install requests
Leave the environment
deactivate
which python3
Run the project's script inside its environment
python3 analyze.py
Keep .venv out of git
echo ".venv/" >> .gitignore
cat .gitignore
Python on your own computer Free
what the self-check checks
import hashlib
import platform
import sys
version = platform.python_version()
in_venv = sys.prefix != sys.base_prefix
passed = True # here we pretend pytest passed
fields = f"python={version} venv={'yes' if in_venv else 'no'} tests={'passed' if passed else 'failed'}"
code = hashlib.sha256(fields.encode()).hexdigest()[:8]
print(f"SETUP-CHECK {fields} code={code}")
typo = fields.replace("venv=no", "venv=yes")
print("changing one field changes the code:", hashlib.sha256(typo.encode()).hexdigest()[:8])
Where the academy's Python runs
import platform
import sys
print("Python", platform.python_version())
print("running on:", sys.platform)
print("in a virtual environment:", sys.prefix != sys.base_prefix)
How APIs work Free
building a request body
import json
new_campaign = {
"name": "Summer Launch - Social",
"budget": 1500,
"channel": "social",
}
body = json.dumps(new_campaign)
print(body)
print(type(body))
Headers and bodies
import json
body = '{"data": [{"id": "cmp_101", "name": "Spring Sale - Search", "clicks": 5470}, {"id": "cmp_104", "name": "Newsletter Promo - Email", "clicks": 1920}], "count": 2}'
result = json.loads(body)
print(type(result))
print(result["count"])
for campaign in result["data"]:
print(campaign["name"], campaign["clicks"])
Calling APIs from Python Free
spend on active search campaigns
import requests
def active_channel_spend(channel):
response = requests.get(
"https://api.gradient.test/v1/campaigns",
params={"status": "active", "channel": channel},
timeout=10,
)
response.raise_for_status()
total = 0
for campaign in response.json()["data"]:
total += campaign["spend"]
return total
try:
print("Search spend:", active_channel_spend("search"))
print("Email spend:", active_channel_spend("email"))
except requests.exceptions.RequestException as error:
print("Request failed:", error)
The requests library
import requests
response = requests.get("https://api.gradient.test/v1/campaigns")
print(response.status_code)
print(response.headers["content-type"])
data = response.json()
print(data["count"], "campaigns")
Query parameters with params=
import requests
response = requests.get(
"https://api.gradient.test/v1/campaigns",
params={"status": "active", "channel": "search"},
)
print(response.url)
for campaign in response.json()["data"]:
print(campaign["name"], "-", campaign["spend"])
Sending data with POST
import requests
new_campaign = {"name": "Summer Launch - Social", "budget": 1500, "channel": "social"}
response = requests.post("https://api.gradient.test/v1/campaigns", json=new_campaign)
print(response.status_code)
created = response.json()
print(created["id"], created["status"])
When things go wrong
import requests
try:
response = requests.get("https://api.gradient.test/v1/campaigns/cmp_999", timeout=10)
response.raise_for_status()
print(response.json()["name"])
except requests.exceptions.HTTPError:
print("The API refused:", response.status_code, response.json()["error"]["message"])
except requests.exceptions.RequestException as error:
print("Couldn't complete the request:", error)
API keys and secrets Free
a script that never contains the key
import os
import requests
os.environ["GRADIENT_API_KEY"] = "gk_demo_7Hq2_not_a_real_secret" # stands in for your .env file
def get_report():
key = os.environ.get("GRADIENT_API_KEY")
if key is None:
raise RuntimeError("GRADIENT_API_KEY isn't set. Copy .env.example to .env and fill it in.")
response = requests.get(
"https://api.gradient.test/v1/report",
headers={"X-Api-Key": key},
timeout=10,
)
response.raise_for_status()
return response.json()
report = get_report()
print("Status: OK")
print("Report fields:", sorted(report))
Sending a key
import requests
url = "https://api.gradient.test/v1/report"
no_key = requests.get(url)
print(no_key.status_code, no_key.json()["error"]["message"])
wrong_key = requests.get(url, headers={"X-Api-Key": "guess-123"})
print(wrong_key.status_code, wrong_key.json()["error"]["message"])
right_key = requests.get(url, headers={"X-Api-Key": "gk_demo_7Hq2_not_a_real_secret"})
print(right_key.status_code, right_key.json())
Environment variables
import os
os.environ["GRADIENT_API_KEY"] = "gk_demo_7Hq2_not_a_real_secret" # stands in for: export GRADIENT_API_KEY=...
key = os.environ.get("GRADIENT_API_KEY")
print("Found a key:", key is not None)
missing = os.environ.get("SOME_OTHER_KEY")
print(missing)
Sending email and text messages Free
a rain check before Saturday's hike
import os
import requests
SMS_KEY = os.environ.get("SMS_KEY", "sk_demo_sms_42_not_a_real_secret")
RAINY = {3, 4, 5} # drizzle, rain, thunderstorm in the practice API's codes
data = requests.get("https://api.gradient.test/v1/forecast", params={"trailhead": "fern-hollow"}).json()
hike = [h for h in data["hours"] if h["time"].startswith("2025-06-14T") and "08" <= h["time"][11:13] <= "11"]
wet = [h for h in hike if h["code"] in RAINY or h["precip_chance"] >= 60]
print(len(hike), "hike hours,", len(wet), "look wet")
if wet:
first = wet[0]["time"][11:16]
text = f"Rain check: {data['name']} forecast shows {data['codes'][str(wet[0]['code'])]} from {first}. Move Saturday's hike?"
reply = requests.post("https://api.gradient.test/v1/sms", headers={"Authorization": f"Bearer {SMS_KEY}"},
json={"to": "+15035550188", "body": text})
print(reply.status_code, len(text), "characters")
An email is a message object
from email.message import EmailMessage
msg = EmailMessage()
msg["From"] = "Trail Club <club@shop.example.com>"
msg["To"] = "ada@example.com"
msg["Subject"] = "Your order has shipped"
msg.set_content("Hi Ada,\n\nYour Ridgeline tent is on its way. It should arrive by Friday.\n\nThe shop team")
print(msg["Subject"])
print(msg.as_string()[:300])
Web pages: HTML and CSS Free
Escaping: text that looks like HTML
import html
name = 'Tent <XL> & "Pro"'
print(html.escape(name))
print(f"<td>{html.escape(name)}</td>")
print(html.unescape("Trail & Tor's "best" <3"))
Scraping web pages Free
structured data beats the layout
import json
import requests
from bs4 import BeautifulSoup
page = requests.get("https://api.gradient.test/shop/products/TE-101").text
soup = BeautifulSoup(page, "html.parser")
block = soup.find("script", type="application/ld+json")
data = json.loads(block.string)
print(data["@type"], "|", data["name"])
print("price:", data["offers"]["price"], data["offers"]["priceCurrency"])
print("rating:", data["aggregateRating"]["ratingValue"], "from", data["aggregateRating"]["reviewCount"], "reviews")
shown = soup.select_one(".product .now").get_text()
print("the page shows", shown, "and the data says", data["offers"]["price"])
Fetch and parse
import requests
from bs4 import BeautifulSoup
reply = requests.get("https://api.gradient.test/shop/deals", params={"page": 1})
print(reply.status_code, reply.headers["content-type"])
soup = BeautifulSoup(reply.text, "html.parser")
print(soup.title.get_text())
cards = soup.select("article.deal")
print(len(cards), "cards")
first = cards[0]
print(first.prettify()[:420])
Following pages, politely
import time
from urllib import robotparser
robots = robotparser.RobotFileParser("https://api.gradient.test/robots.txt")
robots.read()
for path in ["/shop/deals?page=2", "/shop/products/TE-101", "/shop/cart?add=TE-101", "/shop/account/"]:
print(path, robots.can_fetch("*", "https://api.gradient.test" + path))
print("wait between requests:", robots.crawl_delay("*"), "second(s)")
Your first LLM call Free
a reusable ask() function
import requests
def ask(question, system="You write short email subject lines for an outdoor clothing brand.", max_tokens=1000):
response = requests.post(
"https://api.anthropic.com/v1/messages",
headers={"x-api-key": "added-by-the-academy-server", "anthropic-version": "2026-10-21"},
json={
"model": "claude-opus-5-5",
"max_tokens": max_tokens,
"system": system,
"messages": [{"role": "user", "content": question}],
},
timeout=60,
)
response.raise_for_status()
data = response.json()
if data["stop_reason"] == "max_tokens":
raise ValueError("The reply was cut off. Raise max_tokens.")
text = "".join(block["text"] for block in data["content"] if block["type"] == "text")
return text, data["usage"]
for product in ["rain jackets", "trail shoes"]:
text, usage = ask(f"Write one subject line for our {product} sale.")
print(product, "->", text[:80])
print(" tokens in/out:", usage["input_tokens"], usage["output_tokens"])
Capstone: ad-copy reviewer Free
Step 1: load the data
import csv
with open("subjects.csv", "w") as f:
f.write("""subject,audience
Last chance: spring sale ends tonight,customers
Your March report is ready,partners
""")
with open("subjects.csv") as f:
rows = list(csv.DictReader(f))
print(len(rows))
print(rows[0])
print(rows[1]["subject"])
Step 2: rules as functions that return problems
SPAMMY = ["free", "winner", "$$$"]
def check_subject(subject):
problems = [f"spammy word: {word}" for word in SPAMMY if word in subject.lower()]
if len(subject) > 50:
problems.append(f"too long ({len(subject)} characters)")
return problems
print(check_subject("Your March report is ready"))
print(check_subject("FREE gift for our WINNER"))
print(len(check_subject("FREE gift for our WINNER")))
Step 3: a report other tools can read
import json
results = [
{"subject": "Your March report is ready", "problems": [], "ok": True},
{"subject": "FREE gift for our WINNER", "problems": ["spammy word: free", "spammy word: winner"], "ok": False},
]
with open("subject_report.json", "w") as f:
json.dump(results, f, indent=2)
with open("subject_report.json") as f:
print(f.read())
Step 4: one model call, used carefully
results = [
{"subject": "Report ready", "problems": []},
{"subject": "FREE gift for our WINNER", "problems": ["free", "winner"]},
{"subject": "Free shipping", "problems": ["free"]},
]
worst = results[0]
for r in results:
if len(r["problems"]) > len(worst["problems"]):
worst = r
print(worst["subject"])
What an LLM actually does Free
The top choice doesn't always win
import random
options = ["Sunday", "soon", "tomorrow", "March"]
weights = [0.31, 0.22, 0.18, 0.09]
for attempt in range(5):
pick = random.choices(options, weights=weights)[0]
print("Our spring sale ends", pick)
Reading math notation as code Free
Σ means "add them all up"
x = [4, 1, 5]
total = 0
for i in range(len(x)):
total += x[i]
print(total, sum(x))
Subscripts are positions
x = [10, 20, 30]
print(x[0]) # x₁ on paper: the first value
W = [[1, 2], [3, 4]]
print(W[1][0]) # W₂₁ on paper: row 2, column 1
Hats, bars, stars and Greek letters
y = [3, 5, 2]
y_hat = [2.5, 5, 3] # ŷ: the model's predictions
y_bar = sum(y) / len(y) # ȳ: the mean of y, written with a bar
print(y_bar)
print([y[i] - y_hat[i] for i in range(len(y))]) # yᵢ − ŷᵢ, each error
Functions and arrows
def f(x):
return 2 * x + 1
print(f(3))
scores = {"cat": 0.2, "dog": 0.7, "bird": 0.1}
print(max(scores, key=scores.get)) # argmax: the class, not the score
Reading a whole formula
import math
z = [2.0, 1.0, 0.1]
total = sum(math.exp(v) for v in z)
print([round(math.exp(v) / total, 3) for v in z])
Vectors Free
three customers as points
import numpy as np
ana = np.array([2, 40.0])
ben = np.array([3, 45.0])
cai = np.array([20, 15.0])
print("ana to ben:", round(float(np.linalg.norm(ana - ben)), 1))
print("ana to cai:", round(float(np.linalg.norm(ana - cai)), 1))
print("ben to cai:", round(float(np.linalg.norm(ben - cai)), 1))
From Vectors · run it in the lesson
Vectors in Python
import numpy as np
v = np.array([3, 1])
print(v, v.shape, v.ndim)
print("v1 =", v[0], " v2 =", v[1])
customer = np.array([12, 64.50, 9]) # orders, average order value, days since last order
print(customer.shape)
From Vectors · run it in the lesson
Turning records into vectors
import numpy as np
import requests
FEATURES = ["spend", "clicks", "conversions"]
campaigns = requests.get("https://api.gradient.test/v1/campaigns").json()["data"]
vectors = {c["id"]: np.array([c[f] for f in FEATURES], dtype=float) for c in campaigns}
for cid, v in list(vectors.items())[:3]:
print(cid, v)
From Vectors · run it in the lesson
What machine learning is Free
learning a cutoff by hand
days = [10, 25, 40, 70, 105, 130]
churned = [0, 0, 0, 1, 0, 1]
for cutoff in [30, 60, 100]:
right = 0
for d, c in zip(days, churned):
guess = 1 if d > cutoff else 0
if guess == c:
right += 1
print(cutoff, right, "of", len(days))
print("always 'stays':", churned.count(0), "of", len(days))
The artificial neuron Free
one neuron by hand
import math
x = [1, 3] # opened the last email (1 = yes), days since last visit
w = [2.0, -0.5] # weights: how much each input matters
b = -1.0 # bias: the starting point
z = sum(wi * xi for wi, xi in zip(w, x)) + b # weighted sum plus bias
a = 1 / (1 + math.exp(-z)) # sigmoid activation
print(z, round(a, 3))
The same neuron in NumPy
import numpy as np
def neuron(x, w, b):
z = np.dot(w, x) + b
return 1 / (1 + np.exp(-z)) # sigmoid activation
# A "will this customer churn?" neuron: inputs are days since last order (in 100s) and orders per year
w = np.array([1.8, -0.6])
b = -1.0
for x in [[0.2, 9], [1.5, 2], [3.0, 1]]:
print(f"inputs {x} → output {neuron(np.array(x), w, b):.3f}")
A neuron draws a straight line
w1, w2, b = 1.0, 1.0, -1.5 # a hand-picked neuron
for x1, x2 in [(0, 0), (0, 1), (1, 0), (1, 1)]:
z = w1 * x1 + w2 * x2 + b
print((x1, x2), z, "yes" if z > 0 else "no")
Language modeling Free
score a tiny model
import math
# chances the model gave each real next character of "tent"
probs = [0.1, 0.5, 0.4, 0.8]
p_text = math.prod(probs)
loss = sum(-math.log(p) for p in probs) / len(probs)
print(round(p_text, 4))
print(round(loss, 3), round(math.exp(loss), 2))
The chance of a whole text
probs = [0.2, 0.5, 0.9] # P("c"), P("a" | "c"), P("t" | "ca")
p_text = 1
for p in probs:
p_text *= p
print(round(p_text, 3))
Scoring a model: the loss
import math
probs = [0.5, 0.4, 0.1] # chance given to each token that really came next
losses = [-math.log(p) for p in probs]
loss = sum(losses) / len(losses)
print([round(x, 3) for x in losses])
print(round(loss, 3), round(math.exp(loss), 2))
Perplexity: the loss as a number of choices
import math
V = 6 # a fair die: six equally likely faces
loss = -math.log(1 / V) # every guess gives the right face 1/6
print(round(loss, 3), round(math.exp(loss), 3))
The course text, and two simple baselines
from collections import Counter
train = "banana"
vocab = ["a", "b", "n", "x"]
counts = Counter(train)
probs = {c: (counts[c] + 1) / (len(train) + len(vocab)) for c in vocab}
print(probs)
What a unigram model can't see
import math
import random
from collections import Counter
import requests
text = requests.get("https://api.gradient.test/v1/corpus").json()["text"]
counts, V = Counter(text), len(set(text))
logp = lambda s: sum(math.log((counts[c] + 1) / (len(text) + V)) for c in s)
sentence = "It is light and packable."
scrambled = "".join(random.Random(1).sample(sentence, len(sentence)))
for s in [sentence, scrambled]:
print(f"{s!r:<30} log probability {logp(s):.2f}")
Base models vs assistants Free
how much gets resent
questions = [10, 12, 8] # words in each of your messages
replies = [20, 25, 30] # words in each of the assistant's replies
history = 0 # words in the conversation so far
total = 0 # words the model has read across all calls
for call, (q, r) in enumerate(zip(questions, replies), start=1):
history += q # your new message joins the conversation
print(f"call {call}: the model reads {history} words")
total += history
history += r # its reply joins the conversation too
print("you typed:", sum(questions), "words")
print("the model read:", total, "words")
How computers run code Free
Count what the interpreter does
import dis
def one_step(x):
return x + 1
def three_steps(x):
y = x * 2
y = y + 1
return y - 3
for f in (one_step, three_steps):
count = len(list(dis.get_instructions(f)))
print(f.__name__, count, "instructions")
Everything is numbers
print(ord("A"), ord("a"), ord("€"))
print(bin(65)) # 65 written in base 2: the bits themselves
data = "café".encode("utf-8")
print(data, len(data)) # é needs two bytes in UTF-8
print(data.decode("utf-8")) # and back again
Memory: a long row of numbered boxes
a = [4, 9]
b = a # b points at the SAME list, no copy is made
b.append(12)
print(a) # a changed too
print(id(a) == id(b))
c = list(a) # list() builds a new list with the same items
c.append(99)
print(a, c)
The processor: fetch, decode, execute
import dis
def total(price, qty):
return price * qty + 5
for ins in dis.get_instructions(total):
print(ins.opname, ins.argrepr)
HTTP from the wire up Free
Build the bytes yourself
import json
body = json.dumps({"name": "Spring", "budget": 500}).encode("utf-8")
head = (
"POST /v1/campaigns?dry_run=1 HTTP/1.1\r\n"
"Host: api.gradient.test\r\n"
"Content-Type: application/json\r\n"
f"Content-Length: {len(body)}\r\n"
"\r\n"
)
raw = head.encode("ascii") + body
print(raw)
print(len(body), "body bytes;", len(raw), "bytes in total")
URLs and query strings
from urllib.parse import urlsplit, urlencode, parse_qs, quote
url = "https://api.gradient.test/v1/products?category=home&q=caf%C3%A9#reviews"
parts = urlsplit(url)
print(parts.scheme, parts.hostname, parts.path, parts.fragment)
print(parse_qs(parts.query))
print(urlencode({"q": "tents & tarps", "max_price": 200}))
print(quote("reports/Q3 final.pdf"))
Details that cause real bugs
text = "café"
print(len(text), len(text.encode()))
JSON-RPC 2.0 in depth Free
Which error code?
import json
METHODS = {"tools/list": {"params": []}, "tools/call": {"params": ["name"]}}
def check(raw):
try:
msg = json.loads(raw)
except ValueError:
return -32700, "parse error"
if not isinstance(msg, dict) or msg.get("jsonrpc") != "2.0" or "method" not in msg:
return -32600, "invalid request"
if msg["method"] not in METHODS:
return -32601, "method not found"
for needed in METHODS[msg["method"]]["params"]:
if needed not in msg.get("params", {}):
return -32602, "invalid params"
return 0, "ok"
for raw in ['{"jsonrpc": ',
'{"id": 1, "method": "tools/list"}',
'{"jsonrpc": "2.0", "id": 2, "method": "tools/delete"}',
'{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {}}',
'{"jsonrpc": "2.0", "id": 4, "method": "tools/list"}']:
print(check(raw))
One message in, at most one out
import json
def handle(raw, methods):
try:
msg = json.loads(raw)
except json.JSONDecodeError:
return {"jsonrpc": "2.0", "id": None, "error": {"code": -32700, "message": "Parse error"}}
if "id" not in msg:
if msg.get("method") in methods:
methods[msg["method"]](msg.get("params", {}))
return None # notifications get no reply
fn = methods.get(msg.get("method"))
if fn is None:
return {"jsonrpc": "2.0", "id": msg["id"], "error": {"code": -32601, "message": f"Method not found: {msg.get('method')}"}}
return {"jsonrpc": "2.0", "id": msg["id"], "result": fn(msg.get("params", {}))}
seen = []
methods = {
"echo": lambda p: {"you_said": p.get("text")},
"notifications/log": lambda p: seen.append(p["line"]),
}
for raw in ['{"jsonrpc": "2.0", "id": 1, "method": "echo", "params": {"text": "hi"}}',
'{"jsonrpc": "2.0", "method": "notifications/log", "params": {"line": "started"}}',
'{"jsonrpc": "2.0", "id": 2, "method": "missing"}',
'{"jsonrpc": "2.0", "id": 3, "method": ']:
print(handle(raw, methods))
print("logged:", seen)
System design fundamentals Free
A back-of-the-envelope estimate
users = 200_000
requests_each = 25
peak_factor = 3
per_server = 500 # requests a second one server handles
order_size_kb = 2
orders_a_day = 8_000
average = users * requests_each / 86_400
peak = average * peak_factor
servers = int(peak // per_server) + 1 + 1 # round up, plus one spare
print("average requests a second:", round(average))
print("peak requests a second:", round(peak))
print("servers:", servers)
print("order storage a year (GB):", round(orders_a_day * order_size_kb * 365 / 1_000_000, 1))
What a cache does to the database
reads = 2000 # reads a second
for hit_rate in [0, 0.5, 0.9, 0.99]:
to_database = reads * (1 - hit_rate)
print(f"hit rate {hit_rate:>4.0%}: {to_database:>6.0f} reads a second reach the database")
Why modulo sharding hurts
import hashlib
def h(text):
return int(hashlib.sha256(text.encode()).hexdigest()[:8], 16)
keys = [f"user-{i}" for i in range(1000)]
before = {k: h(k) % 4 for k in keys}
after = {k: h(k) % 5 for k in keys}
moved = sum(before[k] != after[k] for k in keys)
print(f"{moved} of 1000 keys moved")
Every lesson has its own recipes
These are from the 55 free lessons. A free account opens all 345 lessons, each ending in a gallery like this, with the previews run live.