Learning Goals
3 minBy the end of this lesson you can:
- Test whether a string contains something using
in. - Find where something sits with
find(), and handle the-1it returns when it is missing. - Check the shape of input with
startswith(),endswith()andisdigit()before converting it.
Warm-Up · The Input That Was Not a Number
5 minLesson 3 converted a field with int(). What happens when the field is not what you assumed?
age = int("13") print(age + 1) age = int("thirteen") print(age + 1)
Answer
Output
14 Traceback (most recent call last): ValueError: invalid literal for int() with base 10: 'thirteen'
The program stops dead. A pupil typing a word where a number belongs should not crash your quiz.
Section 7 teaches catching the error after it happens. This lesson does the other half: check the text is a number before converting, so the error never occurs.
New Concept · Questions a String Can Answer
12 minLike checking a form before you file it. Is the name box filled in? Does the phone number start with 01? Is the age box actually digits? Each is one question with a yes-or-no answer.
in — is it in there at all?
order = "pizza with extra hot sauce" print("hot sauce" in order) print("chicken" in order) print("HOT SAUCE" in order)
Output
True False False
The third line matters: in is case sensitive. Lower both sides first — lesson 2 — if case should not count.
find() — whereabouts is it?
full_name = "Leo Reed" print(full_name.find(" ")) print(full_name.find("z"))
Output
3 -1
-1 means "not found", and it is a trap-1 is a perfectly valid index — the last character. So text[text.find("z")] silently gives you the last character instead of failing. Always test the result before you use it: if pos != -1:.
Shape checks
phone = "0123456789" code = "ADV002-0147" print(phone.startswith("01")) print(code.startswith("ADV")) print(code.endswith("0147")) print(phone.isdigit()) print(code.isdigit()) print("Mia".isalpha()) print("".isdigit())
Output
True True True True False True False
The family is larger than these — isalnum(), isspace(), islower() — and they all answer True or False. Note the last line: an empty string is not digits, which is exactly the answer you want when someone presses Enter without typing.
count() — how many?
sentence = "the rain in spain stays mainly in the plain" print(sentence.count("ain")) print(sentence.count("the"))
Output
4 2
Why it matters. Every one of these guards a line that would otherwise crash or quietly do the wrong thing. Checking first is cheaper than debugging later.
Worked Example · Checking a Form
12 minFour entries, each checked before it is trusted. Nothing here can crash.
# check_form.py - validate before you convert entries = [ ["Mia Tom", "0123456789", "13"], ["Leo", "60123456789", "14"], ["Ana", "01234", "thirteen"], ["", "0198887777", "12"], ] for name, phone, age in entries: print(f"Checking {name!r}") if name.strip() == "": print(" Name is empty.") elif not name.replace(" ", "").isalpha(): print(" Name has characters that are not letters.") else: print(f" Name OK: {name.strip().title()}") if not phone.isdigit(): print(" Phone must be digits only.") elif not phone.startswith("01"): print(" mobiles start with 01.") elif len(phone) < 10 or len(phone) > 11: print(f" Phone should be 10 or 11 digits, got {len(phone)}.") else: print(f" Phone OK: {phone}") if age.isdigit(): print(f" Age OK: {int(age)} - next year {int(age) + 1}") else: print(f" Age {age!r} is not a number.") print()
Output
Checking 'Mia Tom' Name OK: Mia Tom Phone OK: 0123456789 Age OK: 13 - next year 14 Checking 'Leo' Name OK: Leo Phone: mobiles start with 01. Age OK: 14 - next year 15 Checking 'Ana' Name OK: Ana Phone should be 10 or 11 digits, got 5. Age 'thirteen' is not a number. Checking '' Name is empty. Phone OK: 0198887777 Age OK: 12 - next year 13
Three habits worth copying:
int(age)only runs inside theif. The check and the conversion are never separated, so the crash cannot happen.eliforders the checks from cheapest to most specific, and each entry gets exactly one phone message rather than three.{name!r}shows the quotes, so an empty name is visible as''rather than nothing at all.
"Leo".replace(" ", "").isalpha() is True, but a name like "Ana Rivera" would fail — the slash is not a letter. names contain slashes, hyphens and apostrophes. Validation rules always end up being about people, not about strings.
Try It Yourself
13 minAsk for an age. If it is all digits, print how old they will be next year. If not, print a polite message instead of crashing.
Hint
raw = input("How old are you? ").strip() if raw.isdigit(): age = int(raw) print(f"Next year you will be {age + 1}.") else: print("Please type digits only.")
Ask for a full name. Use find(" ") to locate the first space, then slice out the first name and the rest.
- If there is no space, say so and treat the whole thing as a first name.
- Print both parts, each in title case.
You could do this with split() in one line — do it with find() and slices this time, so the -1 case is something you have handled yourself.
Mini-Challenge 🔥 · Debug: The Search That Found Nothing, Loudly
8 minGrace's menu search reports a match for items that are not on the menu, and its price lookup is nonsense. There are three mistakes.
# menu_search.py - buggy
menu = "pizza:3.50,noodles:5.00,hot chocolate:2.50"
wanted = "Ice cream"
position = menu.find(wanted)
if position:
print("Found", wanted, "at", position)
price = menu[position + len(wanted) + 1]
print("Price: $", price)
else:
print(wanted, "is not on the menu.")Answer
if position:is the wrong test.findreturns-1when missing, and-1is truthy — so "not found" takes theifbranch. Worse, an item found at position0would be treated as missing. It must beif position != -1:.- The search is case sensitive.
"Ice cream"would not match"ice cream"even if it were there. Lower both sides. - The price slice takes one character.
menu[i]is a single character; a price needs a slice, and the sensible way is to split the entry rather than count characters.
A working version, using this lesson plus lesson 3:
# menu_search.py - fixed menu = "pizza:3.50,noodles:5.00,hot chocolate:2.50" wanted = "Ice cream" needle = wanted.lower() found = False for entry in menu.split(","): name, price = entry.split(":") if name == needle: found = True print(f"{wanted} costs $ {float(price):.2f}") if not found: print(f"{wanted} is not on the menu.")
Output
Ice cream is not on the menu.
Change wanted to "Hot chocolate" and it prints Hot chocolate costs $2.50. Splitting into records beats hunting positions inside one long string — and the -1 trap disappears with it.
Recap
3 minin asks whether text contains something; find() says where and returns -1 when it does not, which must always be tested for. startswith(), endswith() and the is...() family check the shape of input, so you can convert only what is safe to convert. All of them are case sensitive.
Vocabulary Card
in- Membership test —
Trueif one string appears inside another. - find
- Returns the index of the first match, or
-1if there is none. - isdigit
- True when every character is a digit and the string is not empty.
- validation
- Checking that input has the expected shape before using it.
Homework
4 minWrite password_check.py. Ask for a password once, then report on it without crashing whatever is typed.
- Say whether it is at least 8 characters.
- Say whether it contains at least one digit.
- Say whether it contains at least one capital letter.
- Say whether it contains a space — it should not.
- Finish with
Strong,Could be better, orWeak, based on how many checks passed.
Hint: loop over the characters and use isdigit() and isupper() on each one.
Sample · password_check.py
# password_check.py - four checks, no crash whatever is typed password = input("Choose a password: ") long_enough = len(password) >= 8 has_digit = False has_capital = False has_space = " " in password for character in password: if character.isdigit(): has_digit = True if character.isupper(): has_capital = True print() print(f"At least 8 characters: {long_enough}") print(f"Contains a digit: {has_digit}") print(f"Contains a capital: {has_capital}") print(f"Contains a space: {has_space}") score = 0 if long_enough: score = score + 1 if has_digit: score = score + 1 if has_capital: score = score + 1 if not has_space: score = score + 1 print() if score == 4: print("Strong") elif score >= 2: print("Could be better") else: print("Weak")
Output
Choose a password: Sukan2026 At least 8 characters: True Contains a digit: True Contains a capital: True Contains a space: False Strong
Two flags start False and are only ever switched on — a common shape when scanning. Note has_space is worded so that False is the good answer, which is why the score adds not has_space.