Mathew K Analytics

Lesson 2 · Python Beginner Course

Python Essentials: Think Like a Programmer (Beginner Python Course, Part 2)

In this lesson you'll be able to: Slice, search and reshape text with string methods Do maths without surprises, and convert between types safely Write…

⬇ Download notebook

📓 Full notebook

Download .ipynb

Python Essentials: Think Like a Programmer#

In this lesson you'll be able to:

  • Slice, search and reshape text with string methods
  • Do maths without surprises, and convert between types safely
  • Write sharper decisions and loops with break, continue, enumerate and zip
  • Package your logic into reusable functions with defaults and return values
  • Borrow ready-made tools from the math and random modules
  • Crack problems with a four-step method: understand, plan, code, test Projects we'll build: a name badge maker, a road-trip fuel splitter, a step-count report, and a Password Strength Coach

Warm-Up: A Two-Minute Refresher#

Print, variables, if and loops: the four tools you already own.

city = "Lisbon"
temperature = 24
print(city, "is", temperature, "degrees today")
Lisbon is 24 degrees today
if temperature > 20:
    print("T-shirt weather")
else:
    print("Bring a jacket")
T-shirt weather
for day in range(1, 4):
    print(f"Day {day}: water the plants")

battery = 30
while battery > 0:
    print(f"Battery at {battery}%")
    battery -= 10
Day 1: water the plants
Day 2: water the plants
Day 3: water the plants
Battery at 30%
Battery at 20%
Battery at 10%

Strings: Python's Text Superpowers#

A string is a row of numbered lockers: one character per locker, and the numbering starts at zero.

print("She said \"hi\" and left.")
print('It\'s raining')
print("Line one\nLine two")
print("Name:\tPriya")
print("Saved in C:\\photos")
She said "hi" and left.
It's raining
Line one
Line two
Name:	Priya
Saved in C:\photos
poem = """Roses need water,
code needs a test,
run it and read it,
then fix all the rest."""
print(poem)
Roses need water,
code needs a test,
run it and read it,
then fix all the rest.
first = "snow"
print(first + "ball")
print("ha" * 3)
print("=" * 20)
snowball
hahaha
====================
word = "PYTHON"
print(word[0])
print(word[2])
print(word[-1])
print(word[-2])
print(len(word))
P
T
N
O
6

Common mistake: asking for a locker that doesn't exist.

print(word[6])
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[9], line 1
----> 1 print(word[6])

IndexError: string index out of range
print(word[len(word) - 1])
print(word[-1])
N
N
ticket = "SKY-2041-BLUE"
print(ticket[0:3])
print(ticket[4:8])
print(ticket[9:])
print(ticket[:3])
print(ticket[::-1])
SKY
2041
BLUE
SKY
EULB-1402-YKS
print(ticket[4:100])
print(ticket[50:60] == "")
2041-BLUE
True

String Methods: Clean, Search and Reshape Text#

A method is a function that belongs to a value: value.method()

messy = "   hello WORLD   "
print(f"[{messy.upper()}]")
print(f"[{messy.lower()}]")
print(f"[{messy.strip()}]")
print(f"[{messy.strip().title()}]")
[   HELLO WORLD   ]
[   hello world   ]
[hello WORLD]
[Hello World]
note = "I love tea. Tea is life. More tea please."
print(note.replace("tea", "coffee"))
print(note.lower().replace("tea", "coffee"))
print(note.lower().count("tea"))
I love coffee. Tea is life. More coffee please.
i love coffee. coffee is life. more coffee please.
3
groceries = "eggs,milk,bread,apples"
items = groceries.split(",")
print(items)
print(len(items), "items")
print(" | ".join(items))
print("Learn to code today".split())
['eggs', 'milk', 'bread', 'apples']
4 items
eggs | milk | bread | apples
['Learn', 'to', 'code', 'today']
filename = "holiday_photo_2024.jpg"
print(filename.find("photo"))
print(filename.find("video"))
print(filename.startswith("holiday"))
print(filename.endswith(".jpg"))
print("2024" in filename)
8
-1
True
True
True
phrase = "Programming is a superpower"
vowels = 0
for char in phrase.lower():
    if char in "aeiou":
        vowels += 1
