Python operators exercises — the gym fee, seconds to hours and the till change coin by coin (and an or that always said yes)
In an FP1 practical we had to simulate the cafeteria’s payment machine, and one of the checks was whether the payment method was accepted. I wrote method == "cash" or "bizum", tried it with cash, tried it with bizum, it worked… and I handed it in. The lecturer tried it with “card” and the machine accepted that too. And “stones”. My or said yes to everything. It took me quite a while to understand why, and it is one of today’s three “What’s wrong here?” snippets: operators do not do what you read, they do what their rules say.
These are the exercises for the operators topic. You should have read the theory and done the practice first. You have five exercises across three levels (basic, intermediate and challenge) with a special focus on // and %, the ones that confuse people most, three snippets with a catch, solutions explained line by line and, at the end, the cheat sheet to keep everything at hand.
Table of Contents
How to use this article
- Read the statement and the expected output. The data are fixed in variables at the top (no
input()), as in the practice. - Try to solve it in your editor. If you have been stuck for more than 10 minutes, open the hint.
- Compare with the commented solution. Not just whether it gives the same: look at why it is written that way.
- Change the data (another number of months, another grade, another amount) and predict the output before running it.
They can all be done without if, with operators only: that is part of the challenge. In the next topic, with conditionals, you will see more comfortable versions.
Basic level
Exercise 1: gym invoice
A gym charges 29.99 € a month. Members who have been there for more than 12 months get a 10 % discount from month 13 on. With months = 15, the program shows the base fee, this month’s fee, the total paid so far and the total savings. Work in cents.
Expected output:
--- Gym invoice --- Months as a member: 15 Base fee: 29.99 € Fee this month: 26.99 € Total paid: 440.85 € Total savings: 9.00 €
Split the months into two bands: those that pay full price and those with a discount (max(0, months - 12) stops it from going negative). And remember that True is 1 and False is 0 when you multiply them.
Exercise 2: time converter
Convert seconds = 3723 into hours, minutes and seconds, and also show it in clock format (01:02:03). Check the result by doing the sum backwards and repeat the conversion with divmod.
Expected output:
3723 seconds are: → 1 h, 2 min and 3 s → in clock format: 01:02:03 Check: 3723 With divmod: 1 2 3
It is pure // and %. The hours are seconds // 3600; what is left after the hours is seconds % 3600, and from that you get the minutes with // 60. The loose seconds are seconds % 60.
Intermediate level
Exercise 3: grade analyser
With a grade out of 100 (grade = 73), show whether it is valid (between 0 and 100), the grade out of 10, whether it is a pass (≥ 50), a merit (≥ 70) or a distinction (≥ 90), what the next level is and how many points are needed to reach it, and how many points it has above a pass. Without if.
Expected output:
--- Analysis of grade 73 --- Valid grade? True Out of 10: 7.3 Passed? True | Merit? True | Distinction? False Next level: Distinction (needs 17 more points) Points above a pass: 23
(grade < 70 and "Merit") is "Merit" if the grade is under 70 and False if not. Chain several of these with or: the first one that is not falsy is the one that stays.
Exercise 4: digit extractor
With n = 2847, get each digit separately without turning the number into text, only with // and %. Also show the sum of the digits, whether the number is divisible by 3 (checking it in two ways), whether it is even, the number backwards and whether it really has 4 digits.
Expected output:
--- Digits of 2847 --- Thousands: 2 | Hundreds: 8 | Tens: 4 | Units: 7 Sum of digits: 21 Divisible by 3? True | check: True Is it even? False Reversed: 7482 Does it have 4 digits? True
n % 10 is the last digit and n // 10 drops the last digit. Combining them: the tens are n // 10 % 10. A number is divisible by 3 if the sum of its digits is.
Final challenge
Exercise 5: cash register
A 37.43 € purchase paid with 50 €. Work out the change and break it down into the smallest number of pieces: 50, 20, 10 and 5 € notes and 2 €, 1 €, 50, 20, 10, 5, 2 and 1 cent coins. Show how many pieces of each kind, the total number of pieces and a check that the breakdown adds up to exactly the change. We do not know loops yet, so each piece goes on its own line.
Expected output:
Change with floats: 12.57 --- Till: 12.57 € of change --- Notes: 50 €: 0 | 20 €: 0 | 10 €: 1 | 5 €: 0 Coins: 2 €: 1 | 1 €: 0 | 50 cent: 1 | 20 cent: 0 Coins: 10 cent: 0 | 5 cent: 1 | 2 cent: 1 | 1 cent: 0 Pieces in total: 5 Does the breakdown add up? True
Turn the change into cents with round(change * 100) and never touch a float again. For each piece, divmod(rest, value) gives you how many fit and what is left, and that remainder is what goes on to the next piece.
What’s wrong here?
Three short snippets with one mistake each. Before reading the explanation, predict the output.
Snippet 1: the average price
coffee1 = 120
coffee2 = 110
coffee3 = 140
average = coffee1 + coffee2 + coffee3 / 3
print("Average price:", average, "cents")
What do you think it prints? The average price of three coffees costing 120, 110 and 140 cents should be 123.33.
Average price: 276.6666666666667 cents
What happens: precedence. / goes before +, so Python works out coffee3 / 3 = 46.67 and then adds the other two: 120 + 110 + 46.67 = 276.67. Only the last coffee has been divided.
Writing the average “as you say it” (add the three and divide by three) without parentheses. The division only affects the operand right next to it.
The fix: parentheses around the sum.
coffee1 = 120
coffee2 = 110
coffee3 = 140
average = (coffee1 + coffee2 + coffee3) / 3 # parentheses: add first, then divide
print("Average price:", round(average, 2), "cents")
Now it prints:
Average price: 123.33 cents
Now (120 + 110 + 140) / 3 = 370 / 3 = 123.33.
When you write an average, type the empty parentheses ( ) / n first and fill in the sum inside. That way you never forget them.
Snippet 2: the receipt for three coffees
total = 3 * 1.10
print("Total:", total)
print("Is it 3.30 €?", total == 3.30)
Three coffees at 1.10 €. Is that 3.30 €?
Total: 3.3000000000000003 Is it 3.30 €? False
What happens: 1.10 cannot be stored exactly in binary, and multiplying it by 3 makes the error visible: 3.3000000000000003. Compared with the 3.30 typed by hand (which is not exact either, but is a different number), == gives False. It is the same problem as the 0.1 + 0.2 from the theory.
Comparing the result of a calculation with decimals using ==. It may work with some data and fail with others.
The fix: compare with a tolerance or, even better, work in cents.
import math
total = 3 * 1.10
print("Is it 3.30 €?", math.isclose(total, 3.30)) # compares with a tolerance
total_cents = 3 * 110 # or better: in cents
print("Is it 330 cents?", total_cents == 330)
Result:
Is it 3.30 €? True Is it 330 cents? True
If your program handles money, decide from the first line that everything is in cents (int) and convert it to euros only when you show it.
Snippet 3: the or that always said yes
The one from my story:
method = "card"
accepted = method == "cash" or "bizum"
print("Accepted?", accepted)
The method is “card”, which is not among the accepted ones. What does it print?
Accepted? bizum
What happens: it does not print False, nor even True: it prints bizum. Python does not share the == between both sides of the or. What it does is:
- It evaluates the left side:
method == "cash"→False. - As the left side is false,
orreturns the right side as it is: the text"bizum". - A non-empty text is truthy, so inside an
ifit would count as “accepted”. With any method.
x == a or b to ask “is x a or b?”. It is always “truthy” as soon as b is not empty or zero. You have to write the full comparison on both sides.
The fix:
method = "card"
accepted = method == "cash" or method == "bizum" # compare on both sides
print("Accepted?", accepted)
Output:
Accepted? False
And with method = "bizum", to check that the accepted ones still work:
Accepted? True
When there are many options, method in ("cash", "bizum", "card") is shorter and has no catch. in asks whether a value is inside a collection; you will see it properly with lists and tuples.
Commented solutions
Solution exercise 1
# Level 1 · Gym invoice (all in cents)
FEE_CENTS = 2999 # 29.99 € a month
DISCOUNT_PCT = 10 # from month 13 on
MONTHS_WITHOUT_DISCOUNT = 12
months = 15 # months as a member (before: input)
# Bands: the first 12 at full price and the rest with discount
discounted_months = max(0, months - MONTHS_WITHOUT_DISCOUNT) # max avoids negatives
full_price_months = months - discounted_months
discount_cents = round(FEE_CENTS * DISCOUNT_PCT / 100) # 299.9 → 300
reduced_fee = FEE_CENTS - discount_cents
# True is 1 and False is 0: with no discount, 0 is subtracted
has_discount = months > MONTHS_WITHOUT_DISCOUNT
fee_this_month = FEE_CENTS - discount_cents * has_discount
total = full_price_months * FEE_CENTS + discounted_months * reduced_fee
savings = discounted_months * discount_cents
print("--- Gym invoice ---")
print("Months as a member:", months)
print(f"Base fee: {FEE_CENTS // 100}.{FEE_CENTS % 100:02d} €")
print(f"Fee this month: {fee_this_month // 100}.{fee_this_month % 100:02d} €")
print(f"Total paid: {total // 100}.{total % 100:02d} €")
print(f"Total savings: {savings // 100}.{savings % 100:02d} €")
Output:
--- Gym invoice --- Months as a member: 15 Base fee: 29.99 € Fee this month: 26.99 € Total paid: 440.85 € Total savings: 9.00 €
Why it is written this way:
- Cents from the start: 29.99 € is 2999 cents. The 10 % discount is 299.9 cents, which
roundleaves at 300 (rounding to the nearest cent; with//it would be 299 and the gym would charge you one cent too much every month). - The bands without
if:max(0, months - 12)gives 3 with 15 months and 0 with 8 (because 8 − 12 = −4 andmaxkeeps the 0). Sofull_price_monthsis 15 − 3 = 12. - The
booltrick:has_discountisTruewith 15 months. When multiplied,Trueis 1:2999 - 300 * True= 2699. With 8 months it would be2999 - 300 * False= 2999.
The sums: total = 12 × 2999 + 3 × 2699 = 35988 + 8097 = 44085 cents (440.85 €); savings = 3 × 300 = 900 cents (9.00 €). The old version of this exercise gave 434.85 €: it was wrong. That is why all the outputs here come from a real run.
With months = 8, no discount:
--- Gym invoice --- Months as a member: 8 Base fee: 29.99 € Fee this month: 29.99 € Total paid: 239.92 € Total savings: 0.00 €
8 × 2999 = 23992 cents, 239.92 €, and savings of 0.00 €: the :02d is what makes both zeros appear.
Working out months - 12 without max. With fewer than 12 months you get negative “discounted months” and the total suddenly drops.
Solution exercise 2
# Level 1 · Time converter: seconds → hours, minutes and seconds
seconds = 3723
hours = seconds // 3600 # whole hours
minutes = seconds % 3600 // 60 # (what is left after the hours) // 60
secs = seconds % 60 # what does not reach a minute
print(seconds, "seconds are:")
print("→", hours, "h,", minutes, "min and", secs, "s")
print(f"→ in clock format: {hours:02d}:{minutes:02d}:{secs:02d}")
# Reverse check and the divmod version
print("Check:", hours * 3600 + minutes * 60 + secs)
h, rest = divmod(seconds, 3600)
m, s = divmod(rest, 60)
print("With divmod:", h, m, s)
Output:
3723 seconds are: → 1 h, 2 min and 3 s → in clock format: 01:02:03 Check: 3723 With divmod: 1 2 3
Why it is written this way:
seconds % 3600 // 60has no parentheses because%and//are on the same step and are done from left to right: first 3723 % 3600 = 123 (what is left after the hour) and then 123 // 60 = 2 minutes.seconds % 60= 3 gives the loose seconds directly: however many hours there are, the remainder by 60 is what does not reach a minute.:02dpads with zeros for the clock format.- The reverse check: 1 × 3600 + 2 × 60 + 3 = 3723.
- The
divmodversion does the same in two steps and reads almost like a sentence: “divide by 3600 and keep the remainder; divide the remainder by 60”.
| Step | Operation | Result |
|---|---|---|
| Hours | 3723 // 3600 | 1 |
| Left after the hours | 3723 % 3600 | 123 |
| Minutes | 123 // 60 | 2 |
| Seconds | 3723 % 60 | 3 |
With a bigger value, seconds = 45296:
45296 seconds are: → 12 h, 34 min and 56 s → in clock format: 12:34:56 Check: 45296 With divmod: 12 34 56
45296 // 3600 = 12 (12 × 3600 = 43200); 2096 is left; 2096 // 60 = 34 (34 × 60 = 2040); and 45296 % 60 = 56.
Working out the minutes as plain seconds // 60: with 3723 it gives 62, which are the total minutes, not the ones left after taking away the hour.
Solution exercise 3
# Level 2 · Analysing a grade out of 100, without if: operators only
grade = 73
out_of_10 = grade / 10
in_range = 0 <= grade <= 100 # chained comparison
passed = grade >= 50
merit = grade >= 70
distinction = grade >= 90
# and/or return an operand: the first band that holds "wins"
next_level = (grade < 50 and "Pass") or (grade < 70 and "Merit") \
or (grade < 90 and "Distinction") or "none, it is already the top one"
missing = (grade < 50 and 50 - grade) or (grade < 70 and 70 - grade) \
or (grade < 90 and 90 - grade) or 0
above_pass = max(0, grade - 50)
print("--- Analysis of grade", grade, "---")
print("Valid grade?", in_range)
print("Out of 10:", out_of_10)
print("Passed?", passed, "| Merit?", merit, "| Distinction?", distinction)
print("Next level:", next_level, "(needs", missing, "more points)")
print("Points above a pass:", above_pass)
Output:
--- Analysis of grade 73 --- Valid grade? True Out of 10: 7.3 Passed? True | Merit? True | Distinction? False Next level: Distinction (needs 17 more points) Points above a pass: 23
Why it is written this way: the interesting part is the two expressions with and and or.
next_level = (grade < 50 and "Pass") or (grade < 70 and "Merit") \
or (grade < 90 and "Distinction") or "none, it is already the top one"
missing = (grade < 50 and 50 - grade) or (grade < 70 and 70 - grade) \
or (grade < 90 and 90 - grade) or 0
This is how next_level is evaluated with grade = 73, from left to right, stopping as soon as something is truthy:
| Piece | Sum | Value | Carries on? |
|---|---|---|---|
grade < 50 and "Pass" | 73 < 50 is False; and stops | False | Yes, or looks at the next one |
grade < 70 and "Merit" | 73 < 70 is False | False | Yes |
grade < 90 and "Distinction" | 73 < 90 is True; and returns the text | "Distinction" | No: it is truthy, or keeps it |
missing works the same way and gets to 90 - grade = 17. Points above a pass: max(0, 73 - 50) = 23. And 0 <= grade <= 100 is the chained comparison that checks the range.
With grade = 42 and with grade = 95:
--- Analysis of grade 42 --- Valid grade? True Out of 10: 4.2 Passed? False | Merit? False | Distinction? False Next level: Pass (needs 8 more points) Points above a pass: 0
And the second one:
--- Analysis of grade 95 --- Valid grade? True Out of 10: 9.5 Passed? True | Merit? True | Distinction? True Next level: none, it is already the top one (needs 0 more points) Points above a pass: 45
With 42, the first piece is already truthy: next level “Pass”, 50 − 42 = 8 points needed, and max(0, 42 - 50) = 0 points above a pass. With 95, no piece holds and or gets to the last value: the text “none, it is already the top one” and 0 points.
This technique fails if the value you want to return is “falsy”. For example, if a grade were exactly on the boundary and the calculation gave 70 - 70 = 0, or would skip it because 0 is falsy. It does not happen here because each piece only holds when the difference is positive, but once you have if (next topic) use if for this.
Solution exercise 4
# Level 2 · Digit extractor for a 4-digit number, without turning it into text
n = 2847
units = n % 10 # the last digit
tens = n // 10 % 10 # drop the last one and keep the new last one
hundreds = n // 100 % 10
thousands = n // 1000 # with 4 digits, whatever is left in front
total_sum = thousands + hundreds + tens + units
backwards = units * 1000 + tens * 100 + hundreds * 10 + thousands
print("--- Digits of", n, "---")
print("Thousands:", thousands, "| Hundreds:", hundreds, "| Tens:", tens, "| Units:", units)
print("Sum of digits:", total_sum)
print("Divisible by 3?", total_sum % 3 == 0, "| check:", n % 3 == 0)
print("Is it even?", units % 2 == 0)
print("Reversed:", backwards)
print("Does it have 4 digits?", 1000 <= n <= 9999)
Output:
--- Digits of 2847 --- Thousands: 2 | Hundreds: 8 | Tens: 4 | Units: 7 Sum of digits: 21 Divisible by 3? True | check: True Is it even? False Reversed: 7482 Does it have 4 digits? True
Why it is written this way: each digit comes from “dropping the ones on the right” with // and “keeping the last one” with % 10.
| Digit | Expression | Sum | Result |
|---|---|---|---|
| Units | n % 10 | 2847 % 10 | 7 |
| Tens | n // 10 % 10 | 284 % 10 | 4 |
| Hundreds | n // 100 % 10 | 28 % 10 | 8 |
| Thousands | n // 1000 | 2847 // 1000 | 2 |
n // 10 % 10 needs no parentheses for the same reason as in exercise 2: same step, left to right. The sum is 2 + 8 + 4 + 7 = 21, and 21 % 3 = 0, so 2847 is divisible by 3 (2847 = 3 × 949, and n % 3 == 0 confirms it). The number backwards is built by multiplying each digit by its new position: 7 × 1000 + 4 × 100 + 8 × 10 + 2 = 7482. And 1000 <= n <= 9999 checks that it really has 4 digits.
Getting the thousands with n // 1000 % 10 is fine, but if the number had 5 digits (12847) n // 1000 would give 12. That is why the last line checks the range.
Solution exercise 5
# Level 3 · Cash register: the change, note by note and coin by coin
amount = 37.43
handed_over = 50.00
change = handed_over - amount
print("Change with floats:", change) # the float is not exact
rest = round(change * 100) # we move to WHOLE cents
total_cents = rest # kept to check at the end
# divmod(rest, value) returns (how many pieces fit, what is left)
b50, rest = divmod(rest, 5000)
b20, rest = divmod(rest, 2000)
b10, rest = divmod(rest, 1000)
b5, rest = divmod(rest, 500)
m200, rest = divmod(rest, 200)
m100, rest = divmod(rest, 100)
m50, rest = divmod(rest, 50)
m20, rest = divmod(rest, 20)
m10, rest = divmod(rest, 10)
m5, rest = divmod(rest, 5)
m2, rest = divmod(rest, 2)
m1 = rest # whatever is left are single cents
print(f"--- Till: {total_cents // 100}.{total_cents % 100:02d} € of change ---")
print("Notes: 50 €:", b50, "| 20 €:", b20, "| 10 €:", b10, "| 5 €:", b5)
print("Coins: 2 €:", m200, "| 1 €:", m100, "| 50 cent:", m50, "| 20 cent:", m20)
print("Coins: 10 cent:", m10, "| 5 cent:", m5, "| 2 cent:", m2, "| 1 cent:", m1)
pieces = b50 + b20 + b10 + b5 + m200 + m100 + m50 + m20 + m10 + m5 + m2 + m1
total_sum = (b50 * 5000 + b20 * 2000 + b10 * 1000 + b5 * 500 + m200 * 200 + m100 * 100
+ m50 * 50 + m20 * 20 + m10 * 10 + m5 * 5 + m2 * 2 + m1)
print("Pieces in total:", pieces)
print("Does the breakdown add up?", total_sum == total_cents)
Output:
Change with floats: 12.57 --- Till: 12.57 € of change --- Notes: 50 €: 0 | 20 €: 0 | 10 €: 1 | 5 €: 0 Coins: 2 €: 1 | 1 €: 0 | 50 cent: 1 | 20 cent: 0 Coins: 10 cent: 0 | 5 cent: 1 | 2 cent: 1 | 1 cent: 0 Pieces in total: 5 Does the breakdown add up? True
Why it is written this way:
- A single step with decimals:
handed_over - amountis the only sum with afloat, and straight after it is turned into cents withround(change * 100). Here the subtraction prints as12.57, but you will not always be that lucky (tryamount = 0.10andhanded_over = 0.30):roundguarantees a correct integer whatever happens. Withint(change * 100)you could end up one cent short if thefloatwere 1256.9999… divmodin a cascade: each linepiece, rest = divmod(rest, value)stores how many pieces fit and overwritesrestwith what is left, which is what the next line receives. It is a double assignment:divmodreturns a pair and Python shares its two values out between the two variables.- From largest to smallest: that way you get the fewest pieces (with euros it always works).
This is how the change gets eaten up, piece by piece:
| Piece | divmod | Fit | Left |
|---|---|---|---|
| 10 € | divmod(1257, 1000) | 1 | 257 |
| 5 € | divmod(257, 500) | 0 | 257 |
| 2 € | divmod(257, 200) | 1 | 57 |
| 1 € | divmod(57, 100) | 0 | 57 |
| 50 cent | divmod(57, 50) | 1 | 7 |
| 20 and 10 cent | divmod(7, 20), divmod(7, 10) | 0 | 7 |
| 5 cent | divmod(7, 5) | 1 | 2 |
| 2 cent | divmod(2, 2) | 1 | 0 |
(50 € and 20 € give 0 with 1257 left because they do not fit.) Total: 5 pieces, and the check 1000 + 200 + 50 + 5 + 2 = 1257 gives True.
Doing the breakdown with float values (change // 0.05). With decimals, // can give one piece fewer because of a tiny error and leave a remainder of 0.0499999. In whole cents it never happens.
Exam-style quiz
Now test yourself as in the exam. The FP1 quizzes look like this: 15 questions in 20 minutes (16 in 30 in the Sequences one), almost all single choice (each wrong answer subtracts a third of what the question is worth) and many «what does this code print?», with the odd fill in the gaps, matching or true or false. Here are 13 original questions on operators. Answer without running anything, press Check and read the explanation for each one. These questions are also the question bank for the FP1 theory mock exam, which will mix them with those from the other topics.
What does this program print? Write the exact output.
print(17 // 5, 17 % 5)
Solution
Answer: 3 2
// gives the whole quotient and % the remainder: 17 = 5 × 3 + 2. It is the pair you will use to turn seconds into minutes, get the digits of a number or check whether it is even.
What value does this code show?
a = True b = False print(not a or b and a)
FalseTrue- A syntax error
None
Solution
Answer: False
Precedence from highest to lowest: not, and, or. It is worked out as (not a) or (b and a) = False or False = False. If in doubt, add brackets: nobody will take marks off for them.
What does this program print? Write the exact output.
print(2 + 3 * 4 ** 2)
Solution
Answer: 50
First the power: 4 ** 2 = 16. Then the multiplication: 3 × 16 = 48. And finally the addition: 2 + 48 = 50.
Which of these expressions is True when n is even?
n % 2 == 0n / 2 == 0n // 2 == 1n % 2 == 1
Solution
Answer: n % 2 == 0
% is the go-to operator for «is it a multiple of…?». n % 2 == 0 → even; n % 3 == 0 → multiple of 3.
Match each expression with its result:
7 / 2 | |
7 // 2 | |
7 % 2 | |
2 ** 3 |
Definitions:
- 1
- 6
- 14
- 3.5
- 3
- 8
Solution
7 / 2→ 3.57 // 2→ 37 % 2→ 12 ** 3→ 8
/ is division with decimals, // whole quotient, % remainder and ** power. 2 ** 3 = 2 × 2 × 2 = 8 (not 6: it is not a multiplication).
A classmate wants to check whether colour is 'red' or 'blue' and writes colour == 'red' or 'blue'. What happens?
- The condition is always true, because
'blue'on its own counts asTrue - It works fine
- A syntax error
- It is always false
Solution
Answer: The condition is always true, because 'blue' on its own counts as True
Python reads (colour == 'red') or ('blue'). If the first part is false, the or returns 'blue', and a non-empty text is «true» in an if. It is one of the most common FP1 mistakes.
What does this program print? Write the exact output.
total = 10 total += 5 total *= 2 total -= 4 print(total)
Solution
Answer: 26
+=, *= and -= are shortcuts: 10 + 5 = 15, 15 × 2 = 30, 30 − 4 = 26.
True or false?
5 == 5.0isTrue.'5' == 5isTrue.3 < 5 < 9isTrue.=and==do the same thing.
Solution
- T —
5 == 5.0isTrue. - F —
'5' == 5isTrue. - T —
3 < 5 < 9isTrue. - F —
=and==do the same thing.
An int and a float with the same value are equal, but a text is never equal to a number. Python lets you chain comparisons (3 < 5 < 9 is 3 < 5 and 5 < 9). And = assigns, while == compares.
What is round(2.5) in Python?
232.5- An error
Solution
Answer: 2
With ties (x.5), round goes to the even neighbour: round(2.5) is 2 and round(3.5) is 4. It is a trap that turns up in more than one quiz.
What does this code print?
x = 0 print(x != 0 and 10 / x > 1)
False- A
ZeroDivisionError True0
Solution
Answer: False
and and or evaluate with short-circuiting: if the first part of an and is false, they already know the result and do not look at the second. That is how you protect a division: «check before you divide».
Complete to turn 135 seconds into minutes and seconds (2 min 15 s):
minutes = 135 ____ 60 and seconds = 135 ____ 60
Words:
- %
- //
- **
- /
Solution
minutes = 135 // 60 and seconds = 135 % 60
135 // 60 = 2 minutes and 135 % 60 = 15 seconds. With / you would get 2.25, which is not minutes and seconds.
What does this program print?
print("ha" * 3 + "!")hahaha!ha3!- An error
ha ha ha !
Solution
Answer: hahaha!
With text, + concatenates and * n repeats n times. The multiplication goes first: 'ha' * 3 = 'hahaha', and then the '!' is joined on.
What is -7 // 2?
-4-3-3.53
Solution
Answer: -4
-7 / 2 = -3.5, and // goes to the integer below: -4. With positive numbers it looks as if it «removes decimals», but with negatives you can see the difference.
Watch it step by step in Python Tutor
Let’s watch the or from snippet 3 live. Copy this into Python Tutor, choose Python 3 and step through it line by line:
method = "card" # Buggy version: or returns "bizum", which is truthy wrong = method == "cash" or "bizum" # What Python does inside, step by step left = method == "cash" # False wrong_2 = left or "bizum" # False or "bizum" → "bizum" # Correct version: a full comparison on each side right = method == "cash" or method == "bizum" print(wrong, wrong_2, right)
Output:
bizum bizum False
What to look at step by step:
- After the
wrongline, the variable appears with the value"bizum": astr, not abool. That is the symptom. leftisFalse: the comparison with “cash” does happen, and it is false.wrong_2repeats the sum in two steps and gives"bizum"again:False or "bizum"returns the second operand.rightisFalse, a realbool: with the full comparison on both sides, “card” is not accepted.- Change
methodto"bizum"and run it again:rightbecomesTrueandwrongis still"bizum". With any method,wrongwould be the same.
Cheat sheet: Python operators
The essentials in 30 seconds: / always gives a float; // is the quotient rounded down and % the remainder (and b * (a // b) + a % b == a). Comparisons give a bool; != is “not equal”. and and or stop as soon as they know the answer and return one of their operands. Decimals are not compared with ==: cents or math.isclose. When in doubt, parentheses.
| I want… | Code | Result |
|---|---|---|
| Divide with decimals | 7 / 2 | 3.5 |
| How many groups fit | 17 // 5 | 3 |
| What is left over | 17 % 5 | 2 |
| Both | divmod(17, 5) | (3, 2) |
| Power and root | 2 ** 10, 9 ** 0.5 | 1024, 3.0 |
| Even or odd | n % 2 == 0 | True if even |
| Last digit | 2847 % 10 | 7 |
| Drop the last digit | 2847 // 10 | 284 |
| Cents to euros | f"{c // 100}.{c % 100:02d} €" | 10.91 € with 1091 |
| Seconds to h, min, s | s // 3600, s % 3600 // 60, s % 60 | 1 2 3 with 3723 |
| Not equal | price != 100 | True |
| Inside a range | 0 <= grade <= 100 | True |
| Compare decimals | math.isclose(0.1 + 0.2, 0.3) | True |
| Default value | name or "Anonymous" | 'Anonymous' if name is "" |
| Protect a division | b != 0 and a / b > 3 | no ZeroDivisionError |
| Several options | method in ("cash", "bizum") | True or False |
Precedence (highest to lowest): () → ** → sign -x → * / // % → + - → comparisons → not → and → or. ** groups from right to left (2 ** 3 ** 2 = 512); the rest, from left to right.
Exercise templates:
| Exercise | Template | With today’s data |
|---|---|---|
Bands without if | max(0, months - 12) | 3 discounted months |
| Bool as a number | fee - discount * has_discount | 2699 |
| Time | h, r = divmod(s, 3600); m, s = divmod(r, 60) | 1, 2, 3 |
| Next level | (g < 70 and "Merit") or (g < 90 and "Distinction") or "…" | 'Distinction' |
| Digits | n // 10 ** k % 10 | digit at position k |
| Change | pieces, rest = divmod(rest, value) in a cascade | 5 pieces for 1257 |
| Symptom | Likely cause |
|---|---|
You get 5.0 instead of 5 | You used /; for an integer, // |
-7 // 2 gives −4 and you expected −3 | // rounds down; to cut, int(-7 / 2) |
0.1 + 0.2 == 0.3 gives False | Binary decimals; use math.isclose or cents |
| The average comes out huge | Missing parentheses: (a + b + c) / 3 |
-2 ** 2 gives −4 | ** goes before the sign; write (-2) ** 2 |
An or accepts anything | x == a or b; write x == a or x == b |
ZeroDivisionError with an and | The division comes before the check; swap them |
NameError with += | The variable was not initialised (total = 0) |
Before handing it in, ask yourself:
- Is each division the right one (
/for decimals,//for “how many fit”)? - Is there any
==between twofloatvalues? If so, can you use cents ormath.isclose? - Do the formulas with addition and multiplication have the parentheses you would use by hand?
- Does every
orand everyandhave a full comparison on both sides?
And with that you have operators mastered. The next step is Python conditionals: you will see how if, elif and else turn these comparisons into decisions, and how exercise 3 becomes much more readable. If you want to go over where we came from, here is variables and data types in Python.
Did this article help you? Here is how you can support SergioLearns →




The hardware that holds up the operating system — theory with my IC2 assignment on the printer, an interrupt that stops everything and a cache that hits (and the infinite loop that didn’t freeze my laptop)