PythonMastery
beginner 14 min read · lesson 4 of 19 in Python Fundamentals

Operators & Expressions

1 · The lesson

read

Operators are the symbols and keywords that do something with values. You've seen + and = already. This lesson covers the operators you will use every day: arithmetic, comparison, logical, membership and assignment operators. It also covers the precedence rules, which decide which operator runs first when a line has several.

Most operators behave as they do in maths. This lesson spends most of its time on the places where they don't.


1. Arithmetic Operators

python
print(7 + 3)                    # 10
print(7 - 3)                    # 4
print(7 * 3)                    # 21
print(7 / 3)                    # 2.3333333333333335   (true division always gives a float)
print(7 // 3)                   # 2                    (floor division drops the remainder)
print(7 % 3)                    # 1                    (modulo gives the remainder)
print(7 ** 3)                   # 343                  (7 to the power of 3)

Two of these surprise newcomers:

/ always returns a float, even when the result is whole. 6 / 2 is 3.0, not 3. If you need an integer, use //.

% (remainder) is useful for "every Nth" checks. Want to do something every 5 iterations? Check i % 5 == 0. Want to know if a number is even? n % 2 == 0. Want a value to wrap around a circle of 360 degrees? angle % 360.

python
print(9 % 3 == 0)               # True: 9 is a multiple of 3
print(10 % 3 == 0)              # False: 10 / 3 leaves a remainder of 1
print(7 % 2 == 0)               # False: 7 is odd
print(370 % 360)                # 10: turning 370 degrees ends at the same place as 10 degrees

For floating-point problems like 0.1 + 0.2 != 0.3, go back to Numbers in Depth. The short version: never compare floats with ==. Use math.isclose().


2. Comparison Operators

python
print(5 == 5)                   # True   (equal)
print(5 != 4)                   # True   (not equal)
print(5 < 10)                   # True
print(5 > 10)                   # False
print(5 <= 5)                   # True
print(5 >= 6)                   # False

Every comparison returns a bool. You will use comparisons in conditions and loops all the time.

Chained comparisons. Many languages make you write x > 0 && x < 10. Python lets you write the comparison the way it is written in maths:

python
x = 5
print(0 < x < 10)               # True  (it reads like maths)
print(0 < x < 10 < 100)         # True  (a chain can be as long as you like)

This is shorter and easier to read, so use it.


3. == vs is: Equality vs Identity

== asks "do these have the same value?" is asks "are these the same object in memory?" These are different questions, and they can have different answers.

python
a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)                   # True  (same contents)
print(a is b)                   # False (two different list objects)
print(a is c)                   # True  (the same object, as in the Variables lesson)

Rule of thumb: use == for almost everything. Use is only to compare with the three singletons (objects that exist only once): None, True and False.

The next examples use if, which the Conditionals lesson teaches. For now, read if result is None: as "run the indented lines only when result is None is True".

python
if result is None:              # correct
    ...

if result == None:              # works, but is not good Python style, and code checkers will warn you
    ...
+ setup added so this can run · defines result
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

result = _AutoMock('result')

4. Logical Operators and Short-Circuit Evaluation

python
print(True and False)           # False
print(True or False)            # True
print(not True)                 # False

The important part is short-circuit evaluation. Python evaluates left-to-right and stops as soon as the answer is decided:

  • and stops at the first falsy value
  • or stops at the first truthy value
python
def expensive_check():
    print("ran expensive_check")
    return True

# expensive_check never runs, because `False and ...` is already False
result = False and expensive_check()
print(result)                   # False, with no print from the function

You can use this to avoid errors:

python
user = None
# Without short-circuit, the second condition would crash on None.name
if user is not None and user.name == "Ada":
    print("Hello, Ada")

If user is not None is False, Python never evaluates user.name, so the AttributeError never happens. Python programmers use this pattern very often.


5. Truthiness: What Counts as False

Python evaluates any value in a boolean context (like if or while). The falsy values are a short list. Learn them:

