Operators and Expressions
This section explains Python operators and expressions in depth. For each operator category you’ll get: the conceptual meaning, syntax, worked examples, common pitfalls, performance/implementation notes where relevant, and best practices. Where useful, I show the underlying bit-level or evaluation behaviour so you truly understand what’s happening, not just what to type.
Quick terminology
- Operator — a symbol or keyword that tells Python to perform an operation (e.g., +, and, is).
- Operand — the value(s) the operator acts on.
- Expression — any combination of operators and operands that produces a value.
- Evaluation — how and when Python computes the value of an expression.
1. Arithmetic operators
Arithmetic operators perform numeric calculations. They typically operate on int, float, complex, or any user-defined numeric types that implement the corresponding special methods.
Operators and meanings
- + : addition
- - : subtraction (also unary negation)
- * : multiplication
- / : true division — always returns float (even if operands are integers)
- // : floor division — returns the floor of the quotient (type: int for integer operands, float for float operands)
- % : modulo (remainder)
- ** : exponentiation (power)
- +x, -x : unary plus and minus
Examples and explanation
a = 7
b = 3
a + b # 10 -> addition
a - b # 4 -> subtraction
a * b # 21 -> multiplication
a / b # 2.3333333333 -> true division (float)
a // b # 2 -> floor division (7 / 3 = 2.333..., floor -> 2)
a % b # 1 -> remainder of 7 divided by 3
a ** b # 343 -> 7 to the power 3
Key points
- / returns a floating point: 4 / 2 == 2.0.
- // returns the floor. For negatives, floor is toward negative infinity:
- Example: -7 // 3 == -3 because -7 / 3 == -2.333..., floor is -3.
- % satisfies the relation:
a == (a // b) * b + (a % b) — with // as floor division, so for negatives % gives a non-negative remainder when divisor is positive. - ** has higher precedence than unary minus: -2**2 is parsed as -(2**2) → -4. Use parentheses for clarity.
- Exponentiation can be expensive for large powers (consider pow(a, b, mod) if you want modular exponentiation).
Pitfalls
- Mixing int and float yields float results; watch for floating-point precision errors (e.g., 0.1 + 0.2 != 0.3 exactly).
- Floor division and modulo for negative numbers can be unintuitive — test examples when negative operands are possible.
2. Comparison operators
Comparison (relational) operators compare values and return bool (True/False).
Operators
- == : equality
- != : inequality
- < : less than
- > : greater than
- <= : less than or equal
- >= : greater than or equal
Examples & notes
3 == 3 # True
3 != 4 # True
2 < 5 # True
5 <= 5 # True
'abc' < 'b' # True, lexicographic comparison of strings
Important behaviour
- Comparisons for sequences (strings, lists, tuples) are lexicographic: Python compares element by element.
- You can chain comparisons: a < b <= c is equivalent to a < b and b <= c but evaluated more efficiently and safely (middle expression evaluated once).
- Example: 1 < x <= 10
- == compares values (calls __eq__), while is (see Identity) compares object identity.
Pitfalls
- Floating-point comparisons: avoid direct equality (==) on floating-point results unless you control the calculation — prefer tolerance-based checks: abs(a - b) < 1e-9.
3. Logical operators
Logical operators combine boolean expressions.
Operators
- and — logical AND
- or — logical OR
- not — logical NOT
Evaluation rules (truthiness)
Python uses truthiness: any object can be tested in boolean context. By default:
- False values: False, None, numeric zero (0, 0.0), empty sequences/collections ('', [], {}, ()), and objects that implement __bool__() returning False.
- Everything else is considered True.
Short-circuiting behaviour (important)
- A and B: evaluate A; if A is falsey, return A (do not evaluate B); otherwise evaluate and return B.
- A or B: evaluate A; if A is truthy, return A (do not evaluate B); otherwise evaluate and return B.
- not A: return True if A is falsey, else False.
Note: and/or return the actual operand value, not necessarily a bool. Only when used in conditional contexts does Python coerce to bool.
Examples
# Short-circuit behavior
def expensive():
print("called")
return True
False and expensive() # returns False, expensive() not called
True or expensive() # returns True, expensive() not called
# Return values
1 and 2 # returns 2 (since 1 is truthy, returns second operand)
0 and 2 # returns 0 (first is falsey)
'' or 'fallback' # returns 'fallback'
Best practice
- Use and/or for boolean logic. Avoid relying on the non-boolean return values unless you intentionally want fallback behaviour (common idiom: value or default).
- Be careful when expressions have side effects — short-circuiting can skip those effects.
4. Bitwise operators
Bitwise operators operate on integers at the level of their binary representation.
Operators
- & : bitwise AND
- | : bitwise OR
- ^ : bitwise XOR (exclusive OR)
- ~ : bitwise NOT (ones’ complement)
- << : left shift
- >> : right shift (arithmetic for signed integers in Python; conceptually logical for non-negative ints)
Python integers are of arbitrary precision (no fixed width), so bitwise operations work on an infinite-length two's complement representation conceptually; practically you can treat them as operating on the binary form of the integer.
Examples (showing binary)
Represent numbers in binary with bin():
a = 0b1010 # 10
b = 0b0111 # 7
a & b # 0b0010 -> 2
a | b # 0b1111 -> 15
a ^ b # 0b1101 -> 13
~a # bitwise NOT, returns -11 in Python because ~x == -(x+1)
# Shifts
1 << 3 # 8 (1 * 2**3)
16 >> 2 # 4 (16 // 2**2)
Understanding ~
- ~x equals -(x + 1). Example: ~0 == -1, ~1 == -2.
Shifts
- x << n multiplies x by 2**n.
- x >> n divides x by 2**n using floor division for non-negative numbers; for negative numbers it arithmetic-shifts (preserves sign), which can be surprising.
Use cases
- Low-level bit twiddling, flags, masks, compact storage, cryptography primitives, performance-sensitive code.
Pitfalls
- Because Python int is unbounded, there’s no overflow; left-shifting very large values consumes memory/time.
- Avoid bit tricks that depend on fixed-width representations unless you explicitly mask bits.
5. Assignment operators
Assignment operators assign values to variables. Python supports simple assignment and augmented assignment operators (compound assignment).
Simple assignment
- = : assigns right-hand value to left-hand target(s)
x = 5
a, b = 1, 2 # tuple unpacking
Augmented assignment
- +=, -=, *=, /=, //=, %=, **=, &=, |=, ^=, <<=, >>=
Example and semantics:
x = 5
x += 3 # x = x + 3 -> 8
lst = [1, 2]
lst += [3] # modifies list in place -> lst is now [1, 2, 3]
For mutable objects, augmented assignments may mutate the object in place (calling __iadd__), whereas for immutable objects they create a new object.
Chained assignment
a = b = 0 # both refer to the same object initially (0 is immutable)
Best practice
- Use augmented assignment for clarity and potential in-place performance.
- Be mindful with mutables — a = b leads to aliasing; mutate a and b both see change if same object.
6. Membership operators (in, not in)
Membership operators check whether a value is contained in a container.
Operators
- x in y — True if x is an element of y
- x not in y — negation of in
Containers supported
- Sequences (strings, lists, tuples)
- Sets, dict (checks keys), custom objects implementing __contains__
Examples
'a' in 'abc' # True
3 in [1, 2, 3] # True
'k' in {'k': 1} # True (checks keys)
5 not in range(10) # False
Performance notes
- in is O(1) average for set and dict (hash-based).
- in is O(n) for lists and tuples (linear scan).
- For large membership checks, prefer set when appropriate.
Pitfalls
- For dict, in checks keys, not values; use .values() if you need values (but that's O(n)).
- For custom classes, implement __contains__ to support in.
7. Identity operators (is, is not)
Identity operators test whether two references point to the same object (identity), not whether their values are equal.
Operators
- is — True if both operands refer to the same object (id(a) == id(b))
- is not — negation
Examples and notes
a = []
b = []
a == b # True -> values equal (both empty lists)
a is b # False -> different objects
c = a
a is c # True -> same object
Subtleties
- Small integers and short strings are interned or cached by CPython for performance. Example: a = 256; b = 256; a is b may be True, while for larger integers it may be False. This is an implementation detail — do not rely on it.
Use == to test equality of value; use is only to check identity or to test singletons like None:
if x is None: ...This is the recommended pattern.
Pitfalls
- is with literals can give surprising results because of interning. Always use == for numeric/string equality checks.
8. Operator precedence and associativity
When an expression has multiple operators, precedence determines which operator is applied first; associativity determines the order when operators have the same precedence.
Precedence table (highest → lowest)
(Condensed, common operators; higher appears earlier)
- Parentheses: () — explicit grouping (highest precedence)
- Exponentiation: ** — right-associative
- Unary plus/minus, bitwise NOT: +x, -x, ~x
- Multiplicative: *, /, //, %
- Additive: +, -
- Bitwise shifts: <<, >>
- Bitwise AND: &
- Bitwise XOR: ^
- Bitwise OR: |
- Comparisons: <, <=, >, >=, !=, == (note: chained comparisons are special)
- not (Boolean NOT)
- and
- or
- Conditional expression: if-else (ternary) — A if cond else B
- Lambda: lambda (lowest precedence among expressions)
Associativity
- Most binary operators are left-associative (evaluated left-to-right): e.g., a - b - c is (a - b) - c.
- Exponentiation is right-associative: 2 ** 3 ** 2 is 2 ** (3 ** 2) → 2 ** 9 → 512.
- Chained comparisons are evaluated such that expressions like a < b < c are a < b and b < c but b is evaluated once.
Example (why precedence matters)
3 + 4 * 2 # 11 -> multiplication first
(3 + 4) * 2 # 14 -> parentheses override precedence
2 ** 3 ** 2 # 512 -> 3 ** 2 = 9, then 2 ** 9
Best practice
- Use parentheses to make intent explicit. Even if you “know” precedence, parentheses improve readability and avoid subtle bugs.
9. Compound expressions
Compound expressions combine multiple operators and sub-expressions. They can include nested function calls, comprehensions, ternary expressions, and chained comparisons.
Examples
# Ternary conditional
result = "even" if n % 2 == 0 else "odd"
# Combined with function calls
print("Positive") if x > 0 else print("Non-positive")
# Comprehension with condition
squares = [x**2 for x in range(10) if x % 2 == 0]
Evaluation order
- Python evaluates left-to-right for most parts, respects precedence, and short-circuits logical operators.
- In comprehensions the loop expression is evaluated in order as specified.
Tips
- Prefer readable compound expressions; avoid overly complex single-line expressions. Break into multiple statements if it improves clarity and debuggability.
10. Short-circuit evaluation (in-depth)
Short-circuiting is a key evaluation optimisation for and and or and is crucial when expressions have side effects or costly computations.
Details
- A and B:
- Evaluate A. If A is falsey, Python returns A immediately (does not evaluate B).
- If A is truthy, evaluate and return B.
- A or B:
- Evaluate A. If A is truthy, return A immediately.
- Otherwise evaluate and return B.
Practical uses
Safe attribute access or fallback:
value = obj and obj.attribute # returns attribute if obj is truthy, else obj (often None) name = input_name or "Guest" # fallback to "Guest" if input_name is empty or NoneGuarding expensive operations:
if short_circuit_condition() and expensive_check(): ...If the first function returns falsey, expensive_check() is not called.
Side effects and gotchas
- If the second operand has side effects (like function call, mutation, I/O), those side effects may be skipped. This can be used intentionally but can also hide bugs.
Example with file operations:
file_obj = open(path) if os.path.exists(path) and os.path.getsize(path) > 0 else NoneIf os.path.exists(path) is False, os.path.getsize(path) is not called — desired to avoid error.
Example illustrating return value (non-boolean)
# returns the first truthy value
result = [] or 0 or "" or None or "fallback" # "fallback"
Best practice
- Use short-circuit intentionally for guards and lazy evaluation.
- Do not write code whose correctness depends on non-obvious short-circuit side effects. Keep logic explicit for maintainability.
11. Example-based explanations — putting it all together
Example 1 — Combined arithmetic, comparison, and logical operators
a = 4
b = 7
c = 2
# compound expression
if (a + b * c) % 2 == 0 and not (b < 0):
print("Even result and b non-negative")
Evaluation steps:
- b * c → 7 * 2 = 14
- a + 14 → 4 + 14 = 18
- 18 % 2 → 0
- 0 == 0 → True
- b < 0 → False; not (False) → True
- True and True → True → prints
Example 2 — Bitwise mask and membership
PERM_READ = 0b100
PERM_WRITE = 0b010
PERM_EXEC = 0b001
perms = PERM_READ | PERM_WRITE # 0b110
# Check write permission
if perms & PERM_WRITE:
print("Write allowed")
perms & PERM_WRITE evaluates to 0b010 (truthy), so condition passes.
Example 3 — Identity vs equality
a = [1, 2, 3]
b = a
c = [1, 2, 3]
a == c # True -> same contents
a is c # False -> different objects
a is b # True -> alias to same list
Example 4 — Short-circuit preventing errors
user = None
# Safe access: only evaluate second part if user is truthy
if user and user.profile.is_active:
print("Active")
# If user is None, user.profile would raise AttributeError; short-circuit prevents it.
Example 5 — Precedence gotcha
x = 2
y = 3
# What is the result?
z = -x ** y
# Parsed as: z = -(x ** y) -> -(2 ** 3) -> -8
# To get (-x) ** y
z_correct = (-x) ** y # (-2) ** 3 -> -8 (same in this odd case)
# But for even y: (-2) ** 2 -> 4 vs -(2 ** 2) -> -4
12. Best practices and performance tips
- Readability first: Use parentheses liberally for clarity, even when not required by precedence.
- Use == for value equality and is for identity checks (primarily for None).
- Prefer and/or for boolean logic rather than bitwise &/| unless you deliberately want bit-level operations or to use NumPy array boolean logic (where & is overloaded).
- Watch mutability with augmented assignment: x += y may mutate x in-place if x is mutable.
- Avoid depending on CPython implementation details (like small integer/string interning).
- For performance-sensitive numeric code, bitwise operators are cheap, but avoid very large shifts that create huge integers.
- When checking membership many times, prefer set over list for O(1) average lookup.
- Prefer f-strings and format for readable output when printing expression results.
13. Short exercises (practice)
- Compute (5 + 3) ** 2 // 7 and show each evaluation step.
- Implement a function that checks whether a number has the 3rd bit set (bit positions starting at 0).
- Demonstrate with code how and and or return operands (not just True/False).
Final notes
This guide covered the semantics and practical use of Python operators. Operators are basic building blocks; understanding evaluation order, short-circuiting, and the difference between value and identity comparisons prevents many subtle bugs.
