Zuri is flying to Lisbon. Her suitcase is full, the airline allows 23 kg, and she has no idea what the bag weighs. At the desk, an agent will put it on a scale and make a decision in about two seconds: pass it, charge for it, or send her away to repack.
That agent runs a small program in their head. It adds up weights, compares the total to a limit, combines a few yes/no questions, and picks one of three outcomes. Python does the same work with two tools: operators, which calculate and compare, and conditionals, which choose what happens next.
This article builds the agent's program one piece at a time. Each snippet was run on Python 3.12, and each output block shows what the terminal printed.
Arithmetic operators: weighing the bag
Python has seven arithmetic operators. Five behave like the keys on a phone calculator. Two, // and %, do work a calculator hides.
| Operator | Name | At the desk |
|---|---|---|
+ |
addition | add up everything in the bag |
- |
subtraction | room left before the limit |
* |
multiplication | six shirts at 350 g each |
/ |
true division | grams to kilograms (always gives a decimal) |
// |
floor division | how many whole items still fit |
% |
remainder (modulo) | what is left after that |
** |
power | volume of a cube-shaped packing box |
Zuri weighs her things in grams. Whole numbers keep the arithmetic clean, a habit worth copying whenever the quantities allow it:
# Everything in grams, so the numbers stay whole
empty_bag = 3500
shirts = 6
shirt = 350
shoes = 1200
laptop = 2100
limit = 23000
total = empty_bag + shirts * shirt + shoes + laptop
spare = limit - total
more_shirts = spare // shirt
left_over = spare % shirt
print("Packed weight:", total, "g")
print("Spare room:", spare, "g")
print("Extra shirts that fit:", more_shirts)
print("Gap after that:", left_over, "g")
print("Packed weight in kilograms:", total / 1000)
$ python packing.py
Packed weight: 8900 g
Spare room: 14100 g
Extra shirts that fit: 40
Gap after that: 100 g
Packed weight in kilograms: 8.9
The bag weighs 3,500 + 6 × 350 + 1,200 + 2,100 = 8,900 g. That leaves 14,100 g of room. Dividing 14,100 by 350 gives about 40.3, and // keeps only the 40 whole shirts. The % operator gives the part that did not fit: 100 g. The two operators work as a pair. One counts the full units, the other reports the remainder.
The last line uses /, which always returns a decimal number, even when the division comes out even.
Floor division rounds toward negative infinity, not toward zero. Positive numbers hide this, and negative ones show it:
print("7 / 2 =", 7 / 2)
print("7 // 2 =", 7 // 2)
print("7 % 2 =", 7 % 2)
print("2 ** 10 =", 2 ** 10)
print("-7 // 2 =", -7 // 2)
print("-7 % 2 =", -7 % 2)
print("35 ** 3 =", 35 ** 3)
$ python operators.py
7 / 2 = 3.5
7 // 2 = 3
7 % 2 = 1
2 ** 10 = 1024
-7 // 2 = -4
-7 % 2 = 1
35 ** 3 = 42875
The answer to -7 // 2 is -4, not -3. The pair still holds together, because (a // b) * b + a % b always equals a: (-4 × 2) + 1 = -7. The last line is the volume in cubic centimetres of a packing cube 35 cm on each side.
Precedence: multiplication goes first
Python follows school arithmetic. Power goes first, then *, /, // and %, then + and -. A misplaced parenthesis changes the answer without any warning:
empty_bag = 3500
shirts = 6
shirt = 350
wrong = (empty_bag + shirts) * shirt
right = empty_bag + shirts * shirt
print("wrong:", wrong)
print("right:", right)
$ python precedence.py
wrong: 1227100
right: 5600
The wrong line adds the bag weight to the shirt count (3,500 + 6, a meaningless sum) and then multiplies by 350. The right line lets * run first, which is what Zuri meant. When you doubt the order, add parentheses. They cost nothing and show the next reader what you meant.
Comparison operators: asking the scale a question
A comparison asks a yes/no question and answers with True or False. Python has six: ==, !=, <, >, <=, >=.
A single = stores a value. A double == asks whether two values are equal. Confusing the two is the most common typo in the language, and we will watch Python catch it at the end.
Python also lets you chain comparisons the way you would write them on paper:
total = 8900
limit = 23000
print("total <= limit:", total <= limit)
print("total == limit:", total == limit)
print("total != limit:", total != limit)
print("0 < total <= limit:", 0 < total <= limit)
print("0.1 + 0.2 =", 0.1 + 0.2)
print("0.1 + 0.2 == 0.3:", 0.1 + 0.2 == 0.3)
print('"apple" < "banana":', "apple" < "banana")
print('"Banana" < "apple":', "Banana" < "apple")
$ python comparisons.py
total <= limit: True
total == limit: False
total != limit: True
0 < total <= limit: True
0.1 + 0.2 = 0.30000000000000004
0.1 + 0.2 == 0.3: False
"apple" < "banana": True
"Banana" < "apple": True
Line by line:
-
8900 <= 23000isTrue. The bag is under the limit. -
8900 == 23000isFalse. It is not exactly at the limit. -
8900 != 23000isTrue. The mirror image of the line above. -
0 < total <= limitisTrue. A chain reads like the inequality from a maths lesson. Python treats it as0 < total and total <= limit, and it readstotalonly once. -
0.1 + 0.2prints as0.30000000000000004. Computers store decimals in binary, and 0.1 has no exact binary form, so a tiny error creeps in. - That error is why
0.1 + 0.2 == 0.3isFalse. Avoid testing decimals for exact equality when they came from arithmetic. This is one reason Zuri weighed everything in whole grams. -
"apple" < "banana"isTrue. Text compares letter by letter, so a comes before b. -
"Banana" < "apple"is alsoTrue, which surprises people. Python compares character codes, and every capital letter has a lower code than every small letter. Convert text to one case before you compare it.
Logical operators: combining questions
One comparison rarely settles a decision. Zuri's carry-on must weigh under 7 kg and fit either under the seat or in the overhead bin. That is three questions joined together.
Python has three logical operators. and is true when both sides are true. or is true when at least one side is. not flips an answer. When they mix, not binds first, then and, then or.
carry_on = 6200
fits_under_seat = False
fits_in_bin = True
light_enough = carry_on <= 7000
fits_somewhere = fits_under_seat or fits_in_bin
print("Light enough:", light_enough)
print("Fits somewhere:", fits_somewhere)
print("Carry-on accepted:", light_enough and fits_somewhere)
print("Not under the seat:", not fits_under_seat)
$ python logic.py
Light enough: True
Fits somewhere: True
Carry-on accepted: True
Not under the seat: True
The carry-on weighs 6,200 g, so light_enough is True. It does not fit under the seat but it does fit in the bin, so or gives True for fits_somewhere. Both halves of the final and are True, so the bag is accepted. The last line shows not turning False into True.
Short-circuit evaluation
Python reads and from left to right and stops at the first False, because nothing on the right can change the result. The same applies to or, which stops at the first True. That makes and a guard:
total_g = 8900
bags = 0
print("Safe check:", bags != 0 and total_g / bags > 20000)
print("Unsafe check:", total_g / bags > 20000)
$ python short_circuit.py
Safe check: False
Traceback (most recent call last):
File "/home/you/short_circuit.py", line 5, in <module>
print("Unsafe check:", total_g / bags > 20000)
~~~~~~~~^~~~~~
ZeroDivisionError: division by zero
Suppose a bag count of zero slips in. In the safe check, bags != 0 is False, so Python never evaluates the division on the right, and the program prints False. In the unsafe check, Python tries to divide by zero and stops with a ZeroDivisionError. Put the cheap, protective test first, and let the risky one follow.
and and or return a value, not only True or False
First, a word on what "truthy" means. Python does not need a True or False to make a decision. In a condition, every value counts as one or the other. A value that counts as False is called falsy. A value that counts as True is called truthy. The falsy values are few: 0, 0.0, the empty string "", and False itself. Everything else is truthy, including negative numbers and any string with at least one character in it.
bags = 0
if bags:
print("Bags counted:", bags)
else:
print("No bags to check")
name = "Zuri"
if name:
print("Hello,", name)
else:
print("No name given")
change = -5
if change:
print("-5 is truthy")
$ python truthy.py
No bags to check
Hello, Zuri
-5 is truthy
A bag count of 0 is falsy, so if bags: reads as "if there are any bags" and the else branch runs. A name with text in it is truthy. The surprise is -5: it is not zero, so it is truthy. Among numbers, only zero is falsy.
Now back to and and or. They hand back one of their operands, and they use truthiness to choose which. or gives back the first truthy value, or the last value if none is truthy. and gives back the first falsy value, or the last value if all are truthy. This gives a neat way to supply a default:
name = ""
print(name or "Passenger")
name = "Zuri"
print(name or "Passenger")
$ python default_name.py
Passenger
Zuri
The empty name is falsy, so or moves on to "Passenger". The name "Zuri" is truthy, so or stops there and returns it.
The bug nearly every beginner writes once
Zuri wants to know what to pack. She writes what she would say aloud: "if the city is Lisbon or Porto, take a light jacket." She is going to Oslo:
city = "Oslo"
if city == "Lisbon" or "Porto":
print("Pack a light jacket")
else:
print("Pack a warm coat")
if "Porto":
print("A non-empty string counts as True")
if city == "Lisbon" or city == "Porto":
print("Pack a light jacket")
else:
print("Pack a warm coat")
$ python jacket.py
Pack a light jacket
A non-empty string counts as True
Pack a warm coat
Oslo is cold, and the first check still says to pack a light jacket. Python reads city == "Lisbon" or "Porto" as (city == "Lisbon") or ("Porto"). The left side is False, so or moves on to "Porto", a non-empty string, which is truthy. The condition passes for every city on Earth. The second if proves the point: a bare non-empty string passes as True. The fix is the third block, where each side of the or makes its own full comparison.
if, elif, else: choosing an outcome
A conditional statement tells Python which block of code to run. Python tests the conditions from top to bottom. It runs the first block whose condition is true, skips the rest, and moves on. The else block runs only if nothing above it matched. Exactly one block runs. Indentation marks which lines belong to which branch.
Here is the agent's decision. Up to 23 kg is free, 23 to 32 kg costs $12 for each extra kilo, and anything heavier is refused. Zuri's brother Baraka has a 26 kg bag:
weight_kg = 26
if weight_kg <= 23:
print("Free")
elif weight_kg <= 32:
extra = weight_kg - 23
print("Extra kilos:", extra)
print("Fee in dollars:", extra * 12)
else:
print("Refused: repack")
$ python check_bag.py
Extra kilos: 3
Fee in dollars: 36
Trace it. Is 26 at most 23? No. Is it at most 32? Yes, so Python works out 26 - 23 = 3 extra kilos, prints the fee of 3 × 12 = 36, and never looks at else. Change the first line and run the script again to see the other branches:
# weight_kg = 18
$ python check_bag.py
Free
# weight_kg = 23
$ python check_bag.py
Free
# weight_kg = 32
$ python check_bag.py
Extra kilos: 9
Fee in dollars: 108
# weight_kg = 35
$ python check_bag.py
Refused: repack
Note the boundaries. A bag of exactly 23 kg is free, and one of exactly 32 kg still pays, because both tests use <=. Whether the limit itself counts as in or out is a decision, and the operator you choose makes it.
The second test does not say "more than 23 and at most 32". It can say less, because by the time Python reaches it, the first test has already failed. The order of the tests carries information. Swap two of them and the program changes meaning:
weight_kg = 26
if weight_kg <= 32:
extra = weight_kg - 23
print("Extra kilos:", extra)
print("Fee in dollars:", extra * 12)
elif weight_kg <= 23:
print("Free")
else:
print("Refused: repack")
# weight_kg = 26
$ python broken_check.py
Extra kilos: 3
Fee in dollars: 36
# weight_kg = 18
$ python broken_check.py
Extra kilos: -5
Fee in dollars: -60
The 26 kg bag still gets the right answer, which is how this bug survives testing. The 18 kg bag does not: it is charged minus $60. The test weight_kg <= 32 is true for every light bag, so the chain stops there and the free case never gets its turn.
The rule: when you test with
<or<=, start with the smallest limit. When you test with>or>=, start with the largest.
elif is not the same as a second if
A chain of elif picks one winner. A row of separate if statements checks each condition on its own, so more than one block can run. Both forms are correct. They answer different questions:
weight_kg = 30
print("Two separate if statements:")
if weight_kg > 23:
print(" over the free limit")
if weight_kg > 25:
print(" over the warning line")
print("One if/elif chain:")
if weight_kg > 25:
print(" over the warning line")
elif weight_kg > 23:
print(" over the free limit")
$ python if_vs_elif.py
Two separate if statements:
over the free limit
over the warning line
One if/elif chain:
over the warning line
A 30 kg bag is over both lines. The separate if statements report both. The chain stops at the first match and reports one. Use separate if statements when the cases can overlap and you want every match. Use elif when the cases compete and only one should apply.
Putting it together: the check-in desk
Now the full program for one passenger. Frequent flyers get 5 extra kilos on the checked bag. Each passenger has a checked bag and a carry-on, and the desk reports the fee and whether the passenger can move on to security. It uses all three kinds of operator and every form of if.
name = "Zuri"
checked_kg = 18
carry_on_kg = 6
is_member = False
free_kg = 23
max_kg = 32
fee_per_kg = 12
carry_on_max_kg = 7
member_bonus_kg = 5
allowed_kg = free_kg
if is_member:
allowed_kg = free_kg + member_bonus_kg
extra_kg = checked_kg - allowed_kg
refused = checked_kg > max_kg
carry_ok = carry_on_kg <= carry_on_max_kg
print("Passenger:", name or "Unnamed")
print("Checked bag allowance:", allowed_kg, "kg")
if refused:
print("Checked bag: refused, repack")
elif extra_kg > 0:
print("Checked bag: extra", extra_kg, "kg, fee in dollars:", extra_kg * fee_per_kg)
else:
print("Checked bag: free")
if carry_ok:
print("Carry-on: OK")
else:
print("Carry-on: too heavy by", carry_on_kg - carry_on_max_kg, "kg")
if not refused and carry_ok:
print("Result: go to security")
else:
print("Result: fix the problems above first")
$ python checkin.py
Passenger: Zuri
Checked bag allowance: 23 kg
Checked bag: free
Carry-on: OK
Result: go to security
Zuri's bag is under the limit and her carry-on is under 7 kg, so she goes through. To see the other branches, change the first four lines and run the script again. Baraka has a 26 kg bag and no membership:
# name = "Baraka", checked_kg = 26, carry_on_kg = 5
$ python checkin.py
Passenger: Baraka
Checked bag allowance: 23 kg
Checked bag: extra 3 kg, fee in dollars: 36
Carry-on: OK
Result: go to security
Baraka pays for three extra kilos, and a fee is a cost, not a failure, so he still goes to security. Next comes Imani, a member with the same 26 kg bag but an 8 kg carry-on. Last is a passenger who left the name blank and brought a 35 kg bag:
# name = "Imani", checked_kg = 26, carry_on_kg = 8, is_member = True
$ python checkin.py
Passenger: Imani
Checked bag allowance: 28 kg
Checked bag: free
Carry-on: too heavy by 1 kg
Result: fix the problems above first
# name = "", checked_kg = 35, carry_on_kg = 4
$ python checkin.py
Passenger: Unnamed
Checked bag allowance: 23 kg
Checked bag: refused, repack
Carry-on: OK
Result: fix the problems above first
Imani's allowance is 28 kg, so her 26 kg bag is free, but her carry-on is one kilo over, so she must fix that first. The blank name falls back to "Unnamed" through or. At 35 kg, the bag is over the 32 kg maximum and gets refused.
Each tool did its own job. Arithmetic (+, -, *) produced the extra kilos and the fee. Comparison (<=, >) turned numbers into yes/no answers. Logic (not, and, or) combined those answers: not refused and carry_ok is the whole test for "go to security". The if/elif/else blocks turned the answers into actions.
What Python says when you slip
I promised to show what happens when = replaces ==:
weight_kg = 23
if weight_kg = 23:
print("exactly at the limit")
$ python typo.py
File "/home/you/typo.py", line 2
if weight_kg = 23:
^^^^^^^^^^^^^^
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
Python refuses to run the file and points at the exact expression. Recent versions even suggest the fix. The harder mistakes run without complaint: an or that is always true, a chain in the wrong order, a decimal compared with ==. No error message flags them. You find them by testing the edge cases, which is what the exercises below ask you to do.
Try it yourself
Copy checkin.py and change it.
- Set
checked_kgto exactly 23 and predict what the program prints, then run it. Now change one operator so that 23 kg costs a fee, and check that nothing else breaks. - Add a rule: a carry-on counts as too heavy above 7 kg, unless the passenger is a member, who may carry 10 kg. Combine
andandorin one condition, and add parentheses to show your intent. - Set
checked_kgto 32 andcarry_on_kgto 7. Which messages do you expect? Run it and check whether the order of yourif/elifchain gives them.
If you build something with these tools, such as a parking meter, a bus fare calculator or a school canteen till, tell me in the comments what decision it makes. And tell me which of the bugs above you have already written yourself.
Top comments (0)