python
bool(False)                     # False
bool(None)                      # False
bool(0)                         # False
bool(0.0)                       # False
bool("")                        # False  (empty string)
bool([])                        # False  (empty list)
bool({})                        # False  (empty dict)
bool(set())                     # False  (empty set)

An empty tuple () is falsy too. Everything else is truthy. This means you rarely write if len(items) > 0:. You write the shorter version:

python
items = [1, 2, 3]

if items:                       # truthy because non-empty
    print("got items")

name = ""
if not name:                    # falsy because empty
    print("name is missing")

6. Membership: in and not in

python
print("py" in "python")         # True   (checks for a substring)
print(3 in [1, 2, 3])           # True   (checks for an item in a list)
print("name" in {"name": "Ada"})# True   (checks the keys of a dict, not the values)
print(99 not in [1, 2, 3])      # True

One line does the work of a whole loop.


7. Assignment Operators

The short forms do the same thing as the long version:

python
x = 10
x += 3                          # same as x = x + 3, so x becomes 13
x -= 5                          # 8
x *= 2                          # 16
x //= 3                         # 5
x %= 2                          # 1
x **= 4                         # 1

One detail to know. It follows from the mutability section in the Variables lesson:

python
# Lists: += changes the same list in place
lst = [1, 2]
lst += [3, 4]
print(lst)                      # [1, 2, 3, 4]  (the same list, now longer)

# Strings can't be changed (they are immutable), so += builds a new string
s = "hi"
s += " there"
print(s)                        # "hi there"  (but this is a new string object)

For one or two concatenations the difference doesn't matter. For thousands of string appends in a loop, build a list and call "".join(parts) at the end. This is much faster.


8. Operator Precedence

When a line has several operators, which one runs first? Operators with higher precedence run first:

PrecedenceOperatorsNotes
Highest**Exponentiation
+x, -x, ~xUnary plus/minus
*, /, //, %Multiplicative
+, -Additive
<, <=, >, >=, ==, !=, in, isComparison
notBoolean NOT
andBoolean AND
orBoolean OR
Lowest=, +=, etc.Assignment

Two ways to use this table:

1. Read it once so you know the rough order.
2. Use parentheses wherever a reader might need to stop and think. That reader may be you, a few months later.

python
# Technically correct, briefly confusing:
if x > 0 and y > 0 or z == 0: ...

# Same logic, zero confusion:
if (x > 0 and y > 0) or z == 0: ...
+ setup added so this can run · defines z, x, y
# Lightweight mock for objects whose attributes/methods aren't critical
class _AutoMock:
    def __init__(self, name='mock'): self._name = name
    def __getattr__(self, k): return _AutoMock(self._name + '.' + k)
    def __call__(self, *a, **kw):
        print('-> ' + self._name + '() called')
        return _AutoMock(self._name + '()')
    def __repr__(self): return '<mock ' + self._name + '>'
    def __str__(self): return '<mock ' + self._name + '>'
    def __bool__(self): return True
    def __iter__(self): return iter([])
    def __len__(self): return 0
    def __getitem__(self, k): return _AutoMock(self._name + '[...]')
    def __setitem__(self, k, v): pass
    def __enter__(self): return self
    def __exit__(self, *a): return False
    async def __aenter__(self): return self
    async def __aexit__(self, *a): return False
    def __add__(self, o): return self
    def __radd__(self, o): return self
    def __sub__(self, o): return self
    def __mul__(self, o): return self
    def __rmul__(self, o): return self
    def __truediv__(self, o): return self
    def __eq__(self, o): return isinstance(o, _AutoMock)
    def __hash__(self): return hash(self._name)
    def __lt__(self, o): return True
    def __le__(self, o): return True
    def __gt__(self, o): return False
    def __ge__(self, o): return False
    def __mro_entries__(self, bases): return (object,)

z = _AutoMock('z')
x = _AutoMock('x')
y = _AutoMock('y')

Extra parentheses cost nothing, and they make the code easier to read.


Common Mistakes

  • = vs ==. = assigns, == compares. if x = 5: is a SyntaxError in Python. (In C, the same mistake runs without an error.) Inside a condition you always want ==.
  • Treating chained comparisons like other languages. In Python, 1 < x < 10 does what it says: it checks that x is between 1 and 10. In C, the same expression is read as (1 < x) < 10, which is always true, and in Java it does not compile. So if you translate code from those languages, don't copy their longer form out of habit.
  • Forgetting short-circuit. If the right-hand side has side effects (a function call, an API hit), changing the order in an and/or can change what the program does, without any error. expensive() and cheap() runs expensive() first every time, but cheap() and expensive() only runs expensive() when cheap() is truthy.
  • Comparing floats with ==. 0.1 + 0.2 == 0.3 is False. Use math.isclose(). See Numbers in Depth.
  • if value == None: instead of if value is None:. Both usually work, but is None is the standard way. Linters will warn about the first.

🎯 Your Turn: Password Strength Checker

Write a short program that checks one password and prints whether it is strong. In this exercise, a password is strong when all four of these are true:

  • it has at least 12 characters
  • it contains an uppercase letter
  • it contains a lowercase letter
  • it contains a symbol (anything that isn't a letter or a digit)

Skeleton:

python
pw = "Hello123!world"

# TODO 1: long_enough should be True if pw has at least 12 characters
# TODO 2: has_upper should be True if pw contains an uppercase letter
# TODO 3: has_lower should be True if pw contains a lowercase letter
# TODO 4: has_symbol should be True if pw contains a symbol
# TODO 5: combine the four with `and`, then print each check and the result

Run it. Then change pw to "hello123", "Hello123" and "PASSWORD!", and run it again each time.

Hint 1: Each check is one comparison len(pw) gives the number of characters, and len(pw) >= 12 is already a boolean. You don't need anything else for TODO 1.
Hint 2: Finding letters and symbols without a loop pw.lower() returns a copy of the string with every letter in lowercase. If that copy is different from the original (pw.lower() != pw), then pw had at least one uppercase letter. pw.upper() != pw finds a lowercase letter in the same way. pw.isalnum() is True when every character is a letter or a digit, so not pw.isalnum() means there is at least one symbol.
Show full solution
python
pw = "Hello123!world"

long_enough = len(pw) >= 12
has_upper = pw.lower() != pw     # lowering changed something, so pw had an uppercase letter
has_lower = pw.upper() != pw
has_symbol = not pw.isalnum()

strong = long_enough and has_upper and has_lower and has_symbol

print("Long enough:", long_enough)
print("Uppercase:", has_upper)
print("Lowercase:", has_lower)
print("Symbol:", has_symbol)
print("Strong:", strong)

Output:

python
Long enough: True
Uppercase: True
Lowercase: True
Symbol: True
Strong: True

With pw = "Hello123", the program prints Strong: False: the password has only 8 characters and no symbol. Each check is one comparison or one not, and and combines them into one answer. In the Conditionals lesson you will use booleans like these with if, for example to print "weak" or "strong".


What You Learned

  • / always returns a float, // is floor division, and % is the remainder, which you can use for "every Nth" checks.
  • == compares values, and is compares identity. Use is only for None, True, False.
  • and/or short-circuit. This helps you avoid errors on None and skip slow calls.
  • The falsy values are a short list: False, None, 0, 0.0, "", [], (), {}, set(). Everything else is truthy.
  • in / not in work on strings, lists, dicts (keys), and sets.
  • Chained comparisons (0 < x < 10) are valid Python and easy to read.
  • Operators have a precedence order. Add parentheses whenever they make the code clearer.

Next: Comments and style, for writing code that other people can read.

Practice this

on practicepython.in

Short exercises that run in your browser and tell you what your code did, as well as whether a test passed.

Found this useful? Share it with a friend who’s learning Python.