Why does 10 / 2 give 5.0 instead of 5? Why does "a" or "b" return a string instead of True? Python operators hide small surprises like these in plain sight, and they cost real debugging time. This guide covers all seven operator families with a quick reference table up front, one tested example per group, and a precedence chart worth bookmarking.
“There should be one (and preferably only one) obvious way to do it.”
Tim Peters, The Zen of Python
Last Updated: July 2026 | Tested on: Python 3.14.6 | Difficulty: Beginner | Reading Time: 19 minutes
If variables are the nouns, Python operators are the verbs. Variables hold your data, but operators do things with it: add it, compare it, combine it, shift its bits, check whether a value sits inside a collection. Python groups them into seven families, and here is the part that surprises people. The same symbol can do completely different jobs depending on what it operates on. The + sign adds two numbers, glues two strings together, and merges two lists. One symbol, three behaviors, all decided by the type sitting on either side.
Think of this post like the index card you keep stuck to your monitor. Most people land here from a search, looking up one of the Python operators. So the quick reference table sits right at the top, no scrolling past theory. Skim it, grab the one you came for, and read the matching section if the behavior is not obvious.
Table of Contents
Quick Reference Table
| Category | Operators | Example | Result |
|---|---|---|---|
| Arithmetic | + – * / // % ** | 10 // 3 | 3 |
| Comparison | == != < > <= >= | 5 > 3 | True |
| Logical | and or not | True and False | False |
| Assignment | = += -= *= /= //= %= **= | x += 5 | x = x + 5 |
| Identity | is, is not | x is None | True/False |
| Membership | in, not in | “a” in “Rahul” | True |
| Bitwise | & | ^ ~ << >> | 5 & 3 | 1 |
Arithmetic Operators
📄 arithmetic.py: seven arithmetic operators
a, b = 17, 5
print(f"a + b = {a + b}") # Addition
print(f"a - b = {a - b}") # Subtraction
print(f"a * b = {a * b}") # Multiplication
print(f"a / b = {a / b}") # True division (always float)
print(f"a // b = {a // b}") # Floor division (rounds down)
print(f"a % b = {a % b}") # Modulo (remainder)
print(f"a ** b = {a ** b}") # Exponentiation
# The difference between / and //
print(f"\n7 / 2 = {7 / 2}") # 3.5 (true division)
print(f"7 // 2 = {7 // 2}") # 3 (floor division)
print(f"-7 // 2 = {-7 // 2}") # -4 (floors toward negative infinity!)
▶ Output
a + b = 22 a - b = 12 a * b = 85 a / b = 3.4 a // b = 3 a % b = 2 a ** b = 1419857 7 / 2 = 3.5 7 // 2 = 3 -7 // 2 = -4
What happened here: The one to watch is / versus //. True division (/) always hands back a float, even for a clean 6 / 2 which gives 3.0, not 3. Floor division (//) rounds toward negative infinity, not toward zero, which is why -7 // 2 comes out as -4 and not -3. The modulo operator % gives you the remainder, and it earns its keep everywhere: check if a number is even with n % 2 == 0, or wrap an index back to the start of a list with index % length, the same way a clock rolls from 59 minutes back to 0.
Comparison Operators
A comparison operator is like a weighing balance: you put a value on each side, and it can only tip one way or the other. Every comparison answers with a plain True or False, nothing in between. Say two students, Viraj and Niranjan, just got their quiz scores back. Each line below is Python answering one question about those two numbers.
📄 comparison.py: six comparison operators plus chaining
viraj_score = 88
niranjan_score = 92
print(f"Equal: {viraj_score == niranjan_score}")
print(f"Not equal: {viraj_score != niranjan_score}")
print(f"Greater: {viraj_score > niranjan_score}")
print(f"Less: {viraj_score < niranjan_score}")
print(f"Greater/equal: {viraj_score >= 88}")
print(f"Less/equal: {niranjan_score <= 92}")
# Python lets you chain comparisons, which most languages do not
age = 25
print(f"\n18 <= age <= 35: {18 <= age <= 35}") # True, no 'and' needed
print(f"1 < 2 < 3 < 4: {1 < 2 < 3 < 4}") # True, every step is checked
# String comparison (alphabetical, or lexicographic to be precise)
print(f"\n'apple' < 'banana': {'apple' < 'banana'}")
print(f"'A' < 'a': {'A' < 'a'}") # uppercase sorts before lowercase
▶ Output
Equal: False Not equal: True Greater: False Less: True Greater/equal: True Less/equal: True 18 <= age <= 35: True 1 < 2 < 3 < 4: True 'apple' < 'banana': True 'A' < 'a': True
What happened here: Comparison chaining (18 <= age <= 35) is one of those small Python touches you will miss in every other language. In Java or C you would have to spell it out as age >= 18 && age <= 35. Python reads it the way you would say it out loud: is age between 18 and 35. String comparisons work alphabetically by comparing Unicode code points, character by character, like sorting words in a dictionary. Uppercase letters have smaller code points than lowercase ones, so 'A' < 'a' is True.
Logical Operators
📄 logical.py: and, or, not with short-circuit behavior
# Basic logical operations
print(f"True and True: {True and True}")
print(f"True and False: {True and False}")
print(f"True or False: {True or False}")
print(f"not True: {not True}")
# Short-circuit evaluation: Python stops as soon as it knows the answer
# 'and' returns the first falsy value, or the last value if none are falsy
print(f"\n0 and 'hello': {0 and 'hello'}") # 0 (stopped at the first falsy)
print(f"'hi' and 'bye': {'hi' and 'bye'}") # 'bye' (both truthy, returns last)
# 'or' returns the first truthy value, or the last value
print(f"0 or 'hello': {0 or 'hello'}") # 'hello' (first truthy)
print(f"'' or 0 or None: {'' or 0 or None}") # None (all falsy, returns last)
# Practical use: default values
name = "" or "Anonymous"
print(f"\nDefault name: {name}")
▶ Output
True and True: True True and False: False True or False: True not True: False 0 and 'hello': 0 'hi' and 'bye': bye 0 or 'hello': hello '' or 0 or None: None Default name: Anonymous
What happened here: Here is the part that trips up people coming from other languages. Python’s and and or do not simply return True or False. They hand back the actual value that settled the result. and returns the first falsy value it hits, or the last value if everything is truthy. or returns the first truthy value, or the last value if everything is falsy. Think of or like asking a few friends for a ride: the moment one says yes, you stop asking and take that ride.
This behavior is called short-circuit evaluation, and it powers a tidy little default-value trick you will see in real code everywhere: name = user_input or "Anonymous" uses the typed name if there is one, and falls back to "Anonymous" when the input is empty. So if a visitor named Aditi fills in the name box, you keep "Aditi"; if she leaves it blank, the empty string is falsy and "Anonymous" steps in.
Assignment Operators
Picture a shopkeeper named Anvay keeping a running khata for a regular customer. When the customer buys something, Anvay does not rewrite the whole balance from scratch, he just adds the new amount to the figure already on the page. Augmented assignment operators work the same way: they update a variable using its current value, in one short line.
📄 assignment.py: augmented assignment shortcuts
score = 100
score += 10 # same as: score = score + 10
print(f"After += 10: {score}")
score -= 25 # same as: score = score - 25
print(f"After -= 25: {score}")
score *= 2 # same as: score = score * 2
print(f"After *= 2: {score}")
score //= 3 # same as: score = score // 3
print(f"After //= 3: {score}")
score %= 9 # same as: score = score % 9
print(f"After %= 9: {score}")
score **= 3 # same as: score = score ** 3
print(f"After **= 3: {score}")
▶ Output
After += 10: 110 After -= 25: 85 After *= 2: 170 After //= 3: 56 After %= 9: 2 After **= 3: 8
What happened here: Augmented assignment is just a shorthand. x += 5 means exactly the same thing as x = x + 5, you only type it once. Every augmented operator updates the variable in place using its own value, so score keeps changing line by line as the example runs. One thing to remember if you come from C or Java: Python has no ++ or --. To bump a counter, write x += 1, and to step it down, write x -= 1.
Identity and Membership Operators
📄 identity_membership.py: is, is not, in, not in
# Identity: is / is not check if it is the SAME object in memory
a = [1, 2, 3]
b = a # b points to the same object as a
c = [1, 2, 3] # c is a different object that happens to hold the same value
print(f"a is b: {a is b}") # True (same object)
print(f"a is c: {a is c}") # False (different objects)
print(f"a == c: {a == c}") # True (same value)
print(f"a is not c: {a is not c}") # True
# Only reach for 'is' when checking against None
x = None
print(f"\nx is None: {x is None}")
# Membership: in / not in check if a value exists in a container
team = ["Rahul", "Viraj", "Pravin", "Anvi"]
print(f"\n'Viraj' in team: {'Viraj' in team}")
print(f"'Niranjan' in team: {'Niranjan' in team}")
print(f"'Niranjan' not in team: {'Niranjan' not in team}")
# Works with strings too
print(f"'Py' in 'Python': {'Py' in 'Python'}")
print(f"'py' in 'Python': {'py' in 'Python'}") # case-sensitive, so this is False
▶ Output
a is b: True a is c: False a == c: True a is not c: True x is None: True 'Viraj' in team: True 'Niranjan' in team: False 'Niranjan' not in team: True 'Py' in 'Python': True 'py' in 'Python': False
What happened here: is asks "are these the exact same object in memory?" (same id()), while == asks "do these hold the same value?". Picture two people with identical phone numbers saved: the numbers match (== is True), but they are two separate contacts, not one shared entry (is is False). The full memory-model story lives in the variables tutorial. The in operator checks membership. In the example, team holds the names of four teammates, Rahul, Viraj, Pravin, and Anvi, so 'Niranjan' in team simply asks whether Niranjan made the roster.
It works across lists, tuples, sets, dicts (it checks the keys), and even strings, where it looks for a substring. Note that string membership is case-sensitive, which is why 'py' in 'Python' comes back False.
Bitwise Operators
📄 bitwise.py: operating on individual bits
a = 0b1010 # 10 in decimal
b = 0b1100 # 12 in decimal
print(f"a = {a:04b} ({a})")
print(f"b = {b:04b} ({b})")
print(f"a & b = {a & b:04b} ({a & b})") # AND: both bits must be 1
print(f"a | b = {a | b:04b} ({a | b})") # OR: either bit is 1
print(f"a ^ b = {a ^ b:04b} ({a ^ b})") # XOR: bits must differ
print(f"~a = {~a} (inverts all bits)") # NOT: flip all bits
print(f"a << 2 = {a << 2:08b} ({a << 2})") # Left shift: multiply by 4
print(f"a >> 1 = {a >> 1:04b} ({a >> 1})") # Right shift: divide by 2
▶ Output
a = 1010 (10) b = 1100 (12) a & b = 1000 (8) a | b = 1110 (14) a ^ b = 0110 (6) ~a = -11 (inverts all bits) a << 2 = 00101000 (40) a >> 1 = 0101 (5)
What happened here: Bitwise operators reach past the whole number and work on the individual 1 and 0 bits underneath. Think of each bit as a light switch in a row: & turns a bit on only when both switches are on, | turns it on when either is on, and ^ turns it on only when the two switches disagree. You will not touch these in everyday Python, but they show up in permission flags, network code, cryptography, and competitive programming.
Left shift (<<) moves every bit up one slot, which multiplies by a power of 2, and right shift (>>) moves them down, which divides by a power of 2. One detail worth flagging: ~a prints -11 rather than a tidy binary string, because Python integers are signed and have no fixed width, so flipping every bit of 10 lands on -11.
Operator Precedence
When several Python operators show up in one expression, the interpreter does not just read left to right. It follows a fixed pecking order, the same rules you learned in math class. Parentheses go first, then exponentiation, then multiplication and division, and finally addition and subtraction.
The diagram lays out Python operator precedence from highest (parentheses, at the top) down to lowest (the walrus operator, at the bottom), so you can see the exact order Python applies when it untangles a compound expression. The headline points: parentheses always win, exponentiation binds tighter than a unary minus, and the comparison, identity, and membership operators all sit below arithmetic. That last fact is why 2 + 3 > 4 just works, Python adds before it compares. When you are unsure how an expression will evaluate, glance at the diagram, but honestly, adding a pair of parentheses is the safer move every time.
📄 precedence.py: when precedence matters
# Precedence in action
print(f"2 + 3 * 4 = {2 + 3 * 4}") # 14, not 20 (* runs before +)
print(f"(2 + 3) * 4 = {(2 + 3) * 4}") # 20 (parentheses go first)
print(f"2 ** 3 ** 2 = {2 ** 3 ** 2}") # 512 (** is right to left: 2^(3^2) = 2^9)
print(f"not True or False = {not True or False}") # False (not runs before or)
# When in doubt, reach for parentheses. Clear beats clever.
result = (2 + 3) * (4 - 1) / (2 ** 2)
print(f"(2+3) * (4-1) / (2**2) = {result}")
▶ Output
2 + 3 * 4 = 14 (2 + 3) * 4 = 20 2 ** 3 ** 2 = 512 not True or False = False (2+3) * (4-1) / (2**2) = 3.75
What happened here: The exponentiation operator ** is the one oddball that groups right to left. So 2 ** 3 ** 2 runs as 2 ** (3 ** 2), which is 2 ** 9 = 512, not (2 ** 3) ** 2 = 64. Almost every other operator groups left to right, which is exactly why this one catches people. The takeaway is simple: once an expression has more than two operators, wrap the intent in parentheses. Your future self, reading this code at 2am, will thank you.
Common Mistakes
Mistake 1: Using = instead of ==
This one is so common it has its own folklore. The variables tutorial covers it, but it is worth saying again: = assigns a value, == compares two values. Writing if x = 5: does not quietly do the wrong thing, it raises a SyntaxError on the spot, which is Python protecting you from a classic C-style bug.
Mistake 2: Using 'is' to compare values
🚫 Python REPL: works sometimes, fails mysteriously
>>> a = 1000 >>> b = 1000 >>> a is b # 1000 is past the small-int cache, so two separate objects False >>> a == b # a value check is always correct, caching or not True >>> x = 100 >>> y = 100 >>> x is y # 100 is cached (-5 to 256), so the SAME object is reused True
py (or python) and no file name, then type the lines one at a time. Here is the catch that bites people. When Python compiles a whole .py file, it spots the two identical 1000 literals and keeps just one copy, so a is b comes back True even for a big number. The REPL compiles each line on its own, so it shows you the real cache boundary above. Same Python, opposite answer, purely because of how the code was compiled. That is the whole reason you never lean on is to compare numbers.Mistake 3: Confusing // with /
📄 division_trap.py
# / always returns a float, even for a clean division
print(f"6 / 2 = {6 / 2}") # 3.0, not 3
print(f"type: {type(6 / 2)}") # float
# // gives an int only when both operands are ints
print(f"7 // 2 = {7 // 2}") # 3 (int)
print(f"7.0 // 2 = {7.0 // 2}") # 3.0 (float, because 7.0 is a float)
▶ Output
6 / 2 = 3.0 type: <class 'float'> 7 // 2 = 3 7.0 // 2 = 3.0
Best Practices
- DO use parentheses when mixing operators:
(a + b) * c - DO use comparison chaining:
0 < x < 100instead ofx > 0 and x < 100 - DO use
infor membership tests:if name in team_list - DO use
isonly forNone:if result is None - DO use
orfor default values:name = user_input or "Anonymous" - DON’T use
isto compare numbers or strings - DON’T write
x = x + 1whenx += 1is clearer - DON’T chain Python operators into tangled expressions and lean on precedence. Add parentheses and make the intent obvious
Practice Exercises
- Exercise 1: Calculate area and perimeter of a circle (radius=5) using
**. - Exercise 2: Use
//and%to extract digits from a 4-digit number. - Exercise 3: Build a calculator taking two numbers and an operator string. Handle division by zero.
Conclusion
Python operators stretch all the way from basic math to flipping individual bits. If you remember only three things, make them these. First, / always returns a float, so reach for // when you want integer division. Second, and and or hand back real values, not just True or False, which is what makes the or default-value trick work. Third, is checks identity while == checks value, so keep is for None and use == for everything else. And when precedence gets murky, a pair of parentheses always wins.
Next up: String Operations. The complete reference for every string method, plus slicing, searching, and formatting tricks. And if you want the full roadmap from basics to AI/ML, browse every post in order at the Python + AI/ML tutorial series home.
Frequently Asked Questions
What is the difference between / and // in Python?
/ is true division, so it always returns a float, even for 6 / 2 which gives 3.0. // is floor division, so it rounds down to the nearest integer. For negative numbers, // rounds toward negative infinity: -7 // 2 gives -4, not -3.
Does Python have ++ and -- operators?
No. The Python operators list has no increment (++) or decrement (--). Use x += 1 and x -= 1 instead. Writing ++x in Python is valid syntax but does nothing useful, since it is read as two unary plus operators stacked together.
What is the walrus operator := in Python?
The walrus operator := (introduced in Python 3.8) assigns a value to a variable as part of an expression. For example, if (n := len(data)) > 10: assigns len(data) to n and checks if it exceeds 10 in one line. It is covered in detail in the walrus operator tutorial.
What is the difference between 'is' and '==' in Python?
== checks if two values are equal. is checks if two variables reference the exact same object in memory. Use == for value comparison and is only for identity checks, particularly x is None. Using is to compare numbers or strings can produce unexpected results due to Python's object caching.
How does short-circuit evaluation work in Python?
With and, Python stops at the first falsy value and returns it. With or, Python stops at the first truthy value and returns it. If no short-circuit occurs, the last value is returned. This means and/or return actual values, not just True/False. Example: 0 or 'default' returns 'default'.
What does the modulo operator % do in Python?
The modulo operator % returns the remainder of division. 17 % 5 gives 2 because 17 divided by 5 is 3 remainder 2. Common uses: check if a number is even (n % 2 == 0), cycle through values (index % length), and validate divisibility.
Interview Questions on Python Operators
The same ideas as they show up in real interviews, framed as scenarios you can practice out loud.
Q: A teammate writes if user_role == "admin" or "manager": and suddenly every user passes the admin check. What went wrong?
Python reads that as (user_role == "admin") or ("manager"), and the non-empty string "manager" is always truthy, so the whole condition is always True. The or operator joins two conditions, it does not distribute the comparison across values. The fix is either user_role == "admin" or user_role == "manager" or, more Pythonic, user_role in ("admin", "manager").
Q: You filter a pandas DataFrame with df[(df.age > 18) and (df.age < 35)] and get "ValueError: The truth value of a Series is ambiguous." What do you check first?
The and keyword needs each side to collapse into a single True or False, but a pandas Series holds many booleans at once, so Python cannot decide and raises the error. Libraries like pandas and NumPy overload the bitwise operators &, |, and ~ to work element by element instead. Rewrite it as df[(df.age > 18) & (df.age < 35)], and keep the parentheses, because & binds tighter than the comparisons.
Q: Is x += y always the same as x = x + y?
For numbers and strings, yes, the result is identical. For mutable types like lists, they differ: x += y extends the existing list in place, while x = x + y builds a brand new list and rebinds x to it. That matters when another variable points at the same list, because after += both variables see the change, but after x = x + y the other variable still holds the old list.
Q: What does 2 ** 3 ** 2 evaluate to, and why?
It evaluates to 512. Exponentiation is the rare operator that groups right to left, so Python computes 3 ** 2 first, then 2 ** 9. Most other operators group left to right, which is why interviewers like this one. If you want (2 ** 3) ** 2, which is 64, you have to write the parentheses yourself.
Q: Why does 0.1 + 0.2 == 0.3 return False, and how should you compare floats?
Floats are stored in binary, and 0.1 and 0.2 have no exact binary representation, so the sum comes out as 0.30000000000000004. The == operator then correctly reports that this is not exactly 0.3. Compare floats with a tolerance instead, either math.isclose(0.1 + 0.2, 0.3) or by checking abs(a - b) < 1e-9. For money, use the decimal module and avoid the problem entirely.
Q: In a chained comparison like a < b < c, how many times is b evaluated?
Exactly once. Python treats a < b < c as a < b and b < c, except the middle expression is evaluated a single time and the chain short-circuits: if a < b is already False, c is never touched. That makes chaining both safer and faster than writing the two comparisons by hand when b is an expensive function call.
Go deeper: the official Python documentation covers every edge case of this topic.
Related Posts
Previous: Python Error Messages Explained: How to Read a Traceback
Next: Python: String Operations, The Complete Method Reference
Series Home: Python + AI/ML Tutorial Series

No comment