Operators #

An operator is a symbol that tells Python to perform a specific operation on one or more values (called operands). Python has more operator types than meet the eye — and some of them behave in non-intuitive ways if you’re coming from another language. For example, Python’s and and or don’t always produce True or False; they return one of the operands. The is and == operators look similar but test very different things. This article covers all Python operators thoroughly — including behavior, traps, and when to use them.

Arithmetic Operators #

Arithmetic operators handle basic mathematical operations. Python provides seven arithmetic operators, two of which (// and **) are often missing from other languages.

x = 17
y = 5

print(x + y)    # → 22   addition
print(x - y)    # → 12   subtraction
print(x * y)    # → 85   multiplication
print(x / y)    # → 3.4  division (ALWAYS a float in Python 3)
print(x // y)   # → 3    floor division
print(x % y)    # → 2    modulus / remainder
print(x ** y)   # → 1419857  exponentiation (17⁵)

Important Behaviors to Note #

# Division (/) always produces a float even when it divides evenly
print(10 / 2)    # → 5.0  (a float, not 5!)
print(10 / 5)    # → 2.0  (a float, not 2!)

# ANTI-PATTERN: assuming / produces an int
item_count = 10
per_page = 5
pages = item_count / per_page   # → 2.0, not 2
range(pages)   # TypeError: 'float' object cannot be interpreted as an integer

# CORRECT: use // if you need an integer result
pages = item_count // per_page  # → 2

# Floor division on negative numbers — results round DOWN
print(17 // 5)    # →  3
print(-17 // 5)   # → -4  (not -3! rounds down toward -inf)
print(17 // -5)   # → -4  (not -3!)

# Modulus follows the sign of the divisor (not the dividend) in Python
print(17 % 5)     # →  2
print(-17 % 5)    # →  3  (not -2! follows the sign of y)
print(17 % -5)    # → -3  (not  2! follows the sign of y)

Operators on Non-Numeric Types #

Python supports operator overloading — other types can use arithmetic operators with different meanings:

# Strings: + for concatenation, * for repetition
print("Hello" + " " + "World")   # → Hello World
print("Ha" * 3)                   # → HaHaHa
print(3 * "Na" + " Batman!")      # → NaNaNa Batman!

# Lists: + for merging, * for repeating
print([1, 2] + [3, 4])            # → [1, 2, 3, 4]
print([0] * 5)                    # → [0, 0, 0, 0, 0]

# ANTI-PATTERN: + for merging lists in a loop
result = []
for i in range(5):
    result = result + [i]   # creates a new list every iteration — slow!

# CORRECT: use append() or extend()
result = []
for i in range(5):
    result.append(i)       # in-place modification — efficient

Comparison Operators #

Comparison operators compare two values and always produce True or False.

x = 10
y = 20

print(x == y)    # → False  equal to
print(x != y)    # → True   not equal to
print(x > y)     # → False  greater than
print(x < y)     # → True   less than
print(x >= y)    # → False  greater than or equal to
print(x <= y)    # → True   less than or equal to

Chained Comparison — A Unique Python Feature #

Python allows chained comparisons that read more naturally:

score = 75

# ANTI-PATTERN: other-language style
if score >= 60 and score < 80:
    print("Fair")

# CORRECT: chained comparison — more expressive and readable
if 60 <= score < 80:
    print("Fair")

# More examples
x = 5
print(1 < x < 10)      # → True  (x is between 1 and 10)
print(1 < x < 4)       # → False
print(0 == False == 0)  # → True  (chaining can be longer than two)

String Comparison #

# Strings compare lexicographically (Unicode order)
print("apple" < "orange")    # → True  ('a' < 'o' in Unicode)
print("Budi" < "budi")       # → True  (uppercase < lowercase)
print("abc" == "abc")        # → True
print("10" > "9")            # → False ('1' < '9' in Unicode — careful!)

# ANTI-PATTERN: comparing numeric strings lexicographically
version_list = ["10", "9", "2", "1"]
print(sorted(version_list))   # → ['1', '10', '2', '9'] ← wrong order!

# CORRECT: convert to int for numeric sorting
print(sorted(version_list, key=int))  # → ['1', '2', '9', '10'] ← correct

Logical Operators #

Logical operators combine boolean expressions. Python has three: and, or, and not.

print(True and True)    # → True
print(True and False)   # → False
print(False or True)    # → True
print(False or False)   # → False
print(not True)         # → False
print(not False)        # → True

Short-Circuit Evaluation #

This is the most important behavior of logical operators and often misunderstood:

  • and stops and returns the first falsy operand, or the last operand if all are truthy
  • or stops and returns the first truthy operand, or the last operand if all are falsy

To visualize how the Python interpreter short-circuits when evaluating the and and or operators, look at the flow diagrams below:

flowchart TD
    subgraph AND ["Logical operator 'and' (A and B)"]
        and_start["Evaluate Operand A"] --> and_check{"Is A Truthy?"}
        and_check -->|"No / Falsy"| and_ret_a["Return A (Stop/Short-circuit)"]
        and_check -->|"Yes / Truthy"| and_ret_b["Evaluate & Return B"]
    end

    subgraph OR ["Logical operator 'or' (A or B)"]
        or_start["Evaluate Operand A"] --> or_check{"Is A Truthy?"}
        or_check -->|"Yes / Truthy"| or_ret_a["Return A (Stop/Short-circuit)"]
        or_check -->|"No / Falsy"| or_ret_b["Evaluate & Return B"]
    end

With this logic, the second operand is never evaluated at all if the final result is already determined by the first operand. This is what prevents runtime errors in the guard pattern like data and data[0].

# and — return the first falsy operand, or the last if all are truthy
print(1 and 2)          # → 2     (1 is truthy, continue to 2, return 2)
print(0 and 2)          # → 0     (0 is falsy, stop, return 0)
print("" and "hello")   # → ""    ("" is falsy, stop)
print("a" and "b")      # → "b"   (all truthy, return the last)

# or — return the first truthy operand, or the last if all are falsy
print(1 or 2)           # → 1     (1 is truthy, stop, return 1)
print(0 or 2)           # → 2     (0 is falsy, continue, return 2)
print("" or "hello")    # → "hello"
print("" or 0)          # → 0     (all falsy, return the last)

Using Short-Circuit Idiomatically #

# Default-value pattern with or
name_input = ""
name = name_input or "Guest"   # if name_input is falsy, use "Guest"
print(name)   # → Guest

config = None
host = config or "localhost"
print(host)   # → localhost

# Guard pattern with and — execute only if the condition holds
data = [1, 2, 3]
result = data and data[0]   # grab the first element only if data isn't empty
print(result)   # → 1

data = []
result = data and data[0]   # data is falsy, short-circuit, no data[0] access
print(result)   # → []  (no IndexError!)

# ANTI-PATTERN: writing wordy conditions
if user is not None:
    if user.active is True:
        show_dashboard()

# CORRECT: leverage short-circuit
if user and user.active:
    show_dashboard()
Because and and or return one of the operands (not always True/False), be careful when storing their results in variables. Use bool() explicitly if you actually need a boolean: active = bool(user and user.active).

Assignment Operators #

Assignment operators set a value to a variable. Besides the basic =, Python provides compound assignment operators that shorten read-modify-write operations.

x = 10       # basic assignment

x += 5       # x = x + 5    → 15
x -= 3       # x = x - 3    → 12
x *= 2       # x = x * 2    → 24
x /= 4       # x = x / 4    → 6.0
x //= 2      # x = x // 2   → 3.0
x %= 2       # x = x % 2    → 1.0
x **= 3      # x = x ** 3   → 1.0

# Also works for strings and lists
s = "Hello"
s += " World"    # → "Hello World"

lst = [1, 2]
lst += [3, 4]    # equivalent to lst.extend([3, 4]) → [1, 2, 3, 4]

The Walrus Operator := (Python 3.8+) #

The walrus operator (:=) allows assignment inside an expression. It’s called “walrus” because := resembles a walrus’s eyes and tusks.

# Without walrus: read the data twice or use a temporary variable
import re

text = "Phone number: 081234567890"
match = re.search(r"\d{10,13}", text)
if match:
    print(f"Found: {match.group()}")

# With walrus: more concise
if match := re.search(r"\d{10,13}", text):
    print(f"Found: {match.group()}")
# The walrus operator shines in while loops
# ANTI-PATTERN: reading data twice
data = read_chunk()
while data:
    process(data)
    data = read_chunk()

# CORRECT: walrus removes the duplication
while data := read_chunk():
    process(data)
# Walrus in list comprehensions — filter and transform at once
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# ANTI-PATTERN: computing twice for filter and transform
even_squares = [x**2 for x in numbers if x**2 > 25]

# CORRECT: compute once with walrus
even_squares = [y for x in numbers if (y := x**2) > 25]
print(even_squares)   # → [36, 49, 64, 81, 100]

Bitwise Operators #

Bitwise operators work directly on the binary representation of integers. They’re most often used in systems programming, flag manipulation, cryptography, and data compression.

a = 0b1100   # 12 in decimal
b = 0b1010   # 10 in decimal

#          a = 1100
#          b = 1010
#              ────
print(a & b)   # AND  → 1000 = 8   (1 only if BOTH are 1)
print(a | b)   # OR   → 1110 = 14  (1 if EITHER is 1)
print(a ^ b)   # XOR  → 0110 = 6   (1 if they DIFFER)
print(~a)      # NOT  → -(a+1) = -13  (flip all bits)
print(a << 2)  # Left shift  → 110000 = 48  (multiply by 2^2)
print(a >> 1)  # Right shift → 110    = 6   (divide by 2^1)

Practical Bitwise Usage #

# Flag usage with bitwise OR and AND
PERM_READ   = 0b001   # 1
PERM_WRITE  = 0b010   # 2
PERM_DELETE = 0b100   # 4

# Assign several permissions at once with |
editor_perms = PERM_READ | PERM_WRITE    # → 0b011 = 3
admin_perms  = PERM_READ | PERM_WRITE | PERM_DELETE  # → 0b111 = 7

# Check whether a specific permission is held with &
def has_perm(user_perms, checked_perm):
    return bool(user_perms & checked_perm)

print(has_perm(editor_perms, PERM_READ))    # → True
print(has_perm(editor_perms, PERM_DELETE))  # → False
print(has_perm(admin_perms, PERM_DELETE))   # → True

# Left shift = multiply by a power of 2 (faster than **)
print(1 << 0)   # → 1    (2⁰)
print(1 << 1)   # → 2    (2¹)
print(1 << 8)   # → 256  (2⁸)
print(1 << 10)  # → 1024 (2¹⁰)

Membership Operators #

The in and not in operators check whether a value exists inside a collection or string.

# On strings
print("Python" in "Learning Python")    # → True
print("Java" not in "Learning Python")  # → True

# On lists
fruits = ["apple", "orange", "mango"]
print("apple" in fruits)        # → True
print("durian" in fruits)       # → False
print("durian" not in fruits)   # → True

# On dicts — checks the KEY, not the value
data = {"name": "Budi", "age": 25}
print("name" in data)         # → True  (checks the key)
print("Budi" in data)         # → False (a value, not a key!)
print("Budi" in data.values()) # → True  (if you want to check values)

# On sets — O(1), much faster than lists for large data
number_set = {1, 2, 3, 4, 5}
print(3 in number_set)    # → True  (O(1))
print(6 in number_set)    # → False (O(1))
# ANTI-PATTERN: repeated membership checks on a large list
banned_list = ["user1", "user2", ...]   # thousands of elements
for request in incoming_requests:
    if request.user in banned_list:   # O(n) every time!
        reject(request)

# CORRECT: convert to a set for O(1) lookup
banned_set = set(banned_list)
for request in incoming_requests:
    if request.user in banned_set:      # O(1) every time
        reject(request)

Identity Operators #

The is and is not operators check whether two variables point to the same object in memory — not just whether their values are equal.

# is vs == — the crucial difference
a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)    # → True   (values are equal)
print(a is b)    # → False  (different objects in memory)
print(a is c)    # → True   (c is an alias of a, the SAME object)

print(id(a))     # → e.g. 140234567
print(id(b))     # → e.g. 140234890  (different)
print(id(c))     # → 140234567       (same as a)
# ANTI-PATTERN: using is to compare values
x = 1000
y = 1000
print(x is y)    # → can be True OR False depending on the implementation!
                 # Python caches small ints (-5 to 256)

print(256 is 256)   # → True  (cached)
print(257 is 257)   # → possibly False (not cached)
print("hello" is "hello")  # → possibly True (string interning)

# CORRECT: use == for value comparison
print(x == y)    # → True (always correct for value comparison)

# is is ONLY appropriate for:
# 1. Comparing with None
if result is None:
    pass

# 2. Comparing with True/False (rarely needed)
if value is True:
    pass
Don’t use is to compare ints, strings, lists, or any other values — use ==. Python caches some small objects (ints -5 to 256, short strings), so is can give inconsistent results depending on the value and the interpreter implementation.

Operator Precedence #

When several operators appear in one expression, Python evaluates them by precedence order (highest to lowest):

PrecedenceOperatorDescription
1 (high)()parentheses
2**exponentiation
3+x, -x, ~xunary (positive, negative, bitwise NOT)
4*, /, //, %multiplication, division
5+, -addition, subtraction
6<<, >>bitwise shift
7&bitwise AND
8^bitwise XOR
9|bitwise OR
10==, !=, <, >, <=, >=, in, not in, is, is notcomparison & identity
11notlogical NOT
12andlogical AND
13 (low)orlogical OR
# Precedence examples in real expressions
print(2 + 3 * 4)        # → 14  (* binds tighter than +)
print((2 + 3) * 4)      # → 20  (parentheses change precedence)
print(2 ** 3 ** 2)      # → 512 (** is right-associative: 2**(3**2) = 2**9)
print((2 ** 3) ** 2)    # → 64

# Logic: not → and → or
print(True or False and False)       # → True  (and binds tighter than or)
print(True or (False and False))     # → True  (same, because and comes first)
print((True or False) and False)     # → False (parentheses change the order)

# ANTI-PATTERN: relying on memory of precedence for complex expressions
if x > 0 and y > 0 or z == 0:       # ambiguous when read
    pass

# CORRECT: use parentheses to make the intent clear
if (x > 0 and y > 0) or (z == 0):  # clear intent
    pass

Summary #

  • / always produces a float — use // if you need an integer division result.
  • // and % on negative numbers round toward -∞ — the results can surprise: -17 // 5 is -4, not -3.
  • and and or don’t always produce True/False — both return one of the operands based on short-circuit evaluation.
  • Short-circuit or for default valuesname = input_name or "Guest" is a clean, common Python idiom.
  • The walrus := allows assignment inside expressions — very useful in while loops and comprehensions to avoid double computation.
  • is is only for None, not for values — use == to compare values; is compares object identity in memory.
  • in on a set is O(1) — convert a list to a set if you need repeated membership checks on large data.
  • Use parentheses for complex expressions — don’t rely on memorized operator precedence when an expression involves more than two different operators.

← Previous: Data Types   Next: Conditional Statements →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact