Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Python Module 2 – Values, variables, numbers and strings

Operators, precedence and the walrus

Read Python expressions the way the parser does: operator precedence and grouping, augmented assignment, membership tests with in, and the := walrus.

  • Beginner
  • 18 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot

What you will learn

  • Apply precedence rules and add brackets for clarity
  • Use augmented assignment and the := assignment expression
  • Test membership with in and not in

Before you start

On this page

2 + 3 * 4 ** 2 has exactly one meaning to Python, and two rules decide it. Precedence says which operators bind more tightly than others, and grouping says which way a run of operators of the same level is read. This lesson gives the whole order, shows how to see the grouping the parser actually uses, and then covers the operators that assign: augmented assignment and the walrus, :=.

The order of the operators

From the most tightly binding to the least:

Operators What they are
(…), […], {…} brackets, and list, dictionary and set displays
x[i], x(…), x.name indexing and slicing, calls, attributes
await x waiting in asynchronous code (a later module)
** power
+x, -x, ~x unary plus and minus, bitwise not
*, @, /, //, % multiplication (@ is matrix multiplication), division, remainder
+, - addition and subtraction
<<, >> bit shifts
& bitwise and
^ bitwise exclusive or
| bitwise or
in, not in, is, is not, <, <=, >, >=, !=, == comparisons, all on one level
not x Boolean not
and Boolean and
or Boolean or
x if condition else y conditional expression
lambda anonymous function (the functions module)
:= assignment expression

Operators on the same level are read from left to right, so 100 / 10 / 5 is (100 / 10) / 5. There are two exceptions: ** and the conditional expression are read from right to left. Comparisons chain instead, as the previous lesson showed. Each line of this program prints an expression and then the same expression with every bracket Python adds:

