Operators are the building blocks of any Python expression. They perform operations on values and variables, and the values they act upon are called operands. Python gives you both keyword-based and symbol-based operators, and I use them every day in production code without thinking twice about the distinction.
This article covers every operator type Python ships with, from basic arithmetic through bitwise manipulation, operator overloading, and the operator module. By the end, you will know exactly what each operator does, how precedence works, and how to overload them for your own classes.
Arithmetic Operators
Arithmetic operators work on numeric types, and Python also supports two of them on strings. The full set is: addition +, subtraction -, multiplication *, true division /, floor division //, modulus %, and exponentiation **. All of these are special characters, not keywords.
Floor division deserves special attention. It rounds the result down to the nearest integer, which is not the same as truncating toward zero when negative numbers are involved. Python 3.13+ follows the same behavior as earlier versions here.
x = 15
y = 4
print(x + y) # 19
print(x - y) # 11
print(x * y) # 60
print(x / y) # 3.75 (true division always returns float)
print(x // y) # 3 (floor division)
print(x % y) # 3 (modulus: remainder after division)
print(x ** y) # 50625 (15 to the power of 4)
String operands behave differently. The + operator concatenates two strings, and * repeats a string a given number of times. No other arithmetic operators work on strings.
print("Py" + "thon") # Python
print("Ha" * 3) # HaHaHa
print("-" * 40) # ----------------------------------------
Integer Overflow Is Gone in Python 3
Unlike Java or C, Python integers have arbitrary precision. When a calculation produces a number larger than fits in a fixed-width integer type, Python automatically allocates more memory. There is no overflow in Python 3.13+, ever. This matters when you are working with financial calculations or cryptography where C languages would silently wrap values.
Comparison Operators
Comparison operators evaluate two values and return a bool: either True or False. Python supports six comparison operators. The equality check uses == (two equals signs), while a single = is the assignment operator and will not work for comparison.
x = 10
y = 20
print(x == y) # False
print(x != y) # True
print(x < y) # True
print(x > y) # False
print(x <= y) # True
print(x >= y) # False
Comparisons also work between strings, where Python uses lexicographic (dictionary) order. Uppercase letters sort before lowercase letters because ASCII values differ.
print("apple" < "banana") # True
print("Zoo" < "apple") # True (uppercase Z < lowercase a)
print("2" < "10") # False (character '2' > character '1')
You can chain comparisons in Python, which is a feature most languages do not support. The expression a < b < c evaluates as (a < b) and (b < c) but b is only evaluated once.
x = 5
print(0 < x < 10) # True
print(0 < x < 3) # False
Bitwise Operators
Bitwise operators manipulate individual bits of integer values. Python converts operands to integers, performs the operation on each bit position, and returns the result. These operators are critical for low-level programming, flag masks, and cryptographic code.
x = 10 # binary: 1010
y = 4 # binary: 0100
print(x & y) # 0 (1010 AND 0100 = 0000)
print(x | y) # 14 (1010 OR 0100 = 1110)
print(x ^ y) # 14 (1010 XOR 0100 = 1110)
print(~x) # -11 (bitwise NOT: -(x+1))
print(x << 2) # 40 (1010 shifted left 2 = 101000)
print(x >> 2) # 2 (1010 shifted right 2 = 10)
The unary ~ operator returns the bitwise complement. For any integer n, ~n equals -(n+1). This follows from how two’s complement representation works for signed integers.
Shift Operators on Negative Numbers
Right shift on negative numbers preserves the sign bit in Python. Left shift always shifts in zeros on the right. Python 3.13+ handles these consistently with earlier versions.
x = -8
print(x >> 2) # -2
print(x << 2) # -32
Logical Operators
Python has three logical operators: and, or, and not. Unlike bitwise operators that work on integers, logical operators work on boolean values and return one of the actual operand objects, not necessarily a bool. This is called short-circuit evaluation.
b1 = True
b2 = False
print(b1 and b2) # False
print(b1 or b2) # True
print(not b1) # False
Short-circuit evaluation means Python stops evaluating as soon as the result is determined. and returns the first falsy value or the last value if all are truthy. or returns the first truthy value or the last value if all are falsy.
# Short-circuit demonstration
x = 0
# x is falsy, so and stops here and returns 0 (not evaluates y)
result = x and 1 / 0 # Returns 0, no ZeroDivisionError
# x is falsy, so or continues and returns 5
result = x or 5 # Returns 5
print(result)
This behavior is useful for default values. Instead of writing value if value else default, you can write value or default when you want 0 or "" to also trigger the default.
Assignment Operators
The simple assignment operator = binds a name to an object. Python evaluates the right side first, then binds the name. Compound assignment operators perform an operation and assignment in one step: +=, -=, *=, /=, //=, %=, **=.
a = 10
a += 5 # a = 15
a -= 3 # a = 12
a *= 2 # a = 24
a /= 4 # a = 6.0 (division always produces float in Python 3)
a //= 2 # a = 3.0
a %= 2 # a = 1.0
a **= 3 # a = 1.0
Note that /= always produces a float even when the operands are integers and the result is whole. This is a deliberate Python 3 design decision that avoids the floor-versus-truncation ambiguity that plagued Python 2.
Walrus Operator (Named Assignment)
Python 3.8+ introduced the walrus operator :=, which assigns a value to a variable as part of a larger expression. This is useful when you want to compute a value once and use it in a condition.
if (n := len([1, 2, 3, 4, 5])) > 3:
print(f"List has {n} items")
Membership Operators
Membership operators test whether a value exists inside a sequence. Python has two: in and not in. These operators work on lists, tuples, strings, dictionaries, and sets. For dictionaries, they check keys, not values.
my_list = [1, 2, 3, 4]
my_dict = {"a": 1, "b": 2}
my_str = "Hello"
print(3 in my_list) # True
print(99 in my_list) # False
print("a" in my_dict) # True
print(1 in my_dict) # False (1 is a value, not a key)
print("ell" in my_str) # True
print("x" not in my_str) # True
Membership testing on lists is O(n) because Python must scan each element. On sets and dictionaries it is O(1) average case. If you find yourself doing many membership tests on a list, converting it to a set first will be significantly faster for large collections.
large_list = list(range(1_000_000))
large_set = set(large_list)
# Fast: O(1) lookup
print(999999 in large_set) # True
# Slow: O(n) scan
print(999999 in large_list) # True
Identity Operators
Identity operators test whether two variables refer to the same object in memory, not just whether they are equal. The two operators are is and is not. This distinction matters because two equal values may or may not be the same object.
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a is b) # False (different objects)
print(a is c) # True (same object)
print(a == b) # True (equal values)
# String interning: small strings may be the same object
x = "hello"
y = "hello"
print(x is y) # True (implementation-dependent)
Python performs string interning for identifiers and string literals in some contexts, so you may find that two string variables point to the same object. Do not rely on this behavior. Always use == for value comparison and is only when you specifically need identity comparison.
Using is with None
The canonical use of is is checking for None. Because None is a singleton in Python, is and == behave the same way for None, but is is preferred because it is faster and signals intent clearly.
result = None
if result is None:
print("No result yet")
if result is not None:
print(f"Result: {result}")
Operator Precedence
When an expression contains multiple operators, Python evaluates them in a defined order called operator precedence. The table below lists operators from highest to lowest precedence. Operators on the same line have equal precedence and are evaluated left to right except for **, which is evaluated right to left.
# Without parentheses:
# ** binds tightest, then unary ~, then *///%, then +-, then <<>>, then &, then ^, then |, then comparisons, then not, then and, then or
result = 2 + 3 * 4 ** 2 # 2 + 3 * 16 = 2 + 48 = 50
result = (2 + 3) * 4 ** 2 # 5 * 16 = 80
# right-to-left binding for **
result = 2 ** 3 ** 2 # 2 ** 9 = 512, NOT 64
When you are unsure about precedence, use parentheses. They make intent clear and save anyone reading your code (including future you) from having to remember the full precedence table.
Python Operator Overloading
Python lets you define how operators behave when used with instances of your own classes. This is called operator overloading, and it works by implementing special methods (also called dunder methods because they have double underscores on both sides). When you write a + b, Python calls a.__add__(b).
By default, these special methods either return a NotImplemented object or raise a TypeError if the operation is not defined. This is how you get the familiar “unsupported operand type” error.
class Vector:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
def __add__(self, other: "Vector") -> "Vector":
if not isinstance(other, Vector):
return NotImplemented
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self) -> str:
return f"Vector({self.x}, {self.y})"
v1 = Vector(1.0, 2.0)
v2 = Vector(3.0, 4.0)
print(v1 + v2) # Vector(4.0, 6.0)
The key rule is that you must return NotImplemented (not raise an exception) when you do not know how to handle an operand. This allows Python to try the reversed operation on the other operand. If both return NotImplemented, Python raises TypeError.
Complete Operator Method Reference
Python defines special methods for every operator. Implementing the ones you need gives users of your class a natural syntax without sacrificing functionality.
# Arithmetic
__add__(self, other) # a + b
__sub__(self, other) # a - b
__mul__(self, other) # a * b
__truediv__(self, other) # a / b
__floordiv__(self, other) # a // b
__mod__(self, other) # a % b
__pow__(self, other) # a ** b
__and__(self, other) # a & b
__or__(self, other) # a | b
__xor__(self, other) # a ^ b
# Comparison
__eq__(self, other) # a == b
__ne__(self, other) # a != b
__lt__(self, other) # a < b
__le__(self, other) # a <= b
__gt__(self, other) # a > b
__ge__(self, other) # a >= b
The operator Module
The operator module from Python’s standard library provides functional equivalents of Python’s operators. Each function corresponds to a special method. These are useful when you want to pass an operator as an argument to a higher-order function like map, sorted, or functools.reduce.
import operator
# Equivalent to lambda a, b: a + b
add = operator.add
print(add(10, 20)) # 30
# Equivalent to lambda a, b: a * b
mul = operator.mul
print(mul(6, 7)) # 42
# For itemgetter and attrgetter, see the operator module docs
from operator import itemgetter, attrgetter
students = [("Alice", 85), ("Bob", 92), ("Carol", 78)]
sorted_by_grade = sorted(students, key=itemgetter(1), reverse=True)
print(sorted_by_grade) # [('Bob', 92), ('Alice', 85), ('Carol', 78)]
The operator module is also the reason Python’s special methods use double underscores. The module mirrors the special method names, just without the leading and trailing underscores. For example, operator.add calls __add__, operator.lt calls __lt__, and so on.
Summary
Python gives you seven categories of operators. Arithmetic operators handle numeric math and string repetition. Comparison operators return booleans and support chaining. Bitwise operators work at the individual bit level. Logical operators short-circuit on boolean and non-boolean values alike. Assignment operators combine computation with rebinding. Membership and identity operators test containment and object identity respectively.
Operator precedence determines evaluation order when expressions are complex, and operator overloading lets your classes participate in Python’s operator syntax through special methods. The operator module brings the same functionality into the functional programming world. These concepts form the foundation for everything else in Python, from basic scripts to advanced frameworks.
