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.

How to use this article

  1. Read the statement and the expected output. The data are fixed in variables at the top (no input()), as in the practice.
  2. Try to solve it in your editor. If you have been stuck for more than 10 minutes, open the hint.
  3. Compare with the commented solution. Not just whether it gives the same: look at why it is written that way.
  4. 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:

  1. It evaluates the left side: method == "cash" → False.
  2. As the left side is false, or returns the right side as it is: the text "bizum".
  3. A non-empty text is truthy, so inside an if it would count as “accepted”. With any method.
Why method == "cash" or "bizum" always acceptsAt the top, what the reader thinks: compare method with "cash" or with "bizum". Below, what Python does: it evaluates method == "cash", which with "card" is False, and since that is falsy, or returns the right-hand operand, "bizum", without comparing it with anything. Non-empty text is truthy, so everything is accepted.What you readmethod == "cash" or "bizum"is method "cash" or "bizum"?What Python does(method == "cash")method = "card" → Falseor"bizum"non-empty text = truthyFalse → move on to the rightreturns "bizum" as it isaccepted is "bizum"Fix: method == "cash" or method == "bizum" → False
or does not share the == between both sides: it returns the first truthy value it finds, here the text "bizum".

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 round leaves 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 and max keeps the 0). So full_price_months is 15 − 3 = 12.
  • The bool trick: has_discount is True with 15 months. When multiplied, True is 1: 2999 - 300 * True = 2699. With 8 months it would be 2999 - 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 // 60 has 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.
  • :02d pads with zeros for the clock format.
  • The reverse check: 1 × 3600 + 2 × 60 + 3 = 3723.
  • The divmod version does the same in two steps and reads almost like a sentence: “divide by 3600 and keep the remainder; divide the remainder by 60”.
StepOperationResult
Hours3723 // 36001
Left after the hours3723 % 3600123
Minutes123 // 602
Seconds3723 % 603

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:

PieceSumValueCarries on?
grade < 50 and "Pass"73 < 50 is False; and stopsFalseYes, or looks at the next one
grade < 70 and "Merit"73 < 70 is FalseFalseYes
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.

DigitExpressionSumResult
Unitsn % 102847 % 107
Tensn // 10 % 10284 % 104
Hundredsn // 100 % 1028 % 108
Thousandsn // 10002847 // 10002

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 - amount is the only sum with a float, and straight after it is turned into cents with round(change * 100). Here the subtraction prints as 12.57, but you will not always be that lucky (try amount = 0.10 and handed_over = 0.30): round guarantees a correct integer whatever happens. With int(change * 100) you could end up one cent short if the float were 1256.9999…
  • divmod in a cascade: each line piece, rest = divmod(rest, value) stores how many pieces fit and overwrites rest with what is left, which is what the next line receives. It is a double assignment: divmod returns 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:

Breaking down the change with divmodIt starts from 1257 cents. For each note or coin, from 50 euros down to 1 cent, divmod gives how many pieces fit and the remainder. One 10-euro note fits (257 left), one 2-euro coin (57 left), one 50-cent coin (7 left), one 5-cent coin (2 left) and one 2-cent coin (0 left). The rest give 0.1257 cents of change, piece by piece50 €× 0left 125720 €× 0left 125710 €× 1left 2575 €× 0left 2572 €× 1left 571 €× 0left 5750 cent× 1left 720 cent× 0left 710 cent× 0left 75 cent× 1left 22 cent× 1left 01 cent× 0left 0pieces: 5 · check: 1000 + 200 + 50 + 5 + 2 = 1257
Each divmod takes what it can and passes the remainder on to the next piece; the ones that fit are highlighted in coral.
PiecedivmodFitLeft
10 €divmod(1257, 1000)1257
5 €divmod(257, 500)0257
2 €divmod(257, 200)157
1 €divmod(57, 100)057
50 centdivmod(57, 50)17
20 and 10 centdivmod(7, 20), divmod(7, 10)07
5 centdivmod(7, 5)12
2 centdivmod(2, 2)10