print(f"{vowels} vowels in {len(phrase)} characters")
9 vowels in 27 characters

Common mistake: trying to change a string in place. Strings are immutable: they never change.

name = "kate"
name[0] = "K"
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[18], line 2
      1 name = "kate"
----> 2 name[0] = "K"

TypeError: 'str' object does not support item assignment
name = "K" + name[1:]
print(name)
shout = name.upper()
print(name, shout)
Kate
Kate KATE

Pro tip: a method that seems to do nothing usually worked fine. You forgot to save its result: text = text.strip()

Your turn#

  1. Take " ADA lovelace " and print it as "Ada Lovelace" in one line of chained methods.
  2. Print whether the email "sam@example.org" ends with ".org".

Mini-Project: The Conference Name Badge#

Messy input in, tidy printed badge out: strip, title, split, slicing and center.

raw_name = input("Your full name: ")
raw_role = input("Your role: ")

clean_name = " ".join(raw_name.split()).title()
parts = clean_name.split()
initials = parts[0][0] + parts[-1][0]
print(clean_name, parts, initials)
Your full name:    grace   hopper 
Your role: data ENGINEER
Grace Hopper ['Grace', 'Hopper'] GH
width = 30
border = "+" + "-" * width + "+"
print(border)
print("|" + clean_name.center(width) + "|")
print("|" + raw_role.strip().upper().center(width) + "|")
print("|" + f"({initials})".center(width) + "|")
print(border)
+------------------------------+
|         Grace Hopper         |
|        DATA ENGINEER         |
|             (GH)             |
+------------------------------+

Numbers Without Surprises#

int = whole numbers, float = numbers with a decimal point