Ten expressions and their brackets Python · grouping.py
# Each line prints an expression, then the same expression with the brackets Python adds.
print(2 + 3 * 4, 2 + (3 * 4))  # * before +
print(-3 ** 2, -(3 ** 2))  # ** before the minus sign on its left
print(2 ** 3 ** 2, 2 ** (3 ** 2))  # ** groups from right to left
print(2 ** -1, 2 ** (-1))  # a minus sign on the right belongs to the exponent
print(100 / 10 / 5, (100 / 10) / 5)  # the other operators group from left to right
print(-7 // 2, (-7) // 2)  # the sign binds tighter than //
print(1 + 2 < 4, (1 + 2) < 4)  # arithmetic before comparison
print(not 1 == 2, not (1 == 2))  # comparison before not
print(True or False and False, True or (False and False))  # and before or
print(1 + 2 if False else 3 + 4, (1 + 2) if False else (3 + 4))  # the conditional comes last

Output

14 14
-9 -9
512 512
0.5 0.5
2.0 2.0
-4 -4
True True
True True
True True
7 7

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 grouping.py

The pairs always agree. The rows most worth remembering:

  • -3 ** 2 is -9: the power binds more tightly than the minus sign on its left, as in school algebra. Write (-3) ** 2 to square -3.
  • 2 ** 3 ** 2 is 2 ** 9, or 512, because powers are read from the right. On its right, though, a minus sign belongs to the exponent: 2 ** -1 is 0.5.
  • -7 // 2 is (-7) // 2, which is -4; -(7 // 2) would be -3 (the floor division of the integers lesson).
  • not 1 == 2 is not (1 == 2), because comparisons bind more tightly than not; 1 == not 2 is a syntax error.
  • and binds more tightly than or: a or b and c means a or (b and c).
  • The conditional expression comes almost last, so 1 + 2 if False else 3 + 4 adds up each side first and is 7.

See the parser’s grouping for yourself

Before running an expression, Python parses it into a tree in which every operator joins its operands. The standard library’s ast module shows that tree without running anything:

Two expressions as Python parses them Python · parse_tree.py
import ast

for text in ["2 + 3 * 4", "-3 ** 2"]:
    tree = ast.parse(text, mode="eval")  # parse the expression without running it
    print(text)
    print(ast.dump(tree.body, indent=2))

Output

2 + 3 * 4
BinOp(
  left=Constant(value=2),
  op=Add(),
  right=BinOp(
    left=Constant(value=3),
    op=Mult(),
    right=Constant(value=4)))
-3 ** 2
UnaryOp(
  op=USub(),
  operand=BinOp(
    left=Constant(value=3),
    op=Pow(),
    right=Constant(value=2)))

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 parse_tree.py

In the first tree the * sits inside the +, which means it is worked out first. In the second, the minus sign (USub) applies to the result of the power. A bigger example, drawn as a tree:

The tree of 2 + 3 * 4 ** 2: + at the top joins 2 and a * node, which joins 3 and a ** node of 4 and 2. The result is 50.2 + 3 * 4 ** 2+gives 502*gives 483**gives 1642

The parse tree of 2 + 3 * 4 ** 2

Text description of the diagram

The diagram is the tree Python builds for the expression 2 + 3 * 4 ** 2. Each operator is a node whose two branches are its operands.

  • At the top is +, the operator with the lowest precedence. Its operands are 2 and everything to its right, 3 * 4 ** 2.
  • Below it is *, whose operands are 3 and 4 ** 2.
  • At the bottom is **, which binds most tightly. Its operands are 4 and 2.

Python works the tree out from the bottom up: 4 ** 2 gives 16, then 3 * 16 gives 48, then 2 + 48 gives 50. Written with every bracket, the expression is 2 + (3 * (4 ** 2)).

Brackets are for people

Precedence tells Python what an expression means, but your reader should not need the table to see it. Add brackets wherever two kinds of operator meet and the order is not obvious: and with or, not with a comparison, a minus sign with **. They cost nothing.

School arithmetic (BODMAS or PEMDAS) agrees with Python on the basics: multiplication and division share a level and are worked out from left to right, and so are addition and subtraction.

An expression people read two ways Python · school_maths.py
print(6 / 2 * (1 + 2))  # / and * share a level, left to right: (6 / 2) * 3
print(6 / (2 * (1 + 2)))  # brackets make the other reading explicit
print(8 / 2 / 2)  # (8 / 2) / 2, not 8 / (2 / 2)

Output

9.0
1.0
2.0

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 school_maths.py

6 / 2 * (1 + 2) is 9.0 in Python: the division happens first, then the multiplication. If you meant 6 divided by the product, the brackets have to say so, as on the second line.

Order of Operations Calculator (BODMAS & PEMDAS) Work an expression out step by step in BODMAS order, with the ambiguous ones explained.

A common mistake: multiplying without *

Maths writes 3(x + 1) for “3 times (x + 1)”. In Python, writing something in brackets straight after a value is a call, so it tries to call the number 3:

3(x + 1) is not a multiplication Python · implicit_multiply.py
x = 2
print(3(x + 1))  # maths notation, but Python needs 3 * (x + 1)

Output (exit status 1)

Printed as an error (standard error)

implicit_multiply.py:2: SyntaxWarning: 'int' object is not callable; perhaps you missed a comma?
  print(3(x + 1))  # maths notation, but Python needs 3 * (x + 1)
Traceback (most recent call last):
  File "implicit_multiply.py", line 2, in <module>
    print(3(x + 1))  # maths notation, but Python needs 3 * (x + 1)
          ~^^^^^^^
TypeError: 'int' object is not callable

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 implicit_multiply.py

The compiler already suspects a mistake and adds a SyntaxWarning before the program runs; the TypeError follows when the line runs. Write 3 * (x + 1).

Another trap catches people who know C, which orders comparisons and bitwise operators differently. In Python every comparison binds more loosely than every arithmetic, shift and bitwise operator, so x & 1 == 0 means (x & 1) == 0, the test it looks like.

Augmented assignment

count += 5 works like count = count + 5, and every arithmetic and bitwise operator has such a form: -=, *=, /=, //=, %=, **= and the rest. For numbers and strings, which cannot change, the name is simply rebound to the result. For a list, += changes the list itself:

+= on a number and on a list Python · augmented.py
count = 10
count += 5  # count = count + 5
count //= 4  # count = count // 4
count **= 2  # count = count ** 2
print(count)

prices = [10, 20]
alias = prices
prices += [30]  # += changes this list in place ...
print(prices, alias, prices is alias)

prices = prices + [40]  # ... while + builds a new list
print(prices, alias, prices is alias)

Output

9
[10, 20, 30] [10, 20, 30] True
[10, 20, 30, 40] [10, 20, 30] False

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 augmented.py

After prices += [30], alias sees the new item, because both names still refer to one list that was extended in place. prices = prices + [40] builds a new list instead and moves only the name prices to it, the rebinding of the variables lesson.

Python has no ++ or --. count++ is a syntax error, and ++count is worse, because it is valid: two unary plus signs, which change nothing:

++ does not add one Python · no_increment.py
count = 5
print(++count)  # two plus signs: +(+count), still 5
print(--count)  # two minus signs: -(-count), also 5
count += 1  # the way to add one
print(count)

Output

5
5
6

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 no_increment.py

The walrus operator :=

name := expression assigns the value to the name and also gives the value back, so it can sit inside a larger expression. PEP 572 added it in Python 3.8 under the name assignment expression; “walrus operator” is the nickname it picked up while that proposal was being debated.

The walrus in the shell Python console · walrus_session.pycon
>>> (n := 10) * 2
20
>>> n
10
>>> (size := len("hello") > 3)
True
>>> size
True
>>> (size := len("hello")) > 3
True
>>> size
5
>>> n := 5
  File "<python-input-6>", line 1
    n := 5
      ^^
SyntaxError: invalid syntax

This session was replayed with Python 3.14.8 on macOS 26 arm64, and it printed exactly what is shown.

The session shows the three rules that matter:

  • The walrus has the lowest precedence of all, so it takes everything to its right: size := len("hello") > 3 stores the result of the comparison, True. Put the assignment in brackets when it is part of a bigger expression.
  • On its own line it is a syntax error. An ordinary assignment is a statement, so use = there.
  • The name stays bound afterwards, like any other variable.

Its best use is a value that a condition needs and the code after it uses again. Reading lines until an empty one needs input() twice without it, and once with it:

Adding up numbers until an empty line

With := · sum_lines.py

total = 0
while (line := input()) != "":  # read a line, name it, then test it
    total += int(line)
print("Total:", total)

Input (standard input)

250
120
30

Output

Total: 400

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 sum_lines.py

Without := · sum_lines_without.py

total = 0
line = input()
while line != "":
    total += int(line)
    line = input()  # the same call again, at the end of the loop
print("Total:", total)

Input (standard input)

250
120
30

Output

Total: 400

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 sum_lines_without.py

PEP 572 itself recommends the plain assignment statement whenever both forms would work, because a statement says most clearly what it does. Use := to remove a repeated call or computation, as here, and not to squeeze more into one line.

Membership tests: in and not in

x in y asks whether y contains x, and x not in y asks the opposite:

in with strings, lists and dictionaries Python · membership.py
print("ell" in "hello")  # a substring
print("" in "hello")  # the empty string is in every string
print(3 in [1, 2, 3], 4 not in [1, 2, 3])

marks = {"Asha": 91, "Ravi": 78}
print("Asha" in marks)  # a dict is searched by its keys ...
print(91 in marks, 91 in marks.values())  # ... and by its values only if you ask

print(not 3 in [1, 2], 3 not in [1, 2])  # the same test; not in reads better

Output

True
True
True True
True
False True
True True

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 membership.py

  • For strings, in looks for a substring, and the empty string is part of every string.
  • For lists and tuples, it looks for an item equal to x.
  • For a dictionary, it looks at the keys only; search marks.values() when you mean the values.
  • x not in y means not (x in y) and reads more naturally, so prefer it.

How long in takes depends on the container. A list or a tuple is checked item by item, so the time grows with its length; a set or a dictionary finds an item through its hash, on average in about the same time however large it is. If a program tests membership again and again against the same collection, make that collection a set.

Key takeaways

  • After brackets, indexing, calls and await, ** binds most tightly and groups from the right; then come the unary signs, * / // %, + -, the bitwise operators, the comparisons, not, and, or, the conditional expression, lambda and :=.
  • Operators on one level group from left to right, except ** and the conditional expression; comparisons chain.
  • Brackets cost nothing: add them where and meets or, or a minus sign meets **.
  • x += y rebinds numbers and strings but changes a list in place; there is no ++.
  • := assigns inside an expression, has the lowest precedence and usually needs brackets; prefer = when either would do.
  • in searches strings for substrings, lists for items and dictionaries by key; sets and dictionaries answer fast.

Exercise

Exercise · Easy · Python

Write the leap-year rule as one expression

In the Gregorian calendar, a year is a leap year when it is divisible by 4, except that a year divisible by 100 is a leap year only when it is also divisible by 400. So 2024 and 2000 are leap years, while 2023 and 1900 are not.

Complete is_leap(year) in leap.py. The starter code handles only the first part of the rule, so it wrongly calls 1900 a leap year. Keep the function to a single return statement whose expression uses %, ==, !=, and and or, and add brackets wherever they make it easier to read, even where Python does not need them.

The sample tests check famous years, compare your function with the standard library's calendar.isleap() for every year from 1 to 3000, and read your file to make sure it is one return statement that does not use the calendar module itself.

Starter code · leap.py

def is_leap(year):
    """Return True if year is a leap year in the Gregorian calendar, otherwise False."""
    return year % 4 == 0  # a first draft: 1900 is not a leap year
The sample tests · test_leap.py
import ast
import calendar

import leap
from leap import is_leap


def test_famous_years():
    """gets the well-known years right"""
    assert is_leap(2024) is True
    assert is_leap(2023) is False
    assert is_leap(2000) is True
    assert is_leap(1900) is False
    assert is_leap(2100) is False
    assert is_leap(2400) is True


def test_every_year():
    """agrees with calendar.isleap() for every year from 1 to 3000"""
    wrong = [year for year in range(1, 3001) if is_leap(year) != calendar.isleap(year)]
    assert wrong == []


def test_one_expression():
    """is one return statement and does not use the calendar module"""
    with open(leap.__file__, encoding="utf-8") as source:
        tree = ast.parse(source.read())
    function = [node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == "is_leap"][0]
    body = [node for node in function.body if not (isinstance(node, ast.Expr) and isinstance(node.value, ast.Constant))]
    assert len(body) == 1 and isinstance(body[0], ast.Return), "the body should be a single return statement"
    modules = set()
    for node in ast.walk(tree):
        if isinstance(node, ast.Import):
            modules.update(alias.name for alias in node.names)
        elif isinstance(node, ast.ImportFrom):
            modules.add(node.module)
    assert "calendar" not in modules, "work the rule out with %, without the calendar module"
A hint

Write each part of the rule as a comparison first: year % 4 == 0 means "divisible by 4", and year % 100 != 0 means "not divisible by 100". Then join the parts with and and or. and binds more tightly than or, so a and b or c means (a and b) or c: use brackets to show which parts belong together.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 What does grouping.py print? Work out each line before you look at the choices.

    What does this program print? Choose one answer.

    # Each line prints an expression, then the same expression with the brackets Python adds.
    print(2 + 3 * 4, 2 + (3 * 4))  # * before +
    print(-3 ** 2, -(3 ** 2))  # ** before the minus sign on its left
    print(2 ** 3 ** 2, 2 ** (3 ** 2))  # ** groups from right to left
    print(2 ** -1, 2 ** (-1))  # a minus sign on the right belongs to the exponent
    print(100 / 10 / 5, (100 / 10) / 5)  # the other operators group from left to right
    print(-7 // 2, (-7) // 2)  # the sign binds tighter than //
    print(1 + 2 < 4, (1 + 2) < 4)  # arithmetic before comparison
    print(not 1 == 2, not (1 == 2))  # comparison before not
    print(True or False and False, True or (False and False))  # and before or
    print(1 + 2 if False else 3 + 4, (1 + 2) if False else (3 + 4))  # the conditional comes last
    Show the answer to question 1

    Answer: it prints

    14 14
    -9 -9
    512 512
    0.5 0.5
    2.0 2.0
    -4 -4
    True True
    True True
    True True
    7 7

    * binds before + (14); ** before the minus sign on its left (-9) and from the right (512); the other operators group from the left (2.0); the minus sign binds before // (-4); comparisons before not, and before or (True or (False and False) is True), and the conditional expression last (7).

  2. Question 2 of 5 What does this print?

    Read the code, then choose one answer.

    print(not 1 + 1 == 2)
    Show the answer to question 2

    Answer: False

    Arithmetic binds most tightly, then comparisons, then not: the expression is not ((1 + 1) == 2), which is not True, so False.

  3. Question 3 of 5 What does this print?

    Read the code, then choose one answer.

    a = [1]
    b = a
    a += [2]
    print(b)
    Show the answer to question 3

    Answer: [1, 2]

    += on a list extends that list in place, and b refers to the same list, so it sees the new item. a = a + [2] would have built a new list and left b as [1].

  4. Question 4 of 5 What does the condition while (line := input()) != "": do each time round the loop?

    Choose one answer.

    Show the answer to question 4

    Answer: Reads a line, binds it to line, and continues the loop while that line is not empty

    The brackets make the walrus run first: input() is called, its result is bound to line, and that value is compared with "". Without the brackets, line would receive the result of the comparison, because := has the lowest precedence of all.

  5. Question 5 of 5 Which of these expressions are True?

    Choose every answer that is right.

    Show the answer to question 5

    Answer:

    • "" in "abc"
    • "bc" in "abc"
    • "Asha" in {"Asha": 91}

    In a string, in looks for a substring, and the empty string is in every string. A dictionary is searched by its keys, so 91 is not found. In a list, in looks for an equal item: the list [1] is not an item of [1, 2], and "ab" is not equal to "abc".

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.