(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.

Question 1Short answer

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.

Question 2Single choice

What value does this code show?

a = True
b = False
print(not a or b and a)
  1. False
  2. True
  3. A syntax error
  4. 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.

Question 3Short answer

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.

Question 4Single choice

Which of these expressions is True when n is even?

  1. n % 2 == 0
  2. n / 2 == 0
  3. n // 2 == 1
  4. n % 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.

Question 5Match

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.5
  • 7 // 2 → 3
  • 7 % 2 → 1
  • 2 ** 3 → 8

/ is division with decimals, // whole quotient, % remainder and ** power. 2 ** 3 = 2 × 2 × 2 = 8 (not 6: it is not a multiplication).

Question 6Single choice

A classmate wants to check whether colour is 'red' or 'blue' and writes colour == 'red' or 'blue'. What happens?

  1. The condition is always true, because 'blue' on its own counts as True
  2. It works fine
  3. A syntax error
  4. 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.

Question 7Short answer

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.

Question 8True or false

True or false?

  1. 5 == 5.0 is True.
  2. '5' == 5 is True.
  3. 3 < 5 < 9 is True.
  4. = and == do the same thing.
Solution
  • T — 5 == 5.0 is True.
  • F — '5' == 5 is True.
  • T — 3 < 5 < 9 is True.
  • 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.

Question 9Single choice

What is round(2.5) in Python?

  1. 2
  2. 3
  3. 2.5
  4. 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.

Question 10Single choice

What does this code print?

x = 0
print(x != 0 and 10 / x > 1)
  1. False
  2. A ZeroDivisionError
  3. True
  4. 0
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».

Question 11Fill in the gaps

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.

Question 12Single choice

What does this program print?

print("ha" * 3 + "!")
  1. hahaha!
  2. ha3!
  3. An error
  4. 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.

Question 13Single choice

What is -7 // 2?

  1. -4
  2. -3
  3. -3.5
  4. 3
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:

  1. After the wrong line, the variable appears with the value "bizum": a str, not a bool. That is the symptom.
  2. left is False: the comparison with “cash” does happen, and it is false.
  3. wrong_2 repeats the sum in two steps and gives "bizum" again: False or "bizum" returns the second operand.
  4. right is False, a real bool: with the full comparison on both sides, “card” is not accepted.
  5. Change method to "bizum" and run it again: right becomes True and wrong is still "bizum". With any method, wrong would 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…CodeResult
Divide with decimals7 / 23.5
How many groups fit17 // 53
What is left over17 % 52
Bothdivmod(17, 5)(3, 2)
Power and root2 ** 10, 9 ** 0.51024, 3.0
Even or oddn % 2 == 0True if even
Last digit2847 % 107
Drop the last digit2847 // 10284
Cents to eurosf"{c // 100}.{c % 100:02d} €"10.91 € with 1091
Seconds to h, min, ss // 3600, s % 3600 // 60, s % 601 2 3 with 3723
Not equalprice != 100True
Inside a range0 <= grade <= 100True
Compare decimalsmath.isclose(0.1 + 0.2, 0.3)True
Default valuename or "Anonymous"'Anonymous' if name is ""
Protect a divisionb != 0 and a / b > 3no ZeroDivisionError
Several optionsmethod 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:

ExerciseTemplateWith today’s data
Bands without ifmax(0, months - 12)3 discounted months
Bool as a numberfee - discount * has_discount2699
Timeh, 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'
Digitsn // 10 ** k % 10digit at position k
Changepieces, rest = divmod(rest, value) in a cascade5 pieces for 1257
SymptomLikely cause
You get 5.0 instead of 5You 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 FalseBinary decimals; use math.isclose or cents
The average comes out hugeMissing parentheses: (a + b + c) / 3
-2 ** 2 gives −4** goes before the sign; write (-2) ** 2
An or accepts anythingx == a or b; write x == a or x == b
ZeroDivisionError with an andThe division comes before the check; swap them
NameError with +=The variable was not initialised (total = 0)

Before handing it in, ask yourself:

  1. Is each division the right one (/ for decimals, // for “how many fit”)?
  2. Is there any == between two float values? If so, can you use cents or math.isclose?
  3. Do the formulas with addition and multiplication have the parentheses you would use by hand?
  4. Does every or and every and have 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.

Keep goingAll Python articles

Did this article help you? Here is how you can support SergioLearns →

Sigue aprendiendo

Leave a Reply

Your email address will not be published. Required fields are marked *