print(7 / 2)
print(8 / 2)
print(7 // 2)
print(7 % 2)
print(-7 // 2)
print(2 ** 10)
3.5
4.0
3
1
-4
1024
minutes = 135
hours = minutes // 60
leftover = minutes % 60
print(f"{minutes} minutes is {hours}h {leftover}m")
print("Is 14 even?", 14 % 2 == 0)
135 minutes is 2h 15m
Is 14 even? True
print(round(3.14159, 2))
print(round(7.6))
print(round(2.5), round(3.5))
print(abs(-12))
print(abs(4 - 9))
3.14
8
2 4
12
5
import math
print(math.sqrt(81))
print(math.pi)
print(math.ceil(4.1), math.floor(4.9))
boxes = math.ceil(53 / 12)
print(f"53 mugs need {boxes} boxes of 12")
9.0
3.141592653589793
5 4
53 mugs need 5 boxes of 12

Watch out: computers store decimals in binary, and some can't be stored exactly.

print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)
0.30000000000000004
False
total = 0.1 + 0.2
print(round(total, 2) == 0.3)
print(math.isclose(total, 0.3))
print(f"Total: {total:.2f}")
True
True
Total: 0.30
population = 1234567.891
print(f"{population:,.2f}")
print(f"{0.256:.1%}")
print(f"Order #{42:05}")
print(f"[{'left':<8}] [{'right':>8}] [{'mid':^8}]")
1,234,567.89
25.6%
Order #00042
[left    ] [   right] [  mid   ]

Pro tip: for money, count in pennies (whole numbers) and only divide by 100 when you print.

Type Conversion: Crossing the Text and Number Border#

int() and float() turn text into numbers; str() turns anything into text.

age_text = "25"
print(type(age_text))
print(age_text * 2)
print(int(age_text) * 2)
<class 'str'>
2525
50

Common mistake: gluing text and numbers together with +

age = 25
print("Age: " + age)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[30], line 2
      1 age = 25
----> 2 print("Age: " + age)

TypeError: can only concatenate str (not "int") to str
print("Age: " + str(age))
print(f"Age: {age}")
print("Age:", age)
Age: 25
Age: 25
Age: 25
print(int("ten"))
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[32], line 1
----> 1 print(int("ten"))

ValueError: invalid literal for int() with base 10: 'ten'
print(float("3.5"))
print(int(float("3.5")))
print(int(3.99), round(3.99))
for text in ["42", "4.2", "forty", " 7"]:
    print(f"'{text}' all digits? {text.isdigit()}")
3.5
3
3 4
'42' all digits? True
'4.2' all digits? False
'forty' all digits? False
' 7' all digits? False
print(bool("False"))
print(bool(""))
tickets = input("How many tickets? ")
print(type(tickets))
print(f"Total: £{int(tickets) * 12.5:.2f}")
True
False
How many tickets? 3
<class 'str'>
Total: £37.50

Mini-Project: The Road-Trip Fuel Splitter#

Ask for distance, fuel use and price, then split the cost fairly between friends.

distance_km = float(input("Trip distance in km: "))
litres_per_100km = float(input("Litres used per 100 km: "))
price_per_litre = float(input("Fuel price per litre: "))
people = int(input("People sharing the cost: "))
Trip distance in km: 438
Litres used per 100 km: 6.2
Fuel price per litre: 1.79
People sharing the cost: 3
litres_needed = distance_km * litres_per_100km / 100
total_cost = litres_needed * price_per_litre
each_pays = total_cost / people
tanks = math.ceil(litres_needed / 50)
print(f"Fuel needed:  {litres_needed:.1f} litres")
print(f"Total cost:   £{total_cost:.2f}")
print(f"Each pays:    £{each_pays:.2f}")
print(f"Fill-ups of a 50 L tank: {tanks}")
Fuel needed:  27.2 litres
Total cost:   £48.61
Each pays:    £16.20
Fill-ups of a 50 L tank: 1

Quick check#

What does 9 // 4 give? And what happens with int("9.0")?

Smarter Decisions: Conditionals in Depth#

Chained comparisons, truthy and falsy values, and the one-line if.

temperature = 18
if temperature >= 15 and temperature <= 25:
    print("Long way: perfect for a walk")
if 15 <= temperature <= 25:
    print("Chained: perfect for a walk")
Long way: perfect for a walk
Chained: perfect for a walk
uv_index = 7
if uv_index < 3:
    advice = "No protection needed"
elif uv_index < 6:
    advice = "Wear sunglasses"
elif uv_index < 8:
    advice = "Sunscreen and a hat"
else:
    advice = "Stay in the shade at midday"
print(f"UV {uv_index}: {advice}")
UV 7: Sunscreen and a hat
for value in [0, 7, "", "hi", [], [1, 2], None, 0.0]:
    print(f"{repr(value):>6} is {bool(value)}")
     0 is False
     7 is True
    '' is False
  'hi' is True
    [] is False
[1, 2] is True
  None is False
   0.0 is False
nickname = ""
if nickname:
    print(f"Hi {nickname}!")
else:
    print("Hi there, stranger!")
Hi there, stranger!
answer = "  YES "
if answer.strip().lower() in ["y", "yes", "yep", "sure"]:
    print("Great, booking it now")
else:
    print("No problem, maybe next time")
Great, booking it now
stock = 0
status = "In stock" if stock > 0 else "Sold out"
print(status)
count = 1
print(f"You have {count} {'item' if count == 1 else 'items'} in your basket")
Sold out
You have 1 item in your basket
has_ticket = True
age = 15
with_adult = True
if has_ticket and (age >= 16 or with_adult):
    print("Enjoy the film!")
if not has_ticket:
    print("Please visit the box office")
Enjoy the film!

Pro tip: when you mix and with or, add brackets. They cost nothing and make your intent obvious to the next reader.

Loops in Depth: break, continue and for-else#

break = leave the loop now. continue = skip to the next round.

print(list(range(5)))
print(list(range(2, 11, 2)))
for count in range(3, 0, -1):
    print(count, end="... ")
print("Lift off!")
[0, 1, 2, 3, 4]
[2, 4, 6, 8, 10]
3... 2... 1... Lift off!
readings = [21, 22, 23, 98, 24, 22]
for reading in readings:
    if reading > 50:
        print(f"Sensor fault: {reading}! Stopping.")
        break
    print("Reading OK:", reading)
Reading OK: 21
Reading OK: 22
Reading OK: 23
Sensor fault: 98! Stopping.
emails = ["ana@mail.com", "", "ben@mail.com", "not-an-email", "cy@mail.com"]
for email in emails:
    if "@" not in email:
        continue
    print("Sending invite to", email)
Sending invite to ana@mail.com
Sending invite to ben@mail.com
Sending invite to cy@mail.com
shelf = ["Dune", "Emma", "Ulysses"]
wanted = "Hamlet"
for title in shelf:
    if title == wanted:
        print("Found it!")
        break
else:
    print(f"Sorry, {wanted} is not on the shelf")
Sorry, Hamlet is not on the shelf
correct_pin = "4071"
attempts = 0
while attempts < 3:
    guess = input("Enter PIN: ")
    attempts += 1
    if guess == correct_pin:
        print("Unlocked!")
        break
    print(f"Wrong PIN. {3 - attempts} tries left.")
else:
    print("Card locked.")
Enter PIN: 1234
Wrong PIN. 2 tries left.
Enter PIN: 4071
Unlocked!

Common mistake: forgetting to indent the loop body.

for lap in range(3):
print("Lap", lap)
  Cell In[49], line 2
    print("Lap", lap)
    ^
IndentationError: expected an indented block after 'for' statement on line 1
for lap in range(3):
    print("Lap", lap + 1)
Lap 1
Lap 2
Lap 3

Loop Power Tools: Nested Loops, enumerate and zip#

booked = ["A2", "B3", "B4"]
for row in "ABC":
    line = ""
    for seat in range(1, 5):
        seat_code = f"{row}{seat}"
        line += " XX " if seat_code in booked else f" {seat_code} "
    print(line)
 A1  XX  A3  A4 
 B1  B2  XX  XX 
 C1  C2  C3  C4 
for row in range(1, 6):
    for col in range(1, 6):
        print(f"{row * col:4}", end="")
    print()
   1   2   3   4   5
   2   4   6   8  10
   3   6   9  12  15
   4   8  12  16  20
   5  10  15  20  25
podium = ["Mia", "Theo", "Sana"]
for place, runner in enumerate(podium, start=1):
    print(f"{place}. {runner}")
1. Mia
2. Theo
3. Sana
products = ["Notebook", "Pen", "Backpack"]
prices = [3.5, 1.2, 24.0]
for product, price in zip(products, prices):
    print(f"{product:<10} £{price:>6.2f}")
Notebook   £  3.50
Pen        £  1.20
Backpack   £ 24.00
days = ["Mon", "Tue", "Wed"]
temps = [14, 17]
print(list(zip(days, temps)))
[('Mon', 14), ('Tue', 17)]

Mini-Project: The Weekly Step-Count Report#

zip + conditionals + string repetition = a text bar chart with a goal tracker.

week = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
steps = [6200, 11050, 8400, 3900, 10230, 14800, 7600]
goal = 8000
for day, count in zip(week, steps):
    bar = "#" * (count // 1000)
    flag = "goal!" if count >= goal else ""
    print(f"{day}  {count:>6,}  {bar:<15} {flag}")
Mon   6,200  ######          
Tue  11,050  ###########     goal!
Wed   8,400  ########        goal!
Thu   3,900  ###             
Fri  10,230  ##########      goal!
Sat  14,800  ##############  goal!
Sun   7,600  #######         
days_hit = 0
for count in steps:
    if count >= goal:
        days_hit += 1
total = sum(steps)
best = max(steps)
best_day = week[steps.index(best)]
print(f"Total: {total:,} steps, about {total / len(steps):,.0f} a day")
print(f"Goal reached on {days_hit} of {len(week)} days")
print(f"Best day: {best_day} with {best:,} steps")
Total: 62,180 steps, about 8,883 a day
Goal reached on 4 of 7 days
Best day: Sat with 14,800 steps

Quick check#

What's the difference between break and continue? What two things does enumerate give you?

Your turn#

Change the report so days under 5,000 steps are flagged "rest day?" instead of left blank.

Functions: Write It Once, Use It Everywhere#

A function is a vending machine: put something in, press the button, get something out. You don't need to see the gears.

def print_banner(title):
    """Print a title between two lines of equals signs."""
    print("=" * 24)
    print(title.center(24))
    print("=" * 24)

print_banner("Monday Plan")
print_banner("Shopping")
========================
      Monday Plan       
========================
========================
        Shopping        
========================

Parameter = the placeholder name in the definition (title). Argument = the actual value you pass in when you call it ("Monday Plan").

def celsius_to_fahrenheit(celsius):
    """Convert a Celsius temperature to Fahrenheit."""
    return celsius * 9 / 5 + 32

summer = celsius_to_fahrenheit(30)
print(summer)
print(celsius_to_fahrenheit(-40))
86.0
-40.0
def describe_temperature(celsius):
    fahrenheit = celsius_to_fahrenheit(celsius)
    feel = "warm" if celsius >= 20 else "cool"
    return f"{celsius} C is {fahrenheit:.0f} F, which feels {feel}"

for reading in [8, 21, 35]:
    print(describe_temperature(reading))
8 C is 46 F, which feels cool
21 C is 70 F, which feels warm
35 C is 95 F, which feels warm
def add_vat_printing(price):
    print(price * 1.2)

result = add_vat_printing(10)
print("Stored result:", result)
12.0
Stored result: None

Common mistake: expecting a variable made inside a function to exist outside it.

def basket_total():
    basket_sum = 4.99 + 2.50 + 1.25

basket_total()
print(basket_sum)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[62], line 5
      2     basket_sum = 4.99 + 2.50 + 1.25
      4 basket_total()
----> 5 print(basket_sum)

NameError: name 'basket_sum' is not defined
def basket_total():
    return 4.99 + 2.50 + 1.25

basket_sum = basket_total()
print(f"Basket: £{basket_sum:.2f}")
Basket: £8.74
help(celsius_to_fahrenheit)
Help on function celsius_to_fahrenheit in module __main__:

celsius_to_fahrenheit(celsius)
    Convert a Celsius temperature to Fahrenheit.

Common mistake: calling a function without the arguments it needs.

celsius_to_fahrenheit()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[65], line 1
----> 1 celsius_to_fahrenheit()

TypeError: celsius_to_fahrenheit() missing 1 required positional argument: 'celsius'

Why functions matter:

  • Name an idea once, then reuse it anywhere
  • Fix a bug in one place, not ten
  • Test small pieces on their own
  • Read code like a story: check_rules(), score_password(), report()

Function Superpowers: Defaults, Keywords and Multiple Returns#

def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

print(greet("Amara"))
print(greet("Amara", "Good morning"))
Hello, Amara!
Good morning, Amara!
def order_coffee(size, milk="none", sugars=0):
    return f"{size} coffee | milk: {milk} | sugars: {sugars}"

print(order_coffee("Large"))
print(order_coffee("Large", sugars=2))
print(order_coffee(milk="oat", size="Small"))
Large coffee | milk: none | sugars: 0
Large coffee | milk: none | sugars: 2
Small coffee | milk: oat | sugars: 0
def summarise(numbers):
    """Return the lowest, highest and average of a list."""
    return min(numbers), max(numbers), sum(numbers) / len(numbers)

low, high, average = summarise([12, 7, 19, 4])
print(f"Low {low}, high {high}, average {average}")
print(summarise([1, 2, 3]))
Low 4, high 19, average 10.5
(1, 3, 2.0)
def safe_divide(a, b):
    if b == 0:
        return None
    return a / b

print(safe_divide(10, 4))
print(safe_divide(5, 0))
2.5
None

Pro tip: a good function does one job, and its name is a verb that says what: convert_price(), count_vowels(), send_reminder().

Your turn#

Write a function minutes_to_text(minutes) that returns a string like "2h 15m".

A First Taste of *args#

A star before a parameter collects any number of arguments into a tuple.

def total_bill(*prices):
    print("Received:", prices)
    return sum(prices)

print(total_bill(4.5, 2.25, 3))
print(total_bill(10))
print(total_bill())
Received: (4.5, 2.25, 3)
9.75
Received: (10,)
10
Received: ()
0
def cheer(punctuation, *words):
    for word in words:
        print(word.upper() + punctuation)

cheer("!", "ready", "steady", "go")
print("a", "b", "c", sep=" / ")
READY!
STEADY!
GO!
a / b / c

Borrowing Code: Importing math and random#

A module is a toolbox someone else has already built. import opens the lid.

import math
ladder = math.hypot(3, 4)
print(f"A ladder from 3 m out to 4 m up must be {ladder} m long")

from math import sqrt, pi
print(f"Circle area: {pi * 3 ** 2:.2f}")
print(sqrt(2))
A ladder from 3 m out to 4 m up must be 5.0 m long
Circle area: 28.27
1.4142135623730951
import random
random.seed(42)
print(random.randint(1, 6))
print(random.choice(["pizza", "curry", "tacos", "ramen"]))
print(round(random.random(), 3))
6
pizza
0.025
players = ["Ava", "Ben", "Caz", "Dev", "Eli", "Fay"]
random.shuffle(players)
print("Red team: ", players[:3])
print("Blue team:", players[3:])
print("Captains:", random.sample(players, 2))
Red team:  ['Dev', 'Fay', 'Ava']
Blue team: ['Eli', 'Ben', 'Caz']
Captains: ['Caz', 'Ben']

Pro tip: never name your own file random.py or math.py. Python will import YOUR file instead of the real module, and nothing will work.

The Problem-Solving Method: Understand, Plan, Code, Test#

  1. Understand: what goes in, what comes out? Write an example by hand.
  2. Plan: list the steps in plain English.
  3. Code: turn each step into a line or a small function.
  4. Test: try normal, tricky and empty inputs.
# Problem: find the longest word in a sentence
# Understand: "Coffee fuels late night coding" -> "Coffee"
plan = [
    "Split the sentence into words",
    "Remember the longest word so far (start with nothing)",
    "For each word, if it's longer, remember it instead",
    "Hand back the longest word",
]
for number, step in enumerate(plan, start=1):
    print(f"Step {number}: {step}")
Step 1: Split the sentence into words
Step 2: Remember the longest word so far (start with nothing)
Step 3: For each word, if it's longer, remember it instead
Step 4: Hand back the longest word
def longest_word(sentence):
    """Return the longest word; the first one wins a tie."""
    longest = ""
    for word in sentence.split():
        word = word.strip(".,!?")
        if len(word) > len(longest):
            longest = word
    return longest

print(longest_word("Coffee fuels late night coding"))
Coffee
tests = [
    ("Coffee fuels late night coding", "Coffee"),
    ("Wow!", "Wow"),
    ("a bb ccc bb", "ccc"),
    ("", ""),
]
for sentence, expected in tests:
    got = longest_word(sentence)
    verdict = "PASS" if got == expected else "FAIL"
    print(f"{verdict}: {sentence!r} -> {got!r}")
PASS: 'Coffee fuels late night coding' -> 'Coffee'
PASS: 'Wow!' -> 'Wow'
PASS: 'a bb ccc bb' -> 'ccc'
PASS: '' -> ''
assert longest_word("Python is fun") == "Python"
assert longest_word("Hi, everyone!") == "everyone"
print("All checks passed")
All checks passed

Pro tip: stuck? Shrink the problem. Solve it for one word, then two, then a whole sentence.

Quick check#

In the problem-solving method, why do we plan in plain English before writing code?

Capstone: The Password Strength Coach#

Built from small functions, each doing one job:

  • contains_any() -> check_rules() -> looks_common() -> score_password()
  • strength_label() -> report() -> report_all(*passwords)
  • make_passphrase() suggests strong passwords with random
import string

def contains_any(text, characters):
    """Return True if text contains at least one of the characters."""
    for char in text:
        if char in characters:
            return True
    return False

print(string.digits, string.ascii_uppercase)
print(contains_any("hello", string.digits), contains_any("hell0", string.digits))
0123456789
 ABCDEFGHIJKLMNOPQRSTUVWXYZ
False True
def check_rules(password):
    """Return a list of (passed, tip) pairs, one per rule."""
    return [
        (len(password) >= 12, "Use at least 12 characters"),
        (contains_any(password, string.ascii_lowercase), "Add a lowercase letter"),
        (contains_any(password, string.ascii_uppercase), "Add a capital letter"),
        (contains_any(password, string.digits), "Add a digit"),
        (contains_any(password, string.punctuation), "Add a symbol like ! or ?"),
    ]

for rule in check_rules("sunshine"):
    print(rule)
(False, 'Use at least 12 characters')
(True, 'Add a lowercase letter')
(False, 'Add a capital letter')
(False, 'Add a digit')
(False, 'Add a symbol like ! or ?')
COMMON_WORDS = ["password", "qwerty", "123456", "letmein", "admin"]

def looks_common(password):
    """Return True if the password contains a well-known weak word."""
    lowered = password.lower()
    for word in COMMON_WORDS:
        if word in lowered:
            return True
    return False

print(looks_common("MyPassword99"), looks_common("maple-Comet-71"))
True False
def score_password(password):
    """Score 0-5: one point per rule passed, 0 if it uses a common word."""
    if looks_common(password):
        return 0
    score = 0
    for passed, tip in check_rules(password):
        if passed:
            score += 1
    return score

def strength_label(score):
    labels = ["Terrible", "Very weak", "Weak", "Fair", "Good", "Strong"]
    return labels[score]

print(score_password("sunshine"), strength_label(score_password("sunshine")))
1 Very weak
def report(password):
    """Print a strength meter and tips for one password."""
    score = score_password(password)
    meter = "#" * score + "-" * (5 - score)
    print(f"{password:<32} [{meter}] {score}/5 {strength_label(score)}")
    if looks_common(password):
        print("    - Avoid well-known words like 'password'")
    for passed, tip in check_rules(password):
        if not passed:
            print("    -", tip)

def report_all(*passwords):
    for password in passwords:
        report(password)

report_all("sunshine", "Password123!", "Tr0ub4dor&3", "blue-Kettle-7-Orbit")
sunshine                         [#----] 1/5 Very weak
    - Use at least 12 characters
    - Add a capital letter
    - Add a digit
    - Add a symbol like ! or ?
Password123!                     [-----] 0/5 Terrible
    - Avoid well-known words like 'password'
Tr0ub4dor&3                      [####-] 4/5 Good
    - Use at least 12 characters
blue-Kettle-7-Orbit              [#####] 5/5 Strong
WORDS = ["maple", "rocket", "lantern", "pepper", "harbor",
         "violet", "comet", "biscuit", "glacier", "tundra"]

def make_passphrase(word_count=3, separator="-"):
    """Build a random passphrase like Maple-Comet-Tundra-42!"""
    words = []
    for word in random.sample(WORDS, word_count):
        words.append(word.capitalize())
    number = random.randint(10, 99)
    return separator.join(words) + separator + str(number) + "!"

random.seed(7)
report_all(make_passphrase(), make_passphrase(4, separator="."))
Violet-Lantern-Comet-93!         [#####] 5/5 Strong
Maple.Rocket.Glacier.Lantern.84! [#####] 5/5 Strong
while True:
    attempt = input("Try a password (blank to stop): ")
    if not attempt:
        break
    report(attempt)
    if score_password(attempt) == 5:
        print("Excellent, that one's a keeper!")
        break
Try a password (blank to stop): purpleMonkey
purpleMonkey                     [###--] 3/5 Fair
    - Add a digit
    - Add a symbol like ! or ?
Try a password (blank to stop): purpleMonkey42!
purpleMonkey42!                  [#####] 5/5 Strong
Excellent, that one's a keeper!

Recap#

  • Strings: escapes, triple quotes, index from 0 (or -1 from the end), slice [start:stop:step]
  • String methods return NEW strings: strip, lower, replace, split, join, find, startswith, in
  • Numbers: / gives a float, // floors, % is the remainder; floats are approximate
  • Convert with int(), float(), str(); input() always returns text
  • Chained comparisons, truthy/falsy values, and x if condition else y
  • break, continue, for-else, nested loops, enumerate and zip
  • Functions: parameters, defaults, keywords, return (even several values), docstrings, *args
  • import math and random; solve problems with understand, plan, code, test

Try it yourself#

  1. Coach upgrade: add a rule that fails if the password contains the same character 3 times in a row.
  2. Receipt printer: write line_item(name, price, qty=1) and receipt(*items) that print a neat, totalled receipt.
  3. Study planner: write plan_sessions(total_minutes, session=25) that returns how many sessions and the minutes left over.

What's next#

Lesson 3: Learn Python by Building Projects. You've got the essentials. Next, we put them to work on bigger, real-world programs.

Found this useful?

All lessons, notebooks and datasets here are free. If they helped you, a coffee keeps new lessons coming.