Python operators — theory with the uni cafeteria, // and % for sharing things out and a 0.1 + 0.2 that is not 0.3 (and the sandwich bill that never added up)

The first time I used Python for something “real” was in the uni cafeteria. Laura and I had ordered a coffee (1.10 €) and a sandwich (2.20 €), we put 3.30 € on the table and, since we had just covered variables in FP1 (my first-year programming course), I wrote a five-line program to check we had paid the right amount. This is what I wrote:

# My first "cafeteria program": splitting a coffee and a sandwich
coffee = 1.10        # euros
sandwich = 2.20

total = coffee + sandwich
print("Total:", total)

paid = 3.30          # what we put on the table, counted by hand
print("Paid exactly?", paid == total)

per_person = total / 2
print("Each:", per_person)

And this is what Python answered:

Total: 3.3000000000000003
Paid exactly? False
Each: 1.6500000000000001

3.3000000000000003? We had not paid exactly? And 1.6500000000000001 each? The computer was not broken and Python does not add up badly: the problem was how I had used the operators. + and / on decimals, and a == to compare two numbers that “look” equal but are not. By the end of this article you will know exactly what happened and the three ways to avoid it.

In the variables and data types article you learned to store data in labelled boxes (int, float, str, bool), and I left you two promises: to explain //, % and ** (and why 5 / 2 gives 2.5) and to show you how to compare decimals safely. Today I keep them. We are going to see every operator you need in your first year: arithmetic, comparison, logical and compound assignment operators, plus the order in which Python applies them. All with the uni cafeteria as the example: prices, croissants that have to be shared out on trays and the till that fills up through the morning. Then, in the practice, you will use them in three complete programs.

What is an operator in Python?

An operator is a symbol that tells Python what to do with one or more values. In 3 + 5, the + is the operator and 3 and 5 are its operands. The whole thing (3 + 5) is an expression, and an expression always boils down to a single value: 8.

Notice something that looks trivial but is not: the result of an expression has a type. 3 + 5 gives an int, 5 / 2 gives a float and 5 > 3 gives a bool. Knowing which type comes out of each operator saves you half the mistakes in this topic.

FamilyOperatorsWhat they returnExample
Arithmetic+ - * / // % **A number120 + 95 → 215
Comparison== != < > <= >=True or False120 < 150 → True
Logicaland or notOne of their operands (usually a bool)True and False → False
Compound assignment+= -= *= /= //= %= **=Nothing: they change a variabletill += 120

We will go through them in this order, which is also the order you will use them in: first you calculate, then you compare and then you combine comparisons.

Arithmetic operators in Python: the everyday ones

There are seven. To see them with real data I will use the cafeteria prices in cents (a coffee costs 120 cents, a croissant 95). Later you will see that working in cents is not a quirk of mine: it is the professional way of handling money.

# The seven arithmetic operators with cafeteria prices, in cents
coffee = 120      # 1.20 €
croissant = 95    # 0.95 €

# f"{expression = }" prints the expression as written and its result
print(f"{coffee + croissant = }")     # addition
print(f"{coffee - croissant = }")     # subtraction
print(f"{3 * coffee = }")             # product
print(f"{coffee / 2 = }")             # true division: ALWAYS gives a float
print(f"{coffee // 50 = }")           # floor division: how many 50-cent coins fit
print(f"{coffee % 50 = }")            # remainder: what is left after those coins
print(f"{2 ** 10 = }")                # power
print(f"{9 ** 0.5 = }")               # exponent 0.5 = square root

When you run it:

coffee + croissant = 215
coffee - croissant = 25
3 * coffee = 360
coffee / 2 = 60.0
coffee // 50 = 2
coffee % 50 = 20
2 ** 10 = 1024
9 ** 0.5 = 3.0

f"{expression = }", with the = just before the closing brace, prints the expression as written and its result. It is perfect for quick tests: you do not have to type the label by hand and it never gets out of sync with the calculation.

