Ch 3.1 Arithmetic, Comparison, and Logical Operators
Operators are the fundamental tools for manipulating and comparing data. Python's operators are intuitive and powerful.
1. Arithmetic Operators
a = 17
b = 5
print(a + b) # 22 — addition
print(a - b) # 12 — subtraction
print(a * b) # 85 — multiplication
print(a / b) # 3.4 — division (always returns float)
print(a // b) # 3 — floor division (quotient, truncates decimal)
print(a % b) # 2 — remainder (modulo)
print(a ** b) # 1419857 — exponentiation (17^5)
# Difference between / and //
print(7 / 2) # 3.5 (always float)
print(7 // 2) # 3 (floor division)
print(-7 // 2) # -4 (negative numbers floor toward negative infinity!)
# ** operator
print(2 ** 10) # 1024
print(2 ** 0.5) # 1.4142... (square root)
print(2 ** -1) # 0.5
Math Functions
import math
# Using the math module
print(math.sqrt(16)) # 4.0 (square root)
print(math.floor(3.7)) # 3 (floor)
print(math.ceil(3.2)) # 4 (ceiling)
print(math.factorial(5)) # 120
print(math.log(100, 10)) # 2.0 (log base 10 of 100)
print(math.pi) # 3.141592653589793
# Built-in functions
print(abs(-42)) # 42 (absolute value)
print(round(3.14159, 2)) # 3.14
print(pow(2, 8)) # 256
quotient, remainder = divmod(17, 5) # (3, 2)
2. Comparison Operators
x = 10
y = 20
print(x == y) # False — equal
print(x != y) # True — not equal
print(x < y) # True — less than
print(x > y) # False — greater than
print(x <= y) # True — less than or equal
print(x >= y) # False — greater than or equal
# Comparison operators return bool
result = x < y
print(type(result)) # <class 'bool'>
Comparison Operator Chaining
A Python-exclusive feature — you can chain comparisons just like in mathematics.
score = 75
# Chaining is possible only in Python (other languages require and)
print(60 <= score < 80) # True (grade B)
print(0 <= score <= 100) # True (valid score)
# Equivalent expression (more verbose)
print(60 <= score and score < 80)
# Real-world use: grade classification
def get_grade(score: int) -> str:
"""Returns a letter grade for the given score."""
if not 0 <= score <= 100:
raise ValueError(f"Score must be between 0 and 100: {score}")
if score >= 90:
return "A"
elif score >= 80:
return "B"
elif score >= 70:
return "C"
elif score >= 60:
return "D"
else:
return "F"
for s in [95, 85, 75, 65, 55]:
print(f"{s}: {get_grade(s)}")
3. Logical Operators
# and — True when both are True
print(True and True) # True
print(True and False) # False
print(False and True) # False
# or — True if at least one is True
print(True or False) # True
print(False or False) # False
# not — negation
print(not True) # False
print(not False) # True
print(not 0) # True
print(not "") # True
Short-Circuit Evaluation
Python does not evaluate the remaining expression once the result is determined.
# and: if the first operand is False, the second is not evaluated
def expensive_check():
print("Expensive operation running!")
return True
result = False and expensive_check() # "Expensive operation running!" is NOT printed
print(result) # False
# or: if the first operand is True, the second is not evaluated
result2 = True or expensive_check() # "Expensive operation running!" is NOT printed
print(result2) # True
# Safe access using short-circuit evaluation
user = {"name": "Alice", "profile": {"age": 30}}
# Safely access a dictionary key that may not exist
profile = user.get("profile")
age = profile.get("age") if profile is not None else None
print(age) # 30
Return Values of Logical Operators
Python's logical operators return the actual value, not just True/False.
# and — returns the first operand if falsy, otherwise returns the second
print(0 and "hello") # 0 (first is falsy)
print("hello" and "world") # 'world' (first is truthy)
# or — returns the first operand if truthy, otherwise returns the second
print(0 or "default") # 'default' (first is falsy)
print("value" or "default") # 'value' (first is truthy)
# Practical default value pattern
config_value = None
name = config_value or "default name"
print(name) # default name
# Watch out! 0, False, "" are also falsy
count = 0
result = count or 10 # Problem if 0 is a meaningful value!
print(result) # 10 (unintended result)
# Safer approach: check with is None
result = count if count is not None else 10
print(result) # 0 (correct result)
4. Assignment Operators
x = 10
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 (float!)
x //= 2 # x = x // 2 → 3.0
x **= 2 # x = x ** 2 → 9.0
x %= 4 # x = x % 4 → 1.0
# Works with strings too
text = "Hello"
text += ", World!"
print(text) # Hello, World!
# Works with lists too
items = [1, 2, 3]
items += [4, 5] # same as extend
print(items) # [1, 2, 3, 4, 5]
5. is vs == — Identity vs Equality
# == — compares values (equality)
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True — same value
# is — compares object identity (same memory address)
print(a is b) # False — different objects
# is is True when both variables point to the same object
c = a # c points to the same object as a
print(a is c) # True
# Use id() to view memory addresses
print(id(a), id(b), id(c))
# e.g.: 140234567890 140234567920 140234567890
# a and c share the same address, b has a different address
Small Integer Caching Caveat
# Python caches integers in the range -5 to 256
a = 100
b = 100
print(a is b) # True (cached objects are shared)
# 257 and above are not cached
x = 1000
y = 1000
print(x is y) # False (may vary depending on interpreter implementation)
print(x == y) # True (values are equal)
# Conclusion: always use == for integer comparison, use is only for None/True/False
print(None is None) # True (recommended)
print(True is True) # True
6. Membership Operators — in, not in
fruits = ["apple", "banana", "cherry"]
print("apple" in fruits) # True
print("grape" in fruits) # False
print("grape" not in fruits) # True
# Works with strings too
text = "Hello, Python!"
print("Python" in text) # True
print("Java" not in text) # True
# In dictionaries, searches keys
config = {"host": "localhost", "port": 5432}
print("host" in config) # True (key search)
print("localhost" in config) # False (does NOT search values!)
print("localhost" in config.values()) # True (value search)
7. Practical Examples
Even/Odd Classification
def classify_number(n: int) -> str:
if n % 2 == 0:
return f"{n} is even."
else:
return f"{n} is odd."
for i in range(1, 8):
print(classify_number(i))
Grade Calculation (Production Version)
def calculate_grade(score: int) -> dict[str, str]:
"""Accepts a score and returns grade, pass/fail status, and a message."""
if not 0 <= score <= 100:
raise ValueError(f"Invalid score: {score}")
passed = score >= 60
grade = (
"A" if score >= 90
else "B" if score >= 80
else "C" if score >= 70
else "D" if score >= 60
else "F"
)
message = "Pass" if passed else "Fail"
return {"grade": grade, "passed": str(passed), "message": message}
print(calculate_grade(95)) # {'grade': 'A', 'passed': 'True', 'message': 'Pass'}
print(calculate_grade(45)) # {'grade': 'F', 'passed': 'False', 'message': 'Fail'}
Leap Year Check
def is_leap_year(year: int) -> bool:
"""Returns whether the year is a leap year.
Leap year conditions:
1. Divisible by 4, AND
2. Either not divisible by 100, OR
3. Divisible by 400
"""
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
test_years = [1900, 2000, 2024, 2100, 2400]
for y in test_years:
print(f"{y}: {'Leap year' if is_leap_year(y) else 'Common year'}")
# Output:
# 1900: Common year (divisible by 100 but not 400)
# 2000: Leap year (divisible by 400)
# 2024: Leap year (divisible by 4, not divisible by 100)
# 2100: Common year (divisible by 100 but not 400)
# 2400: Leap year (divisible by 400)
Pro Tips: Safe Default Value Patterns Using
and/or Short-Circuit Evaluation# Pattern 1: Set default with or
# (Be careful when falsy values — 0, [], "" etc. — are meaningful)
def get_config(key: str, default: str) -> str:
env_value = None # Simulating a missing environment variable
return env_value or default
print(get_config("DB_HOST", "localhost")) # localhost
# Pattern 2: Safe chained access with and
user = {"profile": {"address": {"city": "Seoul"}}}
# Unsafe:
# city = user["profile"]["address"]["city"] # Risk of KeyError
# Safe chained access using and
profile = user.get("profile")
address = profile and profile.get("address")
city = address and address.get("city")
print(city) # Seoul
# Python 3.10+ — more concise with the walrus operator (detailed in Ch 3.4)
if (profile := user.get("profile")) and (addr := profile.get("address")):
city = addr.get("city", "Unknown")
print(f"City: {city}")
# Pattern 3: Conditional function call
import os
debug_mode = os.environ.get("DEBUG", "false").lower() == "true"
debug_mode and print("Debug mode enabled") # Runs only when debug_mode is True
You have learned all the arithmetic, comparison, and logical operators. The next chapter covers bitwise operators and special operators.