Chapter 4: Operators and Expressions
Operators are the symbols that let Python combine values, compare objects, and control logic. Together with expressions, they form the language’s working grammar—the way ideas are turned into action. Understanding how operators behave is key to writing code that is both clear and efficient.
Every operation in Python produces a value, even those that seem to exist only for their effect. Expressions combine values and operators to compute results, while statements often use those results to drive control flow or perform actions. This blend of simplicity and precision is what makes Python both expressive and readable.
Python’s operators are grouped by purpose: arithmetic, comparison, logical, bitwise, membership, and identity. Each follows predictable rules and precedences, meaning that once you learn the pattern for one category, the rest fall naturally into place. Parentheses can always be used to make the intended order explicit.
Unlike many languages, Python’s operators work with a wide range of object types. The same symbol can mean numeric addition for numbers, concatenation for strings, or merging for lists. This flexibility comes from special methods like __add__() and __eq__(), which define how objects respond to operators.
This chapter explores how expressions are built, how precedence and associativity control evaluation order, and how Python’s operators provide a compact yet expressive vocabulary for computation, comparison, and logic. Mastering them will help you write code that communicates clearly—both to the interpreter and to other readers.
Arithmetic, comparison, and logical operators
Operators are at the heart of expression syntax in Python. They define how values combine, compare, and interact. The most common groups are arithmetic operators (for mathematical computation), comparison operators (for testing relationships), and logical operators (for combining conditions). Each group has clear, predictable behaviour that applies across many object types.
Arithmetic operators
Arithmetic operators perform mathematical operations on numeric types and, where appropriate, on sequences like strings or lists. Python uses familiar symbols for these operations:
+addition-subtraction*multiplication/true division (always returns a float)//floor division (rounds down to nearest integer)%modulus (remainder)**exponentiation
a = 10
b = 3
print(a + b) # 13
print(a - b) # 7
print(a * b) # 30
print(a / b) # 3.333...
print(a // b) # 3
print(a % b) # 1
print(a ** b) # 1000
Arithmetic operators can also act on compatible sequence types. For instance, "a" * 3 repeats a string, and [1, 2] + [3] concatenates lists. These operations follow the same symbolic pattern as numeric arithmetic but apply meaningfully to collections.
/ operator always produces a float, even if both operands are integers. Use // when you need integer division that discards the remainder.
Comparison operators
Comparison operators evaluate relationships between two values, producing True or False. They are essential for control flow and condition testing:
==equal to!=not equal to<less than<=less than or equal to>greater than>=greater than or equal to
x = 5
y = 8
print(x == y) # False
print(x != y) # True
print(x < y) # True
Comparisons can be chained for readability, following standard mathematical style:
age = 25
print(18 <= age < 30) # True
Python evaluates such chains without repeating the variable, and stops as soon as a comparison fails. This keeps conditionals concise and expressive.
Logical operators
Logical operators combine or invert Boolean expressions. They control the flow of logic in conditionals and loops:
andreturnsTrueif both operands are trueorreturnsTrueif either operand is truenotinverts the truth value
logged_in = True
is_admin = False
if logged_in and not is_admin:
print("User access granted")
Unlike purely Boolean operators in other languages, Python’s and and or return one of their operands rather than a strict Boolean value. This behaviour supports common idioms:
name = "" or "Anonymous" # evaluates to "Anonymous"
status = "active" and 1 # evaluates to 1
and expression is false, or the first part of an or expression is true, Python skips the rest because the outcome is already determined.
Arithmetic, comparison, and logical operators together define how Python computes, tests, and decides. They form the essential vocabulary for writing expressions that describe both numerical and logical relationships within a program.
Identity and membership
Beyond arithmetic and logic, Python provides two small but powerful families of operators that express relationships of a different kind: identity and membership. They do not compare values in the usual sense—they ask whether something is a specific object, or whether it belongs to a collection.
Identity operators
Identity operators test whether two names refer to the same object in memory. They are especially important when dealing with mutable objects or special singletons such as None. The operators are:
is: True if both references point to the same objectis not: True if they point to different objects
a = [1, 2, 3]
b = a
c = [1, 2, 3]
print(a is b) # True (same object)
print(a is c) # False (different object, same content)
print(a == c) # True (values are equal)
The difference between is and == is subtle but essential. The == operator compares the values of two objects, while is checks whether they are literally the same object in memory. Most of the time, equality is what you want, but is is correct when testing for None or other singletons.
is None and is not None for null checks. This is both faster and clearer than equality comparison, since there is only one None object in any Python process.
Membership operators
Membership operators check whether an element exists within a sequence, set, or mapping. They read naturally, almost like plain English, and return True or False depending on the presence of the item.
in: True if the element is foundnot in: True if the element is not found
letters = ["a", "b", "c"]
print("a" in letters) # True
print("z" not in letters) # True
For dictionaries, the membership test applies to keys, not values. To check whether a value exists, use the .values() method explicitly.
person = {"name": "Ada", "age": 36}
print("name" in person) # True (key check)
print("Ada" in person.values()) # True (value check)
Membership testing is highly efficient for sets and dictionaries because these types use hash tables for storage. For lists, tuples, and strings, the operation scans sequentially, so its cost grows with size.
in checks membership, not identity. It asks “is this element contained here?”, not “is this the same object?”.
Together, identity and membership complete Python’s expressive set of relational operators. They let code describe relationships between objects and collections with precision that reads as naturally as English, which is one of the hallmarks of Python’s design philosophy.
Bitwise operators
Bitwise operators act directly on the binary representation of integers. Instead of working with whole numbers as quantities, they manipulate the individual bits that make those numbers up. These operators are common in low-level programming, networking, graphics, and performance-critical code where compact control of data is important.
TypeError. Always ensure operands are integers or explicitly convert them before use.
Each integer in Python is stored in binary form, using ones and zeros. Bitwise operators perform logical operations on those bits position by position:
&bitwise AND (1 only if both bits are 1)|bitwise OR (1 if either bit is 1)^bitwise XOR (1 if bits differ)~bitwise NOT (inverts all bits)<<left shift (moves bits left, filling with zeros)>>right shift (moves bits right, discarding shifted bits)
a = 6 # binary 110
b = 3 # binary 011
print(a & b) # 2 (010)
print(a | b) # 7 (111)
print(a ^ b) # 5 (101)
print(~a) # -7 (bitwise inversion)
print(a << 1) # 12 (shift left)
print(a >> 1) # 3 (shift right)
Left and right shifts effectively multiply or divide integers by powers of two, which can be useful in optimisation or when working with binary data streams. For example, n << 3 is the same as n * 8. Bitwise operations can be visualised more easily by converting numbers to binary strings using bin():
print(bin(6)) # 0b110
print(bin(3)) # 0b11
print(bin(6 & 3)) # 0b10
Although Python integers can grow arbitrarily large, bitwise operators still behave as if working on an infinite string of bits in two’s complement form. This explains why ~6 becomes -7: the inversion flips all bits and changes the sign.
value & 0xFF isolates the lowest 8 bits of value.
Even though bitwise logic may seem niche in everyday Python programming, it reveals the level beneath the surface—the raw binary patterns that all computation rests on. Knowing how these operators behave gives you precise control when you need it, and a deeper understanding of how Python represents and manipulates data at its most fundamental level.
Assignment expressions
Until Python 3.8, assignment was always a statement, not an expression. That meant you could not assign a value as part of another expression. You had to do it in a separate line. The assignment expression operator, written as := and often called the walrus operator, changes this. It allows you to assign a value to a name as part of a larger expression, returning that value at the same time. This makes certain patterns more compact and expressive, especially in loops and conditionals where a value needs to be both tested and reused.
# without the walrus operator
line = input("Enter text: ")
while line != "":
print("You said:", line)
line = input("Enter text: ")
# with the walrus operator
while (line := input("Enter text: ")) != "":
print("You said:", line)
Here, the input is both captured and tested in a single expression. The operator assigns the result of input() to line and also returns that result for comparison. The parentheses are required because the operator has lower precedence than most comparisons and arithmetic operations.
Assignment expressions are most useful when a value would otherwise need to be computed twice, or when combining assignment and condition logic improves clarity. They should be used sparingly and only when the result reads naturally.
if (n := len(items)) > 0:
print(f"{n} items found.")
Assignment expressions make Python slightly more expressive without compromising its simplicity. They enable concise patterns where a value is both needed and tested, bridging the small gap between statements and expressions in the language’s design.
Operator precedence and associativity
When an expression contains several operators, Python must decide which parts to evaluate first. This order is called operator precedence. Operators with higher precedence bind more tightly, meaning they are evaluated before those with lower precedence. When operators share the same precedence level, associativity determines whether evaluation proceeds from left to right or right to left.
Parentheses can always be used to make order explicit, and doing so is often best for clarity. But knowing the general precedence rules helps you read expressions with confidence and understand why Python produces a given result. The following list shows Python’s operator precedence from highest to lowest. Operators on the same line share the same level and are evaluated according to their associativity.
Highest to lowest precedence
| Operator | Description |
() | Grouping or function calls |
x[index], x(attr) | Subscripts, slicing, attribute access |
** | Exponentiation (right-associative) |
+x, -x, ~x | Unary plus, minus, bitwise NOT |
*, /, //, % | Multiplication, division, floor division, modulus |
+, - | Addition, subtraction |
<<, >> | Bitwise shifts |
& | Bitwise AND |
^ | Bitwise XOR |
| | Bitwise OR |
in, not in, is, is not, <, <=, >, >=, ==, != | Comparisons, membership, identity |
not | Logical NOT |
and | Logical AND |
or | Logical OR |
if–else | Conditional expressions |
:= | Assignment expression (walrus) |
=, +=, -=, *=, /=, //=, %=, **=, &=, |=, ^=, <<=, >>= | Assignment operators (right-associative) |
yield, yield from | Yield expressions |
return, lambda | Return, lambda expressions |
Associativity determines how operators of equal precedence are grouped. Most Python operators are left-associative, meaning evaluation proceeds from left to right. Only exponentiation (**), assignment operators, and the walrus operator (:=) associate from right to left:
print(2 ** 3 ** 2) # 2 ** (3 ** 2) = 512
This precedence hierarchy defines how all of Python’s operators fit together. Understanding it turns what might look like a dense expression into a predictable and readable sequence of operations. In practice, parentheses and good naming will always keep your intent explicit, which is the real goal of Pythonic style.