On this page
Python operators are the symbols and keywords that combine values: arithmetic, comparison, logical, membership, identity, bitwise and assignment. Most take a minute to learn. Three cause real bugs, though: is passing for 256 and failing for 257, and/or returning an operand instead of True/False, and += quietly changing a list that another name also points to.
This guide is for anyone who writes basic Python and wants to predict what an expression returns before running it. Every example shows its output, and the last section lists the five operator mistakes worth checking for in code review. It sits between Python strings and user-defined functions in the Python series.
Python operators at a glance
| Category | Operators | Returns |
|---|---|---|
| Arithmetic | + - * / // % ** @ |
a number (@ is matrix multiplication, used by NumPy) |
| Comparison | == != < <= > >= |
bool |
| Logical | and or not |
an operand (not returns bool) |
| Identity | is, is not |
bool |
| Membership | in, not in |
bool |
| Bitwise | & | ^ ~ << >> |
an int (elementwise in NumPy and pandas) |
| Assignment | = += -= *= := |
nothing, except := which returns the value |
Arithmetic operators: why / returns a float
The arithmetic operators behave like a calculator except for division: / always returns a float, and // rounds down toward negative infinity rather than toward zero.
7 + 3 # 10
7 - 3 # 4
7 * 3 # 21
7 / 3 # 2.3333333333333335 - always a float
7 // 3 # 2 - floor division
7 % 3 # 1 - remainder
7 ** 3 # 343 - power
Two things surprise people.
/ always returns a float, even when it divides evenly. For example, 6 / 3 is 2.0, not 2. Use // when you want an integer.
// floors toward negative infinity, which is not truncation:
7 // 2 # 3
-7 // 2 # -4 - not -3
int(-7 / 2) # -3 - truncation, if that is what you wanted
The modulo follows the same rule, so -7 % 2 is 1, not -1. This is deliberate and useful, because x % n always lands in [0, n) for positive n, which makes it safe for cyclic indexing.
Also watch floating point:
0.1 + 0.2 # 0.30000000000000004
0.1 + 0.2 == 0.3 # False
import math
math.isclose(0.1 + 0.2, 0.3) # True
For money, use decimal.Decimal — see our Python numeric data types guide.
How do chained comparisons work?
A chain such as a < b <= c means a < b and b <= c, except that b is evaluated only once:
5 == 5.0 # True - value equality across types
5 != 3 # True
3 < 5 <= 5 # True - chained, reads mathematically
The comparisons section of the language reference defines this exactly. Chaining also short-circuits, so 0 <= index < len(items) is both idiomatic and efficient. Writing 0 <= index and index < len(items) is longer and no clearer.
What is the difference between is and == in Python?
== compares value, while is compares identity. This distinction produces real bugs.
a = [1, 2, 3]
b = [1, 2, 3]
c = a
a == b # True - same contents
a is b # False - different objects
a is c # True - same object
Now the trap:
x = 256
y = int("256")
x is y # True - small ints are cached
x = 257
y = int("257")
x is y # False - a fresh object
As of September 2026 (Python 3.14.7), the CPython integer-object documentation still describes an array of pre-allocated integers from -5 to 256, so small values share objects, while larger ones usually do not. The same applies to short strings via interning. To make things murkier, if you write x = 257 and y = 257 in the same script, the compiler may reuse one constant and x is y becomes True. Since Python 3.8, writing x is 257 directly also triggers a SyntaxWarning asking whether you meant ==.
As a result, is on numbers appears to work in testing and fails in production with real data. Use == for values.
Reserve is for None and other singletons, as PEP 8's programming recommendations advise:
value = None
if value is None:
print("missing")
if value is not None:
print("present")
flag = True
if flag: # not "if flag is True" or "if flag == True"
print("enabled")
if value == None works but is non-idiomatic, and linters will flag it. Similarly, PEP 8 calls if flag is True: worse than == True; a plain truth test is the right form.
What do and and or return in Python?
and and or do not return True/False. Instead, they return one of their operands, as the boolean operations reference states:
"hello" and "world" # 'world' - both truthy, returns the last
"" and "world" # '' - first is falsy, short-circuits
"hello" or "world" # 'hello' - first truthy wins
"" or "default" # 'default'
None or 0 or "found" # 'found' - first truthy
That behaviour gives the default-value idiom:
user_input = ""
name = user_input or "Anonymous" # 'Anonymous'
There is one important caveat: this treats every falsy value as absent. If 0 or "" are legitimate values, use an explicit None check:
config = {"timeout": 0}
# bug: a timeout of 0 becomes 30
timeout = config.get("timeout") or 30 # 30
# correct
timeout = config.get("timeout")
if timeout is None:
timeout = 30 # stays 0
Short-circuiting also guards against errors:
user = None
data = []
if user is not None and user.is_active: # safe - no AttributeError
print("active")
if data and data[0] > 10: # no IndexError on empty
print("big first value")
How do in and not in work?
in tests containment: an element of a list or set, a key of a dict, or a substring of a string. not in is its negation.
3 in [1, 2, 3] # True
"key" in {"key": 1} # True - checks KEYS, not values
"ell" in "hello" # True - substring test
5 not in {1, 2, 3} # True
Note the complexity difference, though. in on a list is O(n), whereas on a set or dict it is O(1) on average. Converting a list to a set before repeated membership tests is therefore one of the easiest performance wins there is:
id_list = [101, 102, 103]
valid_ids = set(id_list) # once
[r for r in [101, 999, 103] if r in valid_ids] # [101, 103]
Bitwise operators, and the pandas gotcha
5 & 3 # 1 - AND
5 | 3 # 7 - OR
5 ^ 3 # 6 - XOR
~5 # -6 - NOT
5 << 1 # 10 - left shift
These matter in pandas and NumPy, where &, | and ~ are the elementwise operators and and/or/not fail:
df[(df.age > 25) & (df.city == "Pune")] # correct
df[df.age > 25 and df.city == "Pune"] # ValueError: truth value is ambiguous
The parentheses are required because & binds more tightly than >. See our NumPy indexing guide for the full explanation.
Augmented assignment: += is not always a rebind
For immutable types, x += 1 builds a new object and rebinds the name. For a list, however, += extends the existing object in place:
a = [1, 2]
b = a
a += [3]
print(b) # [1, 2, 3] - b sees the change
a = [1, 2]
b = a
a = a + [3]
print(b) # [1, 2] - a now names a new list
This is the same names-versus-objects model covered in Python basics.
When is the walrus operator (:=) worth using?
Use := when you need to test a value and keep it in the same step. It assigns inside an expression, and was added in Python 3.8. The language reference calls it an assignment expression:
import re
pattern = re.compile(r"id=(\d+)")
line = "user id=42"
# without
match = pattern.search(line)
if match:
print(match.group(1))
# with
if (match := pattern.search(line)):
print(match.group(1)) # 42
It is genuinely useful in while loops and comprehensions where you would otherwise compute twice:
import io
f = io.StringIO("x" * 20000)
while (chunk := f.read(8192)):
print(len(chunk)) # 8192, 8192, 3616
data = ["1", "x", "3"]
results = [n for s in data if (n := int(s) if s.isdigit() else None) is not None]
# [1, 3]
Use it where it removes duplication. Do not use it to compress two clear lines into one dense one.
Which operator runs first? Precedence in practice
Among the common Python operators, ** binds tightest, then unary minus, then *, /, // and %, then + and -; comparisons come next, and not, and and or bind loosest. Most surprises come from the edges of that order:
2 + 3 * 4 # 14, not 20
2 ** 3 ** 2 # 512 - power is right-associative: 2**(3**2)
-2 ** 2 # -4 - unary minus binds looser than ** on its left
not True == False # True - == binds tighter than not
The full operator precedence table is in the language reference. Still, the practical advice is not to memorise it. Add parentheses when the answer is not instantly obvious to a reader, because it costs nothing and prevents an entire class of bug.
Common mistakes with Python operators
- Using
isto compare numbers or strings. It depends on caching; use==. - Using
and/oron pandas Series. Use&/|with parentheses. - Relying on
orfor defaults when0is valid. Checkis Noneinstead. - Expecting
/to return an int. Use//, and remember it floors negatives. - Forgetting that
+=mutates lists in place. Other names bound to the list see the change.
Next in the series, user-defined functions shows how to package these expressions into reusable code.
The Sunday Growth Brief
One email a week: the best new comparisons, a fresh roadmap and the tech news worth your attention.
No spam. Unsubscribe in one click.
Related reading
Python numeric types covers int, float and Decimal in depth. Python basics covers the core types these operate on.
Frequently asked questions
What is the difference between == and is?
== asks whether two objects have the same value; is asks whether they are literally the same object in memory. Use == for comparisons and reserve is for None and other singletons such as sentinel objects. For booleans, PEP 8 recommends plain truth tests like if flag: rather than comparing with is True or == True.
Why does `a is b` return True for 256 but False for 257?
CPython pre-allocates small integers from -5 to 256, so identical small values share one object. Larger integers usually get fresh objects, although the compiler may reuse a constant within one script. This is an implementation detail, not a language guarantee, and it is exactly why you should never use is to compare numbers.
What does the walrus operator do?
The walrus operator, :=, assigns a value inside an expression and was added in Python 3.8. It helps when you need to both test and keep a value, such as if (m := pattern.search(line)):, which avoids a separate assignment line and a second search. Use it where it removes duplication, not to squeeze two clear lines into one.
What is the difference between / and //?
/ is true division and always returns a float, even when the result is whole. // is floor division, returning the largest integer less than or equal to the result. Note that it floors toward negative infinity, so -7 // 2 is -4, not -3. If you want truncation toward zero instead, use int(-7 / 2).
Written by
Sivaranjani S
Cloud Operations Engineer, Zoho
Cloud operations engineer and technical writer at Zoho, previously technical content consultant at 1stepGrow Academy.