Between the lines:

  • coffee + croissant and coffee - croissant do what you expect: 120 + 95 = 215 and 120 − 95 = 25. As both operands are int, the result is also an int.
  • 3 * coffee multiplies: three coffees are 360 cents.
  • coffee / 2 is ordinary division, the calculator one. Notice it gives 60.0, with its .0, even though 120 divided by 2 is exact. We will see why in a moment.
  • coffee // 50 is floor division: how many 50-cent coins fit completely into a coffee. Two fit (2 × 50 = 100).
  • coffee % 50 is the remainder: what is left after putting down those two coins. 120 − 100 = 20.
  • 2 ** 10 is the power: 2 to the power of 10, 1024. And 9 ** 0.5 is a trick worth knowing: raising to 0.5 is taking the square root, so it gives 3.0.

Classic mistake 1: confusing / with //

This is the mistake I saw most often in the FP1 exercises, and the promise I left you in the variables article: why does 5 / 2 give 2.5? Because in Python 3 the / operator is always true division: the result is a float, whether there are decimals or not. If you come from C or Java, where 5 / 2 between integers gives 2, this one will catch you for sure.

If what you want is to know how many times one number fits into another, you need //. Let’s share out 17 croissants on trays of 5:

# Sharing out 17 croissants on trays of 5
croissants = 17
per_tray = 5

trays = croissants // per_tray       # how many trays are filled completely
left_over = croissants % per_tray    # how many are left over
print(trays, "full trays, left over:", left_over)
print("Check:", trays * per_tray + left_over)   # it has to give 17 again
print("divmod does both:", divmod(croissants, per_tray))

