Operators form the computational engine of software. In this unit, you will dissect how Python evaluates expressions, discover how augmented assignments mutate collections in-place versus constructing new objects, leverage chained relational checks, harness short-circuit logical guards, manipulate raw binary bits with bitwise masks, and internalize operator precedence to write bug-free code.
Master floor division truncation nuances, modulus clock arithmetic, in-place mutability vs reassignment, and the modern Walrus operator (:=).
Harness Pythonic chained comparisons, lexicographical string evaluations, and non-boolean returning short-circuit logical operators (and, or, not).
Manipulate binary integers with AND, OR, XOR, NOT, and Bit Shifts to construct low-overhead Permission Bitmasks and flags.
Distinguish structural equality (==) from singleton identity (is), leverage O(1) membership checks, and resolve evaluation order conflicts.
Arithmetic operators perform mathematical calculations between scalar numeric values. While basic addition and subtraction are intuitive, Python introduces specific mathematical semantics for division and powers:
/) vs. Floor Division (//)/): Always returns a float, even if the division divides evenly (e.g., 10 / 2 -> 5.0).//): Divides and rounds down to the nearest mathematical integer towards negative infinity.# True Division yields float; Floor Division rounds towards -infinity
print(7 / 2, 6 / 2) # 3.5, 3.0 (True division always yields float)
print(7 // 2) # 3 (Positive truncation)
print(-7 // 2) # -4 (Mathematical floor: -3.5 rounds down to -4!)
-7 / 2 == -3). Python adheres strictly to Number Theory: n = d * (n // d) + (n % d). Therefore, -7 // 2 == -4, and -7 % 2 == 1!
In Python, float numbers are represented as IEEE 754 double-precision binary fractions. Certain decimal fractions cannot be represented exactly in binary (base-2), resulting in small precision artifacts:
# Binary floating-point representation dilemma & solutions
print(0.1 + 0.2 == 0.3) # False! (Raw float is 0.30000000000000004)
import math
print(math.isclose(0.1 + 0.2, 0.3)) # True (Scientific tolerance)
from decimal import Decimal
price = Decimal("0.10") + Decimal("0.20")
print(price == Decimal("0.30")) # True (Exact base-10 fintech precision)
float types for financial ledgers or crypto balances. Rounding errors accumulate across millions of calculations. Always configure exact precision via the standard decimal.Decimal library.
| Operator | Name | Numeric Syntax | Sequence Overloading | Dunder Method Equivalent |
|---|---|---|---|---|
+ |
Addition | 5 + 2 -> 7 |
"A" + "B" -> "AB" (Concat) |
__add__(self, other) |
- |
Subtraction | 5 - 2 -> 3 |
Unsupported for sequences | __sub__(self, other) |
* |
Multiplication | 5 * 2 -> 10 |
"A" * 3 -> "AAA" (Repeat) |
__mul__(self, other) |
/ |
True Division | 5 / 2 -> 2.5 |
Unsupported | __truediv__(self, other) |
// |
Floor Division | 5 // 2 -> 2 |
Unsupported | __floordiv__(self, other) |
% |
Modulus | 5 % 2 -> 1 |
"Hi %s" % "Dev" (Legacy fmt) |
__mod__(self, other) |
** |
Exponentiation | 2 ** 3 -> 8 |
Unsupported | __pow__(self, other) |
%) in Cyclical Systems & divmod()The modulus operator computes the mathematical remainder. In production systems, it is heavily utilized for circular queues, clock arithmetic, and round-robin load balancers:
servers = ["server-us-east", "server-eu-west", "server-ap-south"]
# Distribute 4 incoming requests across 3 servers cyclically
for req_id in range(4):
assigned = servers[req_id % len(servers)]
print(f"Req #{req_id} -> {assigned}")
# High-performance built-in divmod(x, y) returns (quotient, remainder) in 1 C-call
quotient, remainder = divmod(125, 60)
print(f"125s = {quotient}m and {remainder}s") # 2m and 5s
[[0] * 3] * 3
When multiplying lists containing mutable objects, Python duplicates the references, not the objects! Modifying grid[0][0] = 1 will simultaneously modify row 1 and row 2. Always construct multi-dimensional matrices using list comprehensions: [[0 for _ in range(3)] for _ in range(3)].
Assignment operators bind variables to objects. Beyond basic assignment (=), Python provides augmented assignment operators that combine arithmetic/bitwise operations with assignment:
| Operator | Example | Equivalent Long-Form | Underlying Method Hook |
|---|---|---|---|
+= |
x += 5 |
x = x + 5 |
__iadd__(self, 5) |
-= |
x -= 3 |
x = x - 3 |
__isub__(self, 3) |
*= |
x *= 2 |
x = x * 2 |
__imul__(self, 2) |
/= |
x /= 4 |
x = x / 4 |
__itruediv__(self, 4) |
//= |
x //= 2 |
x = x // 2 |
__ifloordiv__(self, 2) |
%= |
x %= 10 |
x = x % 10 |
__imod__(self, 10) |
**= |
x **= 3 |
x = x ** 3 |
__ipow__(self, 3) |
&= |
x &= 0b11 |
x = x & 0b11 |
__iand__(self, 0b11) |
+ vs. +=# Case 1: Standard addition creates a BRAND NEW list in memory!
list_a = [1, 2]
old_id = id(list_a)
list_a = list_a + [3]
print(id(list_a) == old_id) # False (New object allocated in heap!)
# Case 2: Augmented += modifies the list IN-PLACE via __iadd__!
list_b = [1, 2]
old_id = id(list_b)
list_b += [3]
print(id(list_b) == old_id) # True! (Same memory address, zero reallocation)
t = ([1, 2], 3); t[0] += [4].
TypeError: 'tuple' object does not support item assignment, YET the list inside the tuple is still mutated to [1, 2, 4]!
+= mutates the list in-place first (successful), and then attempts to assign the result back to t[0], triggering the tuple immutability error.
Introduced in Python 3.8 (PEP 572), the Walrus Operator (:=, named for its resemblance to the eyes and tusks of a walrus) allows you to assign values to variables directly inside an expression.
# Read terminal commands until user types 'exit'
while (command := input("DevAscent-CLI> ").strip().lower()) != "exit":
print(f"Dispatching task: {command}")
print("Session terminated gracefully.")
import re
auth_header = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"
# Extract and bind regex match object in a single conditional line
if (match := re.match(r"^Bearer\s+([A-Za-z0-9\-_\.]+)", auth_header)):
token = match.group(1)
print(f"JWT Token Verified: {token[:16]}...")
results = [y for x in data if (y := expensive_calc(x)) > 100]. This evaluates expensive_calc(x) exactly once per item, cutting compute time in half!
Comparison operators evaluate relationships between values and strictly return a boolean (True or False).
| Operator | Description | Code Example | Result |
|---|---|---|---|
== |
Equal to (checks value equality) | 10 == 10.0 |
True |
!= |
Not equal to | "cat" != "dog" |
True |
> |
Strictly greater than | 15 > 20 |
False |
< |
Strictly less than | 15 < 20 |
True |
>= |
Greater than or equal to | 70 >= 70 |
True |
<= |
Less than or equal to | 42 <= 50 |
True |
When strings are compared with < or >, Python compares them character-by-character based on their Unicode ordinal values (ASCII codes) via ord():
# Uppercase 'A' (65) is less than lowercase 'a' (97)!
print("Apple" < "apple") # True (ord('A')=65 < ord('a')=97)
# Alphabetical sorting order
print("FastAPI" < "Flask") # True ('a' comes before 'l')
# Custom object comparison requires implementing __eq__ or __lt__
class Student:
def __init__(self, score): self.score = score
def __eq__(self, other): return self.score == other.score
s1, s2 = Student(95), Student(95)
print(s1 == s2) # True (Dunder __eq__ intercepted comparison)
# The IEEE 754 NaN anomaly: NaN is NEVER equal to anything, including itself!
nan_val = float("nan")
print(nan_val == nan_val) # False! Always verify via math.isnan()
float('nan') in a set or use it in an algorithm, val == float('nan') will always return False. Always invoke import math; math.isnan(val) when filtering invalid floating-point numbers in production data pipelines.
In languages like C, Java, or JavaScript, writing 18 <= age <= 65 fails because it evaluates (18 <= age) -> true, and then computes true <= 65 -> true, leading to catastrophic logic bugs. In those languages, you must write age >= 18 && age <= 65.
Python natively supports Chained Comparisons modeled directly after mathematical inequalities:
score = 85
# Highly readable, mathematically elegant
if 70 <= score <= 100:
print("Candidate eligible for Distinction Honors")
# Multi-term equality chaining
a = b = c = 10
print(a == b == c) # True
# Chaining with directional transitions
print(5 < 10 > 2) # True (Equivalent to: (5 < 10) and (10 > 2))
low < expensive_function() < high, Python guarantees that expensive_function() is called only once, avoiding redundant CPU overhead and preventing bugs caused by state mutations!
False == False in [False]. Developers often expect (False == False) in [False] -> True in [False] -> False. However, Python parses this as a chain: (False == False) and (False in [False]) -> True and True -> True! Always use explicit parentheses.
Python provides three logical operators: and, or, and not.
and and or Return Operands!
Unlike languages that coerce logical results strictly to true/false, Python's and and or operators return the exact value of the operand that determined the outcome:
X and Y: Evaluates X. If X is Falsy, returns X immediately. Otherwise, returns Y.X or Y: Evaluates X. If X is Truthy, returns X immediately. Otherwise, returns Y.# 'or' returns first Truthy operand
print("DevAscent" or "Default") # 'DevAscent'
print("" or "Fallback API") # 'Fallback API'
print([] or [1, 2, 3]) # [1, 2, 3]
# 'and' returns first Falsy operand, or last Truthy operand
print(0 and 100) # 0
print(42 and "Active") # 'Active'
Short-circuit evaluation means Python halts evaluation the moment the final truth value is guaranteed. This allows engineers to safely guard against dangerous operations (like dividing by zero or querying None objects):
user_profile = None
# Safe guard: user_profile is None, so Python short-circuits and never executes
# user_profile['email'], preventing a fatal TypeError: 'NoneType' object is not subscriptable!
if user_profile is not None and user_profile["email"]:
print("Email verified.")
else:
print("No active profile detected.")
user_val or default
Using port = input_port or 8080 is common, but if input_port = 0, Python treats 0 as Falsy and incorrectly overrides it with 8080! In production, use: port = input_port if input_port is not None else 8080.
Bitwise operators operate directly on the individual binary bits (0s and 1s) of integer operands. They are critical in systems programming, network protocol decoding, cryptography, and performance-critical gaming engines.
| Operator | Name | Binary Logic Rule | Example (a=5 (0101), b=3 (0011)) |
|---|---|---|---|
& |
Bitwise AND | 1 only if both bits are 1 | 5 & 3 -> 1 (binary 0001) |
| |
Bitwise OR | 1 if either bit is 1 | 5 | 3 -> 7 (binary 0111) |
^ |
Bitwise XOR | 1 if bits are different | 5 ^ 3 -> 6 (binary 0110) |
~ |
Bitwise NOT | Inverts all bits (Two's complement) | ~5 -> -6 (Rule: -(x + 1)) |
<< |
Bitwise Left Shift | Shifts bits left (multiplies by 2n) | 5 << 1 -> 10 (01010) |
>> |
Bitwise Right Shift | Shifts bits right (divides by 2n) | 5 >> 1 -> 2 (0010) |
~x) operates as if integers have an infinite sequence of leading sign bits. The formula is strictly: ~x = -(x + 1). For example, ~0 == -1, and ~(-1) == 0.
Storing permissions as strings or boolean columns in a database table consumes substantial storage. Professional engineering systems (like Linux filesystem permissions or Discord role systems) compress all permissions into a single integer bitmask:
# Define permission powers of 2 using Left-Shift
READ = 1 << 0 # 0001 (1)
WRITE = 1 << 1 # 0010 (2)
EXECUTE = 1 << 2 # 0100 (4)
ADMIN = 1 << 3 # 1000 (8)
# 1. Granting permissions using Bitwise OR (|)
student_role = READ | EXECUTE # 0101 (5)
instructor_role = READ | WRITE | EXECUTE # 0111 (7)
# 2. Checking permissions using Bitwise AND (&)
def has_permission(role, perm):
return (role & perm) == perm
print(f"Student can write? {has_permission(student_role, WRITE)}") # False
print(f"Instructor can write? {has_permission(instructor_role, WRITE)}") # True
# 3. Revoking permissions using Bitwise AND with NOT (& ~)
instructor_role &= ~WRITE # Strips the WRITE bit cleanly
print(f"Instructor write after revocation: {has_permission(instructor_role, WRITE)}")
# 4. Toggling permissions using Bitwise XOR (^)
student_role ^= EXECUTE # Flips EXECUTE from active to inactive
chmod 755) is fundamentally an integer bitmask! 7 represents READ (4) + WRITE (2) + EXECUTE (1). Bitmasks provide maximum computational speed because bitwise instructions execute in a single CPU clock cycle.
Membership operators validate whether a specified value exists within a sequence or collection (such as a string, list, tuple, set, or dictionary).
# 1. String Substring Match
announcement = "Cohort #14 enrollments are now live on devascentacademy.me"
print("Cohort #14" in announcement) # True
# 2. List Membership
active_courses = ["Python", "AI", "Web Dev"]
print("Rust" not in active_courses) # True
# 3. Dictionary Membership checks KEYS by default!
student = {"name": "Raja Mishra", "role": "STUDENT"}
print("name" in student) # True (Checks key)
print("Raja Mishra" in student) # False! (Does not check values by default)
print("Raja Mishra" in student.values()) # True (Explicitly search values)
| Collection Type | Membership Test Syntax | Time Complexity (Big-O) | Internal Mechanism |
|---|---|---|---|
| List / Tuple | target in my_list |
O(n) Linear Scan | Iterates sequentially from index 0 until item is found. |
| Set / Dictionary | target in my_set |
O(1) Constant Time | Hashes target directly to find memory bucket in 1 step! |
set() turns an 8-second bottleneck into a 2-millisecond operation!
__contains__ Protocol
When Python encounters item in container, it first invokes container.__contains__(item). If that method is undefined, it automatically falls back to iterating via __iter__(), and finally falls back to old-style index iteration via __getitem__().
Identity operators evaluate whether two distinct variables point to the exact same memory address in heap RAM, whereas equality operators (==) evaluate whether two objects hold equivalent content:
is: Singletons Only!# ALWAYS use 'is' when checking against None (PEP 8 requirement)
api_response = None
if api_response is None:
print("Waiting for backend telemetry...")
# NEVER use 'is' for numerical or string equality!
a = 1000
b = 1000
# print(a is b) <-- Unreliable! Depends on compiler memory caching optimization!
print(a == b) # Guaranteed True (Accurate value comparison)
x = 250; y = 250; x is y, it returns True because CPython caches integers from -5 to 256. But if you test x = 257; y = 257; x is y, it may return False! Never rely on is for numbers. Always use ==.
When an expression contains multiple operators, Python relies on Operator Precedence to determine which operation executes first. Operators at the top of the hierarchy bind tighter than those below:
| Precedence | Operators | Description | Associativity |
|---|---|---|---|
| 1 (Highest) | (), [], {} |
Parentheses, list indexing, calls | Left-to-Right |
| 2 | ** |
Exponentiation | Right-to-Left |
| 3 | +x, -x, ~x |
Unary positive, negative, bitwise NOT | Right-to-Left |
| 4 | *, /, //, % |
Multiplication, division, floor div, mod | Left-to-Right |
| 5 | +, - |
Addition, subtraction | Left-to-Right |
| 6 | <<, >> |
Bitwise shift operators | Left-to-Right |
| 7 | & |
Bitwise AND | Left-to-Right |
| 8 | ^ |
Bitwise XOR | Left-to-Right |
| 9 | | |
Bitwise OR | Left-to-Right |
| 10 | ==, !=, <, <=, >, >=, is, in |
Comparisons, identity, membership | Chaining |
| 11 | not |
Logical NOT | Right-to-Left |
| 12 | and |
Logical AND | Left-to-Right |
| 13 | or |
Logical OR | Left-to-Right |
| 14 | if - else |
Ternary conditional expression | Right-to-Left |
| 15 (Lowest) | :=, =, +=, etc. |
Assignment expressions & statements | Right-to-Left |
Subtle bugs emerge when developers assume mathematical or logical evaluation orders that diverge from Python's parser:
# Trap 1: Exponentiation evaluates from RIGHT to LEFT:
print(2 ** 3 ** 2) # 512! (Computed as: 2 ** (3 ** 2) = 2 ** 9)
# NOT: (2 ** 3) ** 2 = 8 ** 2 = 64
# Trap 2: Unary Minus vs Exponentiation
print(-2 ** 2) # -4! (Computed as: -(2 ** 2))
# Parentheses mandatory for negative base: (-2) ** 2 = 4
# Trap 3: Bitwise vs Comparison precedence ('==' binds tighter than '&'!)
flags = 0b0101
MASK = 0b0001
# if flags & MASK == 1: <-- Parsed as: flags & (MASK == 1) -> flags & True -> 1!
if (flags & MASK) == 1: # Correct: Explicit parentheses eliminate ambiguity
print("Mask bit confirmed.")
# Trap 4: Logical 'and' binds tighter than 'or'
print(True or False and False) # True! Parsed as: True or (False and False)
if (is_admin or has_license) and (active_tickets > 0):
Always wrap compound conditions with parentheses to prevent accidental bitwise or logical precedence hijacking.
Never compare floats directly with ==. Employ math.isclose() or the decimal.Decimal engine.
This capstone synthesizes all core concepts from Unit 3 — arithmetic operations, bitwise permission masks, membership verification, and chained comparison inequalities — into a mission-critical IoT packet validation pipeline.
| Telemetry Field | Type | Validation Rule | Python Operator Leveraged |
|---|---|---|---|
region |
str |
Must exist within approved global datacenter regions | Membership (in set) |
temperature_c |
float |
Operational range between -40.0°C and +85.0°C | Chained Comparison (-40.0 <= temp <= 85.0) |
security_flags |
int |
Must contain both ENCRYPTED and SIGNED bitwise flags | Bitwise AND / OR ((flags & mask) == mask) |
payload_size_kb |
int |
Packet size strictly non-negative and under 1024 KB | Relational Comparison (0 < size <= 1024) |
| Flag Constant | Bit Shift Value | Decimal | Operational Meaning |
|---|---|---|---|
FLAG_ENCRYPTED |
1 << 0 |
1 | AES-256 payload ciphertext confirmed |
FLAG_SIGNED |
1 << 1 |
2 | Ed25519 digital signature verified |
FLAG_COMPRESSED |
1 << 2 |
4 | Zstandard byte stream compression active |
FLAG_DIAGNOSTIC |
1 << 3 |
8 | Debug telemetry channel enabled |
The complete, executable Python module executing production telemetry packet security audits:
"""DevAscent Academy — Unit 3 Capstone Module"""
FLAG_ENCRYPTED = 1 << 0 # 1
FLAG_SIGNED = 1 << 1 # 2
FLAG_COMPRESSED = 1 << 2 # 4
APPROVED_REGIONS = {"ap-south-1", "us-east-1", "eu-central-1"}
def validate_telemetry_packet(packet: dict) -> bool:
# 1. Membership checking (O(1) hash lookup)
if packet.get("region") not in APPROVED_REGIONS:
return False
# 2. Chained comparison for thermal limits and payload size
temp_c = packet.get("temperature_c", 0.0)
size_kb = packet.get("payload_size_kb", 0)
if not (-40.0 <= temp_c <= 85.0) or not (0 < size_kb <= 1024):
return False
# 3. Bitwise verification: Must be both ENCRYPTED and SIGNED
flags = packet.get("security_flags", 0)
required_flags = FLAG_ENCRYPTED | FLAG_SIGNED
return (flags & required_flags) == required_flags
# Test Execution
pkt1 = {"region": "ap-south-1", "temperature_c": 32.4, "payload_size_kb": 256, "security_flags": 7}
pkt2 = {"region": "ap-south-1", "temperature_c": 105.0, "payload_size_kb": 64, "security_flags": 7}
print(f"Packet 1 (Standard): {validate_telemetry_packet(pkt1)}")
print(f"Packet 2 (Overheat): {validate_telemetry_packet(pkt2)}")