# With / the type and the meaning change: you cannot carry 3.4 trays
print("17 / 5 =", croissants / per_tray)
print("10 / 2 =", 10 / 2, type(10 / 2))     # exact, but a float anyway
print("10 // 2 =", 10 // 2, type(10 // 2))
print("7.5 // 2 =", 7.5 // 2)               # if an operand is a float, // gives a float

# // rounds DOWN (towards minus infinity), not towards zero
print("-7 // 2 =", -7 // 2)
print("-7 % 2 =", -7 % 2)
print("int(-7 / 2) =", int(-7 / 2))         # int() cuts the decimals: towards zero

This is what it prints:

3 full trays, left over: 2
Check: 17
divmod does both: (3, 2)
17 / 5 = 3.4
10 / 2 = 5.0 <class 'float'>
10 // 2 = 5 <class 'int'>
7.5 // 2 = 3.0
-7 // 2 = -4
-7 % 2 = 1
int(-7 / 2) = -3

The first four lines of output are the ones you will use the most in your life:

  • 17 // 5 gives 3: 3 trays are filled completely.
  • 17 % 5 gives 2: 2 croissants are left over.
  • The check trays * per_tray + left_over gives 17 again (3 × 5 + 2 = 17). This equality always holds, and it is the best way to understand what // and % are: the quotient and the remainder from primary-school division.
  • divmod(17, 5) does both at once and returns the pair (3, 2). You will use it in the exercises to give change.

On the other hand, 17 / 5 gives 3.4, which is the correct answer to a different question: you cannot carry 3.4 trays to the counter. And look at the type lines: 10 / 2 gives 5.0 of type float, while 10 // 2 gives 5 of type int. Same value, different type.

Look at the drawing: at the top, the croissants being shared out; at the bottom, what happens with negative numbers, which is where // fools you.

Floor division, remainder and rounding downTop: 17 croissants shared out into 3 trays of 5 with 2 left over: 17 // 5 is 3 and 17 % 5 is 2. Bottom: a number line: 3.4 goes down to 3; -3.5 goes down to -4 with //, while int() cuts it to -3.17 croissants on trays of 5tray 1tray 2tray 3left over17 // 5 = 3 full trays17 % 5 = 2 left over17 / 5 = 3.4 (there is no 0.4 tray)// rounds DOWN, also with negatives-5-4-3-2-10123453.417 // 5 → 3-3.5-7 // 2 → -4 // (floor)int(-7 / 2) → -3 int() (towards 0)
// counts the full trays and % the ones left over; with negatives, // keeps going down to the left.
I want…OperatorExampleResult
An average, a price per person, a percentage/120 / 260.0 (float)
How many complete groups fit//17 // 53
What is left after sharing out%17 % 52
Both at oncedivmoddivmod(17, 5)(3, 2)

// does not drop the decimals: it rounds down. With positive numbers it is the same thing, but -7 // 2 gives -4, not -3, because −3.5 rounded down is −4. If what you want is to cut off the decimals (going towards zero), use int(-7 / 2), which gives -3. And if one operand is a float, // returns a float: 7.5 // 2 gives 3.0.

Classic mistake 2: % is not percentage

When you see % in Python, it does not mean “per cent”. It means modulo, the remainder of floor division. 120 % 50 is not “50 % of 120” (that would be 60); it is what is left when you take 50 away from 120 as many times as it fits: 20.

What is something so odd good for? For much more than it seems:

  • Even or odd: n % 2 == 0 is True if n is even. If the remainder of dividing by 2 is 0, nothing is left over.
  • Divisible by another number: 15 % 3 == 0 is True; 16 % 3 == 0 is False (1 is left over).
  • The last digit of a number: 2847 % 10 is 7. And 2847 // 10 drops that last digit (284). Combining both you will get every digit out in the exercises.
  • Formatting money: if you have 1091 cents, 1091 // 100 are 10 euros and 1091 % 100 are 91 cents. You will do it in the practice.

To calculate a real percentage, multiply and divide: 15 % of 1200 is 1200 * 15 / 100 (or 1200 * 15 // 100 if you want an integer).

With negative numbers, Python’s remainder takes the sign of the divisor: -7 % 2 gives 1, and 5 % -2 gives -1. It looks capricious, but it is what makes b * (a // b) + a % b == a always hold: 2 × (−4) + 1 = −7. In the practice you will check it with −17 and 5.

Comparison operators: they return True or False

Comparison operators ask something about two values and answer with a bool: True or False. They are the basis of every decision you will make in the next topic.

OperatorMeansExample with price = 120Result
==equal toprice == 120True
!=not equal toprice != 100True
<less thanprice < 150True
>greater thanprice > 150False
<=less than or equal toprice <= 120True
>=greater than or equal toprice >= 200False
# Comparison operators: they always return True or False
price = 120          # cents
budget = 150

print(f"{price < budget = }")
print(f"{price == 120 = }")
print(f"{price != 100 = }")                # != means "not equal to"
print(f"{0 <= price <= 200 = }")           # chained comparison: inside the range
print(f"{'coffee' == 'Coffee' = }")        # capital letters count
print(f"{5 == 5.0 = }")                    # int and float with the same value: equal
print(f"{'5' == 5 = }")                    # text versus number: False, no error
print(f"{'coffee' < 'juice' = }")          # texts: alphabetical order

Real output:

price < budget = True
price == 120 = True
price != 100 = True
0 <= price <= 200 = True
'coffee' == 'Coffee' = False
5 == 5.0 = True
'5' == 5 = False
'coffee' < 'juice' = True

What is worth a second look:

  • 0 <= price <= 200 is a chained comparison: Python reads it as 0 <= price and price <= 200. It checks whether a value is inside a range, and it is much more readable than the version with and.
  • 'coffee' == 'Coffee' is False: for Python, a capital letter is a different character. If you compare what a user types, normalise it first.
  • 5 == 5.0 is True: an int and a float with the same value are equal.
  • '5' == 5 is False and does not raise an error: a text is never equal to a number. It is exactly the silent bug from the variables article, when a value arrives from input() as text and you compare it with a number.
  • 'coffee' < 'juice' is True: texts are compared in alphabetical order (character by character, by their code, to be precise).

Not equal in Python: the != operator

A lot of people search for how to write “not equal” or “different from” in Python. It is !=, an exclamation mark stuck to an equals sign. It reads “is not equal to” and returns the opposite of ==: price != 100 is True because 120 is not 100. There is no <> (Python 2 had it; in Python 3 it is a SyntaxError) and no =!.

A very common use: divisor != 0 before dividing, which you will see in a moment with the logical operators.

Classic mistake 3: confusing = with ==

= stores a value in a variable. == asks whether two values are equal. They look so alike that, in a hurry, one slips in for the other. Look what happens if you write = instead of == in a condition:

price = 120
if price = 120:
    print("It is a coffee")

And the result:

  File "assign.py", line 2
    if price = 120:
       ^^^^^^^^^^^
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?

The good news is that Python 3 spells it out: “Maybe you meant ‘==’ … instead of ‘=’?”. The little ^^^^ arrows point at the part it does not understand. The bad news is that elsewhere the error does not show up: if you write price == 120 on a line of its own meaning to assign, Python compares, stores nothing and carries on happily.

Read it out loud. = reads “is set to” or “store” (price = 120: price is set to 120). == reads “is it equal to?” (price == 120: is price equal to 120?). If the sentence is a question, it is two equals signs.

Comparing decimals safely: why 0.1 + 0.2 is not 0.3

Back to the hook. The receipt did not add up because 1.10 + 2.20 gave 3.3000000000000003. The most famous case of the same problem is this one:

import math

# The problem: 0.1 and 0.2 cannot be stored exactly in binary
print(0.1 + 0.2)
print(0.1 + 0.2 == 0.3)

# With 20 decimal places you see what is really stored
print(f"{0.1:.20f}")
print(f"{0.2:.20f}")
print(f"{0.1 + 0.2:.20f}")
print(f"{0.3:.20f}")

# Solution 1: round before comparing
print(round(0.1 + 0.2, 2) == 0.3)

# Solution 2: math.isclose compares with a tolerance
print(math.isclose(0.1 + 0.2, 0.3))
print(math.isclose(1.10 + 2.20, 3.30))   # the receipt from the hook

# Solution 3: work in cents (integers, which are exact)
print(10 + 20 == 30)
print(110 + 220 == 330)

When you run it:

0.30000000000000004
False
0.10000000000000000555
0.20000000000000001110
0.30000000000000004441
0.29999999999999998890
True
True
True
True
True

What is going on? The computer stores float values in binary, and in binary 0.1 has no exact representation: it is like 1/3 in decimal (0.3333…), which never ends. So Python stores the closest number it can. With 20 decimal places you can see it: 0.1 is actually 0.10000000000000000555 and 0.2 is 0.20000000000000001110. When you add them, the tiny errors add up and the result (0.30000000000000004441) does not land in the same place as the 0.3 you type by hand (0.29999999999999998890).

Why 0.1 + 0.2 is not equal to 0.30.1 and 0.2 cannot be stored exactly in binary. The sum ends up as 0.30000000000000004441 and the 0.3 typed by hand as 0.29999999999999998890: two different points on the line, a tiny distance apart. == sees them as different; math.isclose accepts them because both fall inside its tolerance.What is really stored (20 decimal places)0.1 is stored as0.100000000000000005550.2 is stored as0.20000000000000001110math.isclose tolerance: both fall inside0.3 typed by hand0.299999999999999988900.1 + 0.20.30000000000000004441difference ≈ 0.000000000000000055510.1 + 0.2 == 0.3 → Falsemath.isclose(0.1 + 0.2, 0.3) → True10 + 20 == 30 → True (cents)
== demands exactly the same point; math.isclose accepts a band around it, and whole cents do not have the problem.

As the drawing shows, the difference is tiny (about 0.00000000000000005), but == demands that both values are exactly the same point on the line. That is why it gives False. And it is not a Python bug: it happens in Java, in C, in JavaScript and on your scientific calculator if it looks at enough decimal places.

You have three ways to defend yourself, and all three appear in the program:

  1. Round before comparing: round(0.1 + 0.2, 2) == 0.3 gives True. It works when you know how many decimal places matter (two, for money).
  2. Compare with a tolerance: math.isclose(a, b) returns True if the two numbers are close enough. You need import math at the top. It is the right way to compare results of calculations with decimals, and it is what fixes the receipt from the hook: math.isclose(1.10 + 2.20, 3.30) gives True.
  3. Do not use decimals: work in cents, with integers. 110 + 220 == 330 is always exact, because int values do not have this problem. It is what banks and online shops do, and what you will do in the practice.
SituationWhat to useExample
MoneyCents (int)110 + 220 == 330
Results of calculations with decimalsmath.isclosemath.isclose(average, 7.3)
Only a few decimal places matterroundround(total, 2) == 3.3
Showing it on screenFormatting, not roundf"{total:.2f}" → 3.30

Never compare two float values with == (or !=) if they come from a calculation. It will work with some data and fail with other data, and that kind of bug is the worst to track down.

Logical operators: and, or and not

Comparisons answer single questions. To combine them (“is it open and is there no queue?”) you use the logical operators:

aba and ba or bnot a
TrueTrueTrueTrueFalse
TrueFalseFalseTrueFalse
FalseTrueFalseTrueTrue
FalseFalseFalseFalseTrue

In short: and is only true if both are; or is true if at least one is; not flips it. So far, what you find in any book. Now comes what almost nobody explains in first year:

# Logical operators with the state of the cafeteria
is_open = True
queue = False

print(f"{is_open and not queue = }")     # can I go in now?
print(f"{is_open or queue = }")
print(f"{not is_open = }")

# and and or do not always return True/False: they return one of their operands
name = ""                                # the customer did not give a name
print(f"{name or 'Anonymous' = }")       # "" is falsy: or returns the second one
print(f"{'Laura' or 'Anonymous' = }")    # "Laura" is truthy: it keeps it
print(f"{0 or 5 = }", f"{3 and 7 = }", f"{'' and 99 = }")

# Short-circuit: if the left side decides, the right side is NOT evaluated
customers = 0
till = 0
print(f"{customers != 0 and till / customers > 5 = }")   # never gets to divide
print(f"{till / customers > 5 and customers != 0 = }")   # divides first: error

This is what it prints:

is_open and not queue = True
is_open or queue = True
not is_open = False
name or 'Anonymous' = 'Anonymous'
'Laura' or 'Anonymous' = 'Laura'
0 or 5 = 5 3 and 7 = 7 '' and 99 = ''
customers != 0 and till / customers > 5 = False
Traceback (most recent call last):
  File "logical.py", line 19, in <module>
    print(f"{till / customers > 5 and customers != 0 = }")   # divides first: error
             ~~~~~^~~~~~~~~~~
ZeroDivisionError: division by zero

and and or return values, not just True or False

Look at name or 'Anonymous': it does not give True or False, it gives 'Anonymous'. That is because in Python and and or return one of their operands:

  • x or y: if x is “truthy”, it returns x; otherwise it returns y.
  • x and y: if x is “falsy”, it returns x; otherwise it returns y.

And what is “truthy” or “falsy” for something that is not a bool? You saw it in the variables article with bool(): 0, 0.0, the empty text "" and None are falsy; almost everything else is truthy. So:

  • name or 'Anonymous' with name = "" returns 'Anonymous', because the empty text is falsy. With 'Laura', it returns 'Laura'. It is the shortest way to set a default value.
  • 0 or 5 gives 5; 3 and 7 gives 7 (3 is truthy, so and returns the second one); '' and 99 gives '' (the empty text is falsy and and keeps it).

When the operands are bool, as in is_open and not queue, none of this matters: the result is the good old True or False.

Short-circuit evaluation

The last two lines of the program are the most important. With customers = 0:

  • customers != 0 and till / customers > 5 gives False without an error. Python looks at the left side first: customers != 0 is False, and “false and anything” is false. So it does not even evaluate the right side. The division by zero never happens.
  • till / customers > 5 and customers != 0 has the same pieces the other way round, and it blows up with ZeroDivisionError: Python starts on the left, tries to divide by zero and stops there.

This is called short-circuit evaluation: and stops as soon as it finds something false and or stops as soon as it finds something true. It is not an exam detail: it is a tool. Put the protecting check first (divisor != 0, “the list is not empty”…) and the dangerous operation after it.

OperatorStops when the left side is…And then returns…
andfalsythe left side
ortruthythe left side

Writing method == "cash" or "bizum" to ask “is it cash or bizum?”. Python reads it as (method == "cash") or "bizum", and since "bizum" is a non-empty text (truthy), the result is never false. You have to repeat the comparison: method == "cash" or method == "bizum". We take it apart in the exercises.

Compound assignment operators: the shortcut that saves time

Very often you want to change a variable based on its own value: add a sale to the till, count one more coffee. Writing till = till + 120 works, but Python has a shortcut: till += 120. There is one for every arithmetic operator.

# The cafeteria till through the morning (cents)
till = 0
coffees = 0

till += 120      # a coffee        (till = till + 120)
coffees += 1
till += 95       # a croissant
till += 120      # another coffee
coffees += 1
till -= 50       # we give back 50 cents of wrong change
print("Till:", till, "| coffees sold:", coffees)

till *= 2        # "what if the whole morning were like this?"
print("Doubled:", till)
till //= 3       # split over 3 shifts (integer)
print("Per shift:", till)
till /= 2        # /= turns it into a float, even if the division is exact
print("Half an hour:", till, type(till))

Real output:

Till: 285 | coffees sold: 2
Doubled: 570
Per shift: 190
Half an hour: 95.0 <class 'float'>

This is how the till evolves line by line (the trace table I recommend you always make when a variable changes several times):

LineOperationtill aftercoffees after
starttill = 0, coffees = 000
coffeetill += 120, coffees += 11201
croissanttill += 952151
coffeetill += 120, coffees += 13352
refundtill -= 502852
doubledtill *= 25702
three shiftstill //= 31902
half an hourtill /= 295.02

The sums: 120 + 95 + 120 − 50 = 285; 285 × 2 = 570; 570 // 3 = 190 (exact); 190 / 2 = 95.0. Look at the last line of the output: after /=, the till is already a float, just as happened with /. The shortcuts behave exactly like the operator inside them.

+= is the operator you will type most in FP1: every counter (coffees += 1) and accumulator (total += price) in loops uses it. And it also works with texts: message += "!" adds an exclamation mark at the end.

Using += on a variable that does not exist yet. sales += 1 without having written sales = 0 first gives NameError, because Python needs the previous value to add 1 to it. Always initialise your counters.

Operator precedence: order matters

When an expression has several operators, which one goes first? As in maths, multiplication goes before addition. But Python has more operators than school maths, and some cases are surprising:

# Precedence: what is worked out first when there are several operators
print(3 + 8 * 2)            # * before +
print((3 + 8) * 2)          # parentheses rule
print(-2 ** 2)              # ** before the leading -: -(2 ** 2)
print((-2) ** 2)
print(2 ** 3 ** 2)          # ** groups from right to left: 2 ** 9
print(10 - 4 - 3)           # same level: from left to right
print(17 % 10 // 2)         # % and // too: (17 % 10) // 2

# The receipt: 3 coffees, 2 croissants and a 50-cent voucher split in half
total = 3 * 120 + 2 * 95 - 50 // 2
print("Total:", total)

print(True or False and False)    # and before or
print(not 120 == 100)             # not is applied after ==

When you run it:

19
22
-4
4
512
3
3
Total: 525
True
True

Line by line:

  • 3 + 8 * 2 gives 19, not 22: first 8 × 2 = 16 and then 3 + 16. With parentheses, (3 + 8) * 2 does give 22.
  • -2 ** 2 gives −4. It is the most famous trap: ** has higher priority than the minus sign in front of it, so Python works out −(2²). If you want the square of −2, write (-2) ** 2, which gives 4.
  • 2 ** 3 ** 2 gives 512: ** is the only arithmetic operator that groups from right to left, so it is 2 ** (3 ** 2) = 2 ** 9.
  • 10 - 4 - 3 gives 3: within the same level you go from left to right, (10 − 4) − 3. From right to left it would give 9.
  • 17 % 10 // 2 gives 3: % and // are on the same step, so first 17 % 10 = 7 and then 7 // 2 = 3.
  • True or False and False gives True: and goes before or, so it is True or (False and False).
  • not 120 == 100 gives True: not goes after comparisons, so it is not (120 == 100).

And the receipt: 3 coffees, 2 croissants and a 50-cent voucher we split in half. The drawing shows how it is solved step by step and which step of the ladder each operator is on:

Evaluation order of an expressionOn the left, the expression 3 * 120 + 2 * 95 – 50 // 2 is solved in five steps: first 3 * 120 = 360, then 2 * 95 = 190, then 50 // 2 = 25, then 360 + 190 = 550 and finally 550 – 25 = 525. On the right, Python's precedence ladder: **, sign, multiplicative operators, addition and subtraction, comparisons, not, and, or.The receipt, step by stepWho goes first3 * 120 + 2 * 95 – 50 // 2360 + 2 * 95 – 50 // 2step 1360 + 190 – 50 // 2step 2360 + 190 – 25step 3550 – 25step 4525step 51**2-x (sign)3* / // %4+ –5== != < > <= >=6not7and8orsame level: left to right (except **)
The operators at the top of the ladder go first; on the same step you go from left to right.

The sums by hand: 3 × 120 = 360; 2 × 95 = 190; 50 // 2 = 25; 360 + 190 = 550; 550 − 25 = 525. First all the * and // (from left to right) and then the addition and subtraction.

Level (highest to lowest priority)Operators
1( ) parentheses
2** (right to left)
3-x, +x (sign)
4* / // %
5+ -
6== != < > <= >=
7not
8and
9or

You do not need to memorise the whole table. Knowing that ** goes before the sign, that comparisons go before not, and and or (in that order) and that when in doubt you add parentheses covers 99 % of cases. An extra pair of parentheses never breaks anything and makes code easier to read.

Common mistakes with operators

A summary of the ones we have seen, so you have them together:

Using / when you need a whole number of something (trays, tables, groups). / always gives a float; use // and, for what is left over, %.

Thinking % means “per cent”. It is the remainder of the division. A percentage is calculated with * percentage / 100.

Comparing float values with ==. Use cents, math.isclose or round.

Forgetting parentheses in averages: a + b + c / 3 only divides c. The right way is (a + b + c) / 3.

-2 ** 2 gives −4. If the negative number is the base, put it in parentheses.

Watch it step by step in Python Tutor

Python Tutor runs your code line by line and draws every variable with its value at each moment. Copy this program, choose Python 3 and press Visualize Execution:

price = 120
quantity = 3
voucher = 50

subtotal = quantity * price
total = subtotal - voucher // 2        # // before -
average = total / quantity             # / always gives a float
exact = average * quantity == total

customers = 0
ok = customers != 0 and total / customers > 100   # never gets to divide

name = ""
customer = name or "Anonymous"         # or returns a value, not a bool

print(total, average, exact, ok, customer)

Output:

335 111.66666666666667 True False Anonymous

What to look at step by step:

  1. When total = subtotal - voucher // 2 runs, total appears straight away with 335. Python does not store the result of voucher // 2 in any variable: it works it out (25) before the subtraction, because of precedence, and uses it immediately. 360 − 25 = 335.
  2. average appears as 111.66666666666667: a float, even though all the operands were int. That is the /.
  3. exact comes out True: here average * quantity gives exactly 335.0 again. You will not always be that lucky with decimals; that is why math.isclose exists.
  4. When you reach the ok line, notice the program moves on without an error even though customers is 0: customers != 0 is False and and no longer evaluates the division. Swap the two parts and you will see the ZeroDivisionError live.
  5. customer is 'Anonymous', not True: or returned its second operand because name was an empty text.

Summary

OperatorWhat it doesExampleResult
/True division (always float)5 / 22.5
//Floor division (rounds down)17 // 53
%Remainder17 % 52
**Power2 ** 101024
==, !=Equal, not equal120 != 100True
Chained <=Inside a range0 <= 120 <= 200True
and, orCombine, with short-circuit"" or "Anonymous"'Anonymous'
notNegatenot TrueFalse
+= and friendsChange a variable based on its valuetill += 120till goes up by 120
math.iscloseCompare decimals with a tolerancemath.isclose(0.1 + 0.2, 0.3)True

Three ideas to take away:

  1. Every operator returns a type. / always gives a float, // and % give the quotient and the remainder (and b * (a // b) + a % b == a), and comparisons give a bool.
  2. Never compare decimals with ==. Work in cents when it is money and use math.isclose when it is a calculation.
  3. and and or stop as soon as they know the answer and return one of their operands. Put the protecting check first and, when unsure about the order, parentheses.

With comparison and logical operators you now have everything you need for your programs to make decisions. That is exactly what comes next: in Python conditionals you will use these same expressions (grade >= 5 and attendance >= 75, divisor != 0…) inside if, elif and else. But first, put them to work in the operators practice: a unit converter, the coffee card with IGIC in cents and a calculator that does not break with negative numbers.

Next step: practicePython operators practice — a unit converter, the coffee card with IGIC in cents and a calculator with negative numbers (and the cent I kept losing)

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 *