Module 2.2

Python Operators

Operators are the building blocks of all calculations and comparisons in Python. From simple math to complex logic, mastering operators is essential for every Python programmer!

35 min read
Beginner
Hands-on Examples
What You'll Learn
  • Arithmetic operators (+, -, *, /)
  • Comparison operators (==, !=, <, >)
  • Logical operators (and, or, not)
  • Assignment operators (=, +=, -=)
  • Identity & membership operators
  • Operator precedence rules
Contents
01

What Are Operators?

Operators are special symbols that perform operations on values and variables. They're the verbs of programming - they make things happen!

Real-World Analogy

Think of operators like the buttons on a calculator. Each button performs a specific action:

  • + adds numbers together
  • - subtracts one from another
  • = shows you the result
  • < compares which is smaller
In Programming

Operators work on operands (the values being operated on):

  • 5 + 3 → operands: 5 and 3, operator: +
  • x * y → operands: x and y, operator: *
  • not True → operand: True, operator: not

Types of Operators

Python has seven types of operators, each designed for different purposes. Think of them as different toolboxes - you pick the right tool for the job at hand.

Category Purpose Examples
Arithmetic Mathematical calculations +, -, *, /, //, %, **
Comparison Compare values ==, !=, <, >, <=, >=
Logical Combine conditions and, or, not
Assignment Assign values =, +=, -=, *=, /=
Identity Check object identity is, is not
Membership Check membership in, not in
Bitwise Binary operations &, |, ^, ~, <<, >>
02

Arithmetic Operators

Arithmetic operators perform mathematical calculations. These are the operators you'll use most often - from simple addition to complex mathematical expressions.

Operator Category

Arithmetic Operators

Arithmetic operators perform mathematical operations on numeric values. Just like a calculator, these operators let you add, subtract, multiply, divide, and more. Python includes some special operators you might not find on a basic calculator, like floor division and exponentiation.

The operators: + (add), - (subtract), * (multiply), / (divide), // (floor divide), % (modulo), ** (power)

Basic Operations

The four basic arithmetic operations work exactly as you'd expect from math class. Addition combines values, subtraction finds the difference, multiplication scales values, and division splits them.

# Addition (+) - combines values
print(10 + 5)      # 15
print(3.5 + 2.1)   # 5.6

The + operator adds numbers together, just like on a calculator. It works with both integers (whole numbers like 10) and floats (decimal numbers like 3.5). When you add an integer and a float together, Python automatically gives you a float result.

# Subtraction (-) - finds the difference
print(10 - 5)      # 5
print(3.5 - 2.1)   # 1.4 (approximately)

The - operator subtracts the second number from the first. Notice that 3.5 - 2.1 gives approximately 1.4 - due to how computers store decimal numbers, you might see tiny rounding differences (like 1.3999999999999999).

# Multiplication (*) - scales values
print(10 * 5)      # 50
print(3.5 * 2)     # 7.0

The * operator multiplies numbers. When you multiply an integer by a float (like 3.5 * 2), Python returns a float (7.0) to preserve potential decimal places.

# Division (/) - always returns a float
print(10 / 5)      # 2.0 (not 2!)
print(7 / 2)       # 3.5

The / operator divides numbers. Important: In Python 3, division always returns a float, even when dividing evenly! That's why 10 / 5 gives 2.0 instead of 2. This is a common gotcha for beginners.

Important: Regular division (/) always returns a float in Python 3, even when dividing evenly. 10 / 5 gives 2.0, not 2.

Special Arithmetic Operators

Python includes three operators that are incredibly useful but often confuse beginners: floor division, modulo, and exponentiation. Let's break each one down clearly.

# Floor Division (//) - divides and rounds DOWN to nearest integer
print(7 // 2)      # 3 (not 3.5!)
print(10 // 3)     # 3
print(-7 // 2)     # -4 (rounds toward negative infinity!)

Floor division (//) divides and then rounds DOWN to the nearest whole number. Think of it as "how many whole times does this fit?" - 7 divided by 2 is 3.5, but floor division gives you just 3. Be careful with negative numbers: -7 // 2 rounds toward negative infinity, giving -4 (not -3)!

# Modulo (%) - returns the REMAINDER after division
print(7 % 2)       # 1 (7 divided by 2 = 3 remainder 1)
print(10 % 3)      # 1 (10 divided by 3 = 3 remainder 1)
print(8 % 4)       # 0 (divides evenly, no remainder)

The modulo operator (%) gives you the remainder after division. When you divide 7 by 2, you get 3 with 1 left over - that leftover 1 is what modulo returns. If a number divides evenly (like 8 % 4), the remainder is 0. This is super useful for checking if numbers are even/odd!

# Exponentiation (**) - raises to a power
print(2 ** 3)      # 8 (2 × 2 × 2)
print(5 ** 2)      # 25 (5 × 5)
print(16 ** 0.5)   # 4.0 (square root!)

The exponentiation operator (**) raises a number to a power. 2 ** 3 means "2 to the power of 3" which is 2 × 2 × 2 = 8. Here's a cool trick: using a fractional power like 0.5 calculates the square root! So 16 ** 0.5 gives you 4.0.

Pro Tip: Modulo (%) is incredibly useful! Use it to check if a number is even (n % 2 == 0), cycle through values, or extract digits from numbers.

Practical Examples

Let's see how these operators solve real-world problems. From checking even numbers to calculating compound interest, arithmetic operators are everywhere.

# Check if a number is even or odd
number = 17
if number % 2 == 0:
    print(f"{number} is even")
else:
    print(f"{number} is odd")  # Output: 17 is odd

This is the classic even/odd check using modulo. If a number divided by 2 has no remainder (% 2 == 0), it's even. Otherwise, it's odd. Since 17 % 2 = 1 (not 0), we know 17 is odd. You'll use this pattern constantly in programming!

# Calculate compound interest: A = P(1 + r)^t
principal = 1000
rate = 0.05
time = 3
amount = principal * (1 + rate) ** time
print(f"After {time} years: ${amount:.2f}")  # After 3 years: $1157.63

Here we use exponentiation (**) to calculate compound interest. The formula A = P(1 + r)^t calculates how much $1000 grows at 5% interest over 3 years. The ** operator handles the "to the power of" part. The :.2f in the f-string formats the result to 2 decimal places.

# Convert seconds to hours, minutes, seconds
total_seconds = 3725
hours = total_seconds // 3600
minutes = (total_seconds % 3600) // 60
seconds = total_seconds % 60
print(f"{hours}h {minutes}m {seconds}s")  # 1h 2m 5s

This example shows floor division and modulo working together. First, we get whole hours by floor-dividing by 3600 (seconds in an hour). Then we use modulo to get the remaining seconds, and floor-divide by 60 to get minutes. Finally, modulo 60 gives leftover seconds. This pattern is common for time conversions!

Operators with Strings and Lists

Here's something cool: the + and * operators also work with strings and lists! They perform concatenation (joining) and repetition.

# String concatenation with +
first_name = "John"
last_name = "Doe"
full_name = first_name + " " + last_name
print(full_name)  # "John Doe"

The + operator works on strings too! When used with strings, it joins (concatenates) them together. Here we combine "John", a space " ", and "Doe" to create "John Doe". This is called string concatenation.

# String repetition with *
separator = "-" * 20
print(separator)  # "--------------------"

The * operator repeats a string multiple times. "-" * 20 creates a string of 20 dashes. This is handy for creating visual separators, padding, or repeated patterns in your output.

# List concatenation with +
list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined = list1 + list2
print(combined)  # [1, 2, 3, 4, 5, 6]

Lists work the same way! The + operator joins two lists into one new list containing all elements from both. Note: this creates a new list - it doesn't modify the original lists.

# List repetition with *
pattern = [0, 1] * 4
print(pattern)  # [0, 1, 0, 1, 0, 1, 0, 1]

Multiplying a list repeats its contents. [0, 1] * 4 creates a new list with the pattern [0, 1] repeated 4 times. This is useful for initializing lists with repeated values or creating patterns.

Practice Questions: Arithmetic Operators

Test your understanding with these hands-on exercises.

Task: Given width = 7 and height = 4, calculate and print the area and perimeter of a rectangle.

Show Solution
width = 7
height = 4

area = width * height
perimeter = 2 * (width + height)

print(f"Area: {area}")        # Area: 28
print(f"Perimeter: {perimeter}")  # Perimeter: 22

Task: Given number = 365, extract and print the hundreds, tens, and units digits using only arithmetic operators (no strings!).

Hint: Use floor division (//) and modulo (%)

Show Solution
number = 365

hundreds = number // 100        # 3
tens = (number % 100) // 10     # 6
units = number % 10             # 5

print(f"Hundreds: {hundreds}")  # Hundreds: 3
print(f"Tens: {tens}")          # Tens: 6
print(f"Units: {units}")        # Units: 5

Task: Calculate the monthly payment for a loan using the formula:

M = P * (r * (1 + r)^n) / ((1 + r)^n - 1)

Where: P = $10000 (principal), r = 0.005 (monthly rate = 6%/12), n = 36 (months)

Show Solution
principal = 10000
monthly_rate = 0.06 / 12  # 6% annual = 0.5% monthly
months = 36

# Calculate using the formula
numerator = monthly_rate * (1 + monthly_rate) ** months
denominator = (1 + monthly_rate) ** months - 1
monthly_payment = principal * (numerator / denominator)

print(f"Monthly Payment: ${monthly_payment:.2f}")  # $304.22
03

Comparison Operators

Comparison operators compare two values and return a boolean (True or False). They're essential for making decisions in your code.

Operator Category

Comparison Operators

Comparison operators evaluate the relationship between two values. They ask questions like "are these equal?" or "is this greater than that?" and always answer with True or False. These operators are the foundation of all decision-making in programming.

The operators: == (equal), != (not equal), < (less than), > (greater than), <= (less or equal), >= (greater or equal)

Equality Operators

The equality operators check if two values are the same or different. Be careful: use double equals (==) for comparison, not single equals (=) which is for assignment!

# Equal to (==)
print(5 == 5)        # True
print(5 == 6)        # False
print("hello" == "hello")  # True
print("hello" == "Hello")  # False (case-sensitive!)

The double equals (==) checks if two values are the same. It returns True if they match, False if they don't. With strings, the comparison is case-sensitive - "hello" and "Hello" are considered different because 'h' and 'H' are different characters.

# Not equal to (!=)
print(5 != 6)        # True
print(5 != 5)        # False
print("cat" != "dog")  # True

The not-equal operator (!=) is the opposite of ==. It returns True when values are different, False when they're the same. Think of it as asking "are these two things NOT the same?"

Common Mistake: Using = instead of ==. Remember: = assigns a value, == compares values. x = 5 sets x to 5, while x == 5 checks if x equals 5.

Relational Operators

These operators compare the relative size of values - which is bigger, smaller, or if they're within a certain range.

# Less than (<)
print(5 < 10)    # True
print(10 < 5)    # False
print(5 < 5)     # False (not less, it's equal)

The less-than operator (<) checks if the left value is smaller than the right. Note that 5 < 5 is False because 5 is not less than itself - they're equal. If you want "less than OR equal," use <= instead.

# Greater than (>)
print(10 > 5)    # True
print(5 > 10)    # False

The greater-than operator (>) checks if the left value is larger than the right. It's the mirror image of less-than.

# Less than or equal to (<=)
print(5 <= 5)    # True (equal counts!)
print(5 <= 10)   # True

The <= operator returns True if the left value is less than OR equal to the right. So 5 <= 5 is True because they're equal - the "or equal" part makes it pass.

# Greater than or equal to (>=)
print(10 >= 10)  # True
print(10 >= 5)   # True

Similarly, >= returns True if the left value is greater than OR equal to the right. These "or equal" operators are commonly used for boundary checks like "is age at least 18?"

Chained Comparisons

Python allows you to chain comparisons together, which makes range checking elegant and readable. This is a feature that many other languages don't have!

# Instead of this:
age = 25
if age >= 18 and age <= 65:
    print("Working age")

# Python lets you write this:
if 18 <= age <= 65:
    print("Working age")  # Much cleaner!

Python has a special feature called chained comparisons. Instead of writing age >= 18 and age <= 65, you can write 18 <= age <= 65. It reads more like English: "18 is less than or equal to age, which is less than or equal to 65."

# More examples of chained comparisons
x = 5
print(1 < x < 10)      # True (x is between 1 and 10)
print(1 < x < 3)       # False (x is not between 1 and 3)
print(0 <= x <= 5)     # True (x is between 0 and 5, inclusive)

Chained comparisons are perfect for range checks. 1 < x < 10 checks if x is between 1 and 10 (exclusive). 0 <= x <= 5 includes the endpoints (inclusive). This is much cleaner than using and to combine two comparisons.

# Works with multiple values
a, b, c = 1, 2, 3
print(a < b < c)       # True (a < b AND b < c)

You can chain comparisons with variables too! a < b < c checks if the values are in ascending order. This is equivalent to a < b and b < c but reads more naturally.

Comparing Strings

Strings are compared character by character using their Unicode (ASCII) values. Uppercase letters come before lowercase letters, and comparison is case-sensitive.

# Strings are compared lexicographically (like dictionary order)
print("apple" < "banana")   # True ('a' comes before 'b')
print("apple" < "Apple")    # False (lowercase > uppercase in ASCII)

Strings are compared character by character, like how words are sorted in a dictionary. "apple" comes before "banana" because 'a' comes before 'b'. Here's a gotcha: lowercase letters have higher values than uppercase in ASCII/Unicode, so "apple" is actually considered greater than "Apple"!

# Character by character comparison
print("abc" < "abd")        # True ('c' < 'd')
print("abc" < "abcd")       # True (shorter string is "smaller")

Python compares strings character by character until it finds a difference. "abc" vs "abd" - the first two characters match, but 'c' < 'd', so "abc" is smaller. When one string is a prefix of another (like "abc" vs "abcd"), the shorter one is considered "smaller."

# Case-insensitive comparison
name1 = "John"
name2 = "john"
print(name1 == name2)                    # False
print(name1.lower() == name2.lower())    # True

Since string comparison is case-sensitive, "John" ≠ "john". To compare strings regardless of case, convert both to lowercase (or uppercase) first using .lower(). This is essential when comparing user input, usernames, or any text where case shouldn't matter.

Practice Questions: Comparison Operators

Test your understanding with these hands-on exercises.

Task: Write code to check if a score (score = 85) is a valid percentage (between 0 and 100 inclusive). Print "Valid" or "Invalid".

Show Solution
score = 85

if 0 <= score <= 100:
    print("Valid")    # Valid
else:
    print("Invalid")

Task: Write code that assigns a letter grade based on a score: A (90+), B (80-89), C (70-79), D (60-69), F (below 60). Test with score = 73.

Show Solution
score = 73

if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
elif score >= 70:
    grade = "C"
elif score >= 60:
    grade = "D"
else:
    grade = "F"

print(f"Score: {score}, Grade: {grade}")  # Score: 73, Grade: C

Task: Given three numbers (a=5, b=2, c=8), print them in ascending order using only comparison operators, not the sort() function.

Show Solution
a, b, c = 5, 2, 8

# Find min, mid, max using comparisons
if a <= b <= c:
    print(a, b, c)
elif a <= c <= b:
    print(a, c, b)
elif b <= a <= c:
    print(b, a, c)
elif b <= c <= a:
    print(b, c, a)
elif c <= a <= b:
    print(c, a, b)
else:
    print(c, b, a)
# Output: 2 5 8
04

Logical Operators

Logical operators combine multiple conditions into one expression. They're essential for making complex decisions in your code.

Operator Category

Logical Operators

Logical operators combine boolean values or expressions. Think of them as the words "and", "or", and "not" in English. They let you build complex conditions like "if the user is logged in AND is an admin" or "if the age is less than 13 OR greater than 65".

The operators: and (both must be True), or (at least one must be True), not (inverts True/False)

The and Operator

The and operator returns True only if BOTH conditions are true. If either condition is false, the entire expression is false.

# Both conditions must be True
print(True and True)    # True
print(True and False)   # False
print(False and True)   # False
print(False and False)  # False

The and operator is like a strict bouncer - BOTH conditions must be True to pass. Think of it as: "Do you have your ID AND are you on the guest list?" You need both to get in. If either condition is False, the whole expression is False.

# Practical example: checking multiple conditions
age = 25
has_license = True

if age >= 18 and has_license:
    print("You can drive!")  # This prints

In real code, and is used to check multiple requirements. Here, someone can drive only if they're 18 or older AND have a license. Both conditions must be true - being 25 years old alone isn't enough if has_license were False.

# Multiple conditions
temperature = 22
is_sunny = True
is_weekend = True

if temperature > 20 and is_sunny and is_weekend:
    print("Perfect day for a picnic!")  # This prints

You can chain multiple and conditions together. All of them must be True for the block to execute. Here we check temperature, weather, AND day of week - perfect for complex real-world decisions!

The or Operator

The or operator returns True if AT LEAST ONE condition is true. It only returns false when both conditions are false.

# At least one condition must be True
print(True or True)    # True
print(True or False)   # True
print(False or True)   # True
print(False or False)  # False

The or operator is more lenient - only ONE condition needs to be True. Think of it as: "Do you have cash OR a credit card?" Either one works. The only way to get False is if ALL conditions are False.

# Practical example: checking alternatives
day = "Saturday"

if day == "Saturday" or day == "Sunday":
    print("It's the weekend!")  # This prints

Here we check if it's a weekend day. Since day == "Saturday" is True, the whole expression is True, and we get our weekend message!

# Check if user is authorized
is_admin = False
is_owner = True

if is_admin or is_owner:
    print("Access granted!")  # This prints (owner is True)

This pattern is common for checking permissions. Even though is_admin is False, is_owner is True, so access is granted. The user only needs to satisfy ONE of the conditions.

The not Operator

The not operator inverts a boolean value - it turns True into False and vice versa.

# Inverts the boolean value
print(not True)   # False
print(not False)  # True

The not operator simply flips the boolean value - True becomes False, False becomes True. It's like a logical switch.

# Practical example
is_logged_in = False

if not is_logged_in:
    print("Please log in first!")  # This prints

not is extremely useful for checking the opposite condition. Instead of writing if is_logged_in == False, the Pythonic way is if not is_logged_in. It reads more naturally: "if NOT logged in, then..."

# Double negation
print(not not True)   # True (back to original)

Double negation cancels out - not not True is True again. While you rarely write this directly, understanding it helps when debugging complex boolean expressions.

# Useful for checking empty/non-empty
my_list = []
if not my_list:  # Empty list is "falsy"
    print("List is empty!")  # This prints

In Python, empty collections (lists, strings, etc.) are "falsy" - they evaluate to False in boolean contexts. So not my_list is True when the list is empty. This is a very common Python pattern for checking if something is empty!

Short-Circuit Evaluation

Python uses "short-circuit" evaluation - it stops evaluating as soon as the result is determined. This makes your code faster and can prevent errors!

# With 'and': if first is False, don't check second
x = 0
# This won't cause division by zero error!
if x != 0 and 10/x > 2:  
    print("Safe division")
# x != 0 is False, so Python doesn't evaluate 10/x

Python is smart and lazy (in a good way!). With and, if the first condition is False, Python already knows the whole expression will be False, so it doesn't bother checking the rest. This is called short-circuit evaluation. Here, 10/x would crash with division by zero, but Python never evaluates it because x != 0 is already False!

# With 'or': if first is True, don't check second
name = "John"
# This won't cause an error even if name could be None
if name or len(name) > 0:
    print("Name exists")

With or, if the first condition is True, Python knows the whole expression is True, so it skips the rest. Here, if name is truthy, Python won't even check len(name).

# Practical: default value pattern
user_name = ""
display_name = user_name or "Guest"
print(display_name)  # "Guest" (because "" is falsy)

Here's a clever trick: since empty strings are "falsy," user_name or "Guest" returns "Guest" when user_name is empty. If user_name had a value, it would return that instead. This is a common pattern for providing default values. Be careful though - this treats 0 and False as falsy too!

Pro Tip: Short-circuit evaluation lets you write x or default to provide default values. If x is falsy (empty, zero, None), default is used instead.

Practice Questions: Logical Operators

Test your understanding with these hands-on exercises.

Task: A movie is rated R (requires age 17+) OR parental consent. Write code to check if someone can watch: age = 15, has_parental_consent = True.

Show Solution
age = 15
has_parental_consent = True

can_watch = age >= 17 or has_parental_consent

if can_watch:
    print("Enjoy the movie!")  # This prints
else:
    print("Sorry, you cannot watch this movie.")

Task: A password is valid if it's at least 8 characters AND contains at least one digit. Check password = "secret123" and print whether it's valid.

Show Solution
password = "secret123"

is_long_enough = len(password) >= 8
has_digit = any(char.isdigit() for char in password)

is_valid = is_long_enough and has_digit

print(f"Password: {password}")
print(f"Long enough: {is_long_enough}")   # True
print(f"Has digit: {has_digit}")          # True
print(f"Valid: {is_valid}")               # True

Task: Write a function that safely divides two numbers. If the divisor is 0, return a default value of -1 instead of crashing. Use short-circuit evaluation.

Show Solution
def safe_divide(a, b, default=-1):
    # Short-circuit: if b is 0, return default without dividing
    return b != 0 and a / b or default

# Test cases
print(safe_divide(10, 2))   # 5.0
print(safe_divide(10, 0))   # -1 (no crash!)
print(safe_divide(15, 3))   # 5.0
05

Assignment Operators

Assignment operators assign values to variables. Beyond the simple =, Python offers compound operators that combine assignment with arithmetic.

Operator Category

Assignment Operators

Assignment operators store values in variables. The basic assignment operator (=) simply stores a value, while compound assignment operators like += combine an operation with assignment in a single step - making your code shorter and cleaner.

The operators: =, +=, -=, *=, /=, //=, %=, **=

Basic Assignment

The basic assignment operator (=) stores a value in a variable. Python also supports multiple assignment in a single line.

# Simple assignment
x = 10
name = "Python"
is_active = True

The basic assignment operator (=) stores a value in a variable. The variable name goes on the left, the value on the right. Think of it as putting a label on a box - the label is the variable name, and the box contents are the value.

# Multiple assignment (same value)
a = b = c = 0
print(a, b, c)  # 0 0 0

Python lets you assign the same value to multiple variables in one line. a = b = c = 0 sets all three variables to 0. This is handy when initializing several counters or flags at once.

# Multiple assignment (different values)
x, y, z = 1, 2, 3
print(x, y, z)  # 1 2 3

You can also assign different values to multiple variables simultaneously using tuple unpacking. x, y, z = 1, 2, 3 assigns 1 to x, 2 to y, and 3 to z. The number of variables must match the number of values!

# Swap values (no temp variable needed!)
a, b = 5, 10
a, b = b, a  # Swap!
print(a, b)  # 10 5

Here's a Python superpower: swapping variables in one line! In many languages, you'd need a temporary variable. In Python, a, b = b, a swaps them elegantly. Python evaluates the right side first, then assigns, so this works perfectly.

Compound Assignment Operators

Compound operators combine an arithmetic operation with assignment. Instead of writing x = x + 5, you can write x += 5. It's shorter, cleaner, and a common pattern in programming.

# Without compound operators (verbose)
counter = 0
counter = counter + 1  # Increment by 1
print(counter)  # 1

The traditional way to increment a variable is counter = counter + 1. It works, but it's repetitive - you have to write counter twice. Python has a better way!

# With compound operators (cleaner!)
counter = 0
counter += 1  # Same as counter = counter + 1
print(counter)  # 1

The compound operator += does the same thing in less code. counter += 1 means "add 1 to counter and store the result back in counter." It's shorter, cleaner, and the standard way programmers write this.

# All compound 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
x //= 2  # x = x // 2 → 3.0
x %= 2   # x = x % 2  → 1.0
x **= 3  # x = x ** 3 → 1.0

Every arithmetic operator has a compound version! -= subtracts, *= multiplies, /= divides, and so on. Notice how the value changes step by step. This example starts with 10 and applies each operation in sequence.

Practical Examples

Compound operators are especially useful in loops and when accumulating values like sums, products, or building strings.

# Counting in a loop
count = 0
for i in range(5):
    count += 1
print(f"Counted to: {count}")  # Counted to: 5

+= is perfect for counting! Each time through the loop, we add 1 to count. After 5 iterations, count equals 5. This is the most common use of +=.

# Accumulating a sum
total = 0
numbers = [10, 20, 30, 40]
for num in numbers:
    total += num
print(f"Sum: {total}")  # Sum: 100

Another common pattern: summing up values. We start with total = 0 and add each number from the list. After the loop, total contains the sum (10 + 20 + 30 + 40 = 100). You could also use Python's built-in sum() function, but this shows how += works.

# Building a string
message = ""
words = ["Hello", "World", "!"]
for word in words:
    message += word + " "
print(message.strip())  # Hello World !

+= works on strings too! Here we build a sentence by adding each word plus a space. The .strip() at the end removes the trailing space. Note: for large strings, this can be slow - use " ".join(words) instead for better performance.

# Calculating factorial
n = 5
factorial = 1
for i in range(1, n + 1):
    factorial *= i
print(f"{n}! = {factorial}")  # 5! = 120

Here *= multiplies and accumulates. Factorial of 5 is 1 × 2 × 3 × 4 × 5 = 120. We start with 1 (not 0, since multiplying by 0 gives 0!) and multiply by each number in the range. The pattern *= for products mirrors += for sums.

Practice Questions: Assignment Operators

Test your understanding of assignment and compound operators.

Problem: Write a program that starts with score = 100. Then:

  • Add 50 points using +=
  • Subtract 25 points using -=
  • Double the score using *=

Print the final score.

Show Solution
score = 100
score += 50   # score = 150
score -= 25   # score = 125
score *= 2    # score = 250
print(f"Final score: {score}")  # Final score: 250

Problem: Calculate the running sum and count as you process numbers [85, 90, 78, 92, 88]. Use compound assignment operators to track the total and count, then calculate the average.

Show Solution
grades = [85, 90, 78, 92, 88]
total = 0
count = 0

for grade in grades:
    total += grade
    count += 1

average = total / count
print(f"Total: {total}")      # Total: 433
print(f"Count: {count}")      # Count: 5
print(f"Average: {average}")  # Average: 86.6

Problem: Starting with $1000 principal at 5% annual interest, use a loop and the *= operator to calculate the balance after 5 years of compound interest (interest added each year).

Show Solution
principal = 1000
rate = 0.05  # 5% annual interest
years = 5

balance = principal
for year in range(1, years + 1):
    balance *= (1 + rate)  # Add interest
    print(f"Year {year}: ${balance:.2f}")

print(f"\nFinal balance: ${balance:.2f}")
# Year 1: $1050.00
# Year 2: $1102.50
# Year 3: $1157.63
# Year 4: $1215.51
# Year 5: $1276.28
# Final balance: $1276.28
06

Identity & Membership Operators

Identity operators check if objects are the same in memory, while membership operators check if a value exists in a collection.

Identity Operators: is and is not

The is operator checks if two variables point to the exact same object in memory - not just if they have the same value. This is different from == which checks value equality.

# is vs == : Understanding the difference
a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)   # True (same VALUES)
print(a is b)   # False (different OBJECTS in memory)
print(a is c)   # True (c points to same object as a)

This is a crucial distinction! == checks if two things have the same value, while is checks if they're the same object in memory. Lists a and b contain identical values, so a == b is True. But they're stored separately in memory, so a is b is False. When we write c = a, we're not copying - c points to the exact same list as a!

# Check object identity with id()
print(id(a))    # e.g., 140234567890
print(id(b))    # e.g., 140234567999 (different!)
print(id(c))    # e.g., 140234567890 (same as a!)

The id() function shows an object's memory address. Notice how a and c have the same ID (same object), while b has a different ID (different object, same values). This helps visualize what is is actually checking.

# is not
print(a is not b)  # True (they are different objects)

is not is the negation of is. It returns True when two variables point to different objects. Note that you write it as is not (two words), not is-not or isnot.

Important: Always use is (not ==) when comparing to None. It's faster and more explicit: if x is None: not if x == None:
# Correct way to check for None
result = None

# Good - use 'is'
if result is None:
    print("No result yet")

When checking for None, always use is, not ==. It's faster (since None is a singleton - there's only one None object in Python) and it's the Pythonic convention. if result is None clearly expresses "if result is nothing."

# Also works - checking if NOT None
if result is not None:
    print(f"Result: {result}")

Similarly, use is not None to check if something has a value. This is very common in functions that may or may not return a result.

# Python caches small integers and some strings
x = 5
y = 5
print(x is y)  # True! (Python reuses small integers)

Here's a quirk: Python caches small integers (-5 to 256) and some strings for efficiency. So x = 5 and y = 5 might actually point to the same object! This is an implementation detail - don't rely on it. Always use == for value comparison.

z = 1000
w = 1000
print(z is w)  # May be False (large integers not cached)

Larger integers aren't cached, so z is w might be False even though they have the same value. The lesson: use == for comparing values, use is only for identity checks (especially with None).

Membership Operators: in and not in

The in operator checks if a value exists in a sequence (string, list, tuple, set, or dict). It's one of Python's most readable and useful operators.

# Check if item is in a list
fruits = ["apple", "banana", "cherry"]
print("banana" in fruits)     # True
print("mango" in fruits)      # False
print("mango" not in fruits)  # True

The in operator checks if a value exists in a collection. It's incredibly readable - "banana" in fruits reads almost like English! Use not in to check if something is absent. This is much cleaner than looping through to find something.

# Check if character is in a string
message = "Hello, World!"
print("H" in message)         # True
print("h" in message)         # False (case-sensitive)
print("Hello" in message)     # True (substring check!)

in works on strings too! It can check for single characters or entire substrings. "Hello" in message returns True because "Hello" appears within "Hello, World!". Remember: it's case-sensitive, so "h" is not found (capital "H" is).

# Check if key is in a dictionary
user = {"name": "John", "age": 30}
print("name" in user)         # True (checks KEYS)
print("John" in user)         # False (not a key)
print("John" in user.values())  # True (check values)

For dictionaries, in checks keys by default, not values. So "name" in user is True (it's a key), but "John" in user is False (it's a value, not a key). To check values, use in user.values().

Practical Examples

Membership operators make your code more readable and are commonly used for validation, filtering, and conditional logic.

# Input validation
valid_choices = ["yes", "no", "maybe"]
user_input = "yes"

if user_input.lower() in valid_choices:
    print("Valid choice!")
else:
    print("Invalid input!")

This is a super common pattern: checking if user input is one of several valid options. Instead of writing if user_input == "yes" or user_input == "no"..., we just check if it's in our list. The .lower() makes it case-insensitive.

# Check file extension
filename = "report.pdf"
allowed_extensions = [".pdf", ".doc", ".txt"]

if any(filename.endswith(ext) for ext in allowed_extensions):
    print("File type accepted")

Here we combine in with any() to check if a filename ends with any of the allowed extensions. The any() function returns True if at least one item in the iterator is True. This is more elegant than multiple or conditions.

# Filter items
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = [1, 2, 4, 6, 8, 10]
common = [n for n in numbers if n in evens]
print(common)  # [1, 2, 4, 6, 8, 10]

The in operator works beautifully in list comprehensions! Here we create a new list containing only the numbers that appear in both lists. This is a simple way to find common elements (though for large lists, using sets would be faster).

Practice Questions: Identity & Membership

Test your understanding of is, is not, in, and not in operators.

Problem: Write a function that checks if a password contains at least one special character from the set !@#$% using the in operator.

Show Solution
def has_special_char(password):
    special_chars = "!@#$%"
    for char in password:
        if char in special_chars:
            return True
    return False

# Test
print(has_special_char("hello"))      # False
print(has_special_char("hello!"))     # True
print(has_special_char("p@ssword"))   # True

Problem: Predict the output of each comparison. Think about when Python caches small integers vs creates new objects.

a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)    # ?
print(a is b)    # ?
print(a is c)    # ?

x = 5
y = 5
print(x is y)    # ?

p = 1000
q = 1000
print(p is q)    # ?
Show Solution
# Lists
a = [1, 2, 3]
b = [1, 2, 3]
c = a

print(a == b)    # True (same values)
print(a is b)    # False (different objects)
print(a is c)    # True (same object - c points to a)

# Small integers (-5 to 256 are cached)
x = 5
y = 5
print(x is y)    # True (same cached object)

# Large integers (not cached)
p = 1000
q = 1000
print(p is q)    # False (different objects)
# Note: In some IDEs this may be True due to optimization

Problem: Write a function that validates an email by checking:

  • Contains exactly one @ symbol
  • Domain is in the allowed list: ["gmail.com", "yahoo.com", "outlook.com"]
  • Username (before @) is not empty and not None
Show Solution
def validate_email(email):
    if email is None:
        return False
    
    allowed_domains = ["gmail.com", "yahoo.com", "outlook.com"]
    
    # Check for exactly one @
    if email.count("@") != 1:
        return False
    
    username, domain = email.split("@")
    
    # Check username is not empty
    if username == "" or username is None:
        return False
    
    # Check domain is allowed
    if domain not in allowed_domains:
        return False
    
    return True

# Test cases
print(validate_email("john@gmail.com"))     # True
print(validate_email("jane@hotmail.com"))   # False (domain not allowed)
print(validate_email("invalid-email"))       # False (no @)
print(validate_email("@gmail.com"))          # False (empty username)
print(validate_email(None))                  # False
07

Bitwise Operators

Bitwise operators work on the binary (bit-level) representation of numbers. While less common in everyday programming, they're essential for low-level operations and performance optimization.

Note: Bitwise operators are advanced. If you're a beginner, feel free to skim this section and return to it later when you need it.

Understanding Binary

Computers store all data as binary (1s and 0s). The number 5 is stored as 101, and 3 is stored as 011. Bitwise operators manipulate these individual bits.

# View binary representation
print(bin(5))   # '0b101'
print(bin(3))   # '0b11' (same as 011)
print(bin(10))  # '0b1010'

The bin() function shows a number's binary representation. The 0b prefix indicates it's binary. So 5 in binary is 101 (4 + 0 + 1 = 5), and 10 is 1010 (8 + 0 + 2 + 0 = 10). Each position represents a power of 2!

# Convert binary string to integer
print(int('101', 2))  # 5
print(int('1010', 2)) # 10

You can convert binary strings back to integers with int() - the second argument (2) tells Python it's base 2 (binary). This is handy when working with binary data or doing bit-level calculations.

Bitwise Operators

Each bitwise operator performs a specific operation on corresponding bits of two numbers.

# AND (&) - 1 if BOTH bits are 1
print(5 & 3)   # 1
# 5 = 101
# 3 = 011
# & = 001 = 1

Bitwise AND (&) compares each bit position. Only positions where BOTH numbers have a 1 result in 1. For 5 (101) and 3 (011), only the rightmost bit is 1 in both, so the result is 001 = 1.

# OR (|) - 1 if EITHER bit is 1
print(5 | 3)   # 7
# 5 = 101
# 3 = 011
# | = 111 = 7

Bitwise OR (|) gives 1 if either number has a 1 in that position. For 5 (101) and 3 (011), the result is 111 = 7 because each position has at least one 1.

# XOR (^) - 1 if bits are DIFFERENT
print(5 ^ 3)   # 6
# 5 = 101
# 3 = 011
# ^ = 110 = 6

Bitwise XOR (^) gives 1 only when the bits are different. Where both are 0 or both are 1, the result is 0. This is useful for toggling bits and for encryption.

# NOT (~) - inverts all bits (two's complement)
print(~5)      # -6

Bitwise NOT (~) flips all bits. Due to how Python represents negative numbers (two's complement), ~5 gives -6. The formula is ~n = -(n+1).

# Left shift (<<) - multiply by 2^n
print(5 << 1)  # 10 (5 * 2)
print(5 << 2)  # 20 (5 * 4)

Left shift (<<) moves bits to the left, adding zeros on the right. Each shift effectively multiplies by 2. 5 << 1 is 5 × 2 = 10, and 5 << 2 is 5 × 4 = 20. This is faster than multiplication!

# Right shift (>>) - divide by 2^n
print(10 >> 1) # 5 (10 / 2)
print(10 >> 2) # 2 (10 / 4)

Right shift (>>) moves bits to the right, dropping bits that fall off. Each shift divides by 2 (integer division). 10 >> 1 is 10 ÷ 2 = 5. This is faster than division!

Practical Uses

Bitwise operators have specific use cases like working with flags, permissions, or performance optimization.

# Check if number is even (faster than % 2)
n = 42
is_even = (n & 1) == 0  # True

A clever trick: the last bit of any even number is 0, and for odd numbers it's 1. So n & 1 extracts just the last bit. If it's 0, the number is even! This is faster than n % 2 == 0 at the machine level.

# Permissions using bit flags
READ = 1    # 001
WRITE = 2   # 010
EXECUTE = 4 # 100

# Combine permissions
user_perms = READ | WRITE  # 011 = 3
print(user_perms)  # 3

Bit flags are a common pattern in systems programming. Each permission is a power of 2, so each occupies a different bit position. Using OR (|), we can combine permissions: READ (001) | WRITE (010) = 011, meaning the user has both read and write permissions.

# Check if user has WRITE permission
has_write = (user_perms & WRITE) != 0
print(f"Can write: {has_write}")  # True

To check if a specific permission is set, use AND (&) with that flag. If the result is non-zero, the permission exists. 011 & 010 = 010 (not zero), so WRITE is enabled. This pattern is used in file systems, network protocols, and game programming.

# Fast multiply/divide by powers of 2
x = 10
print(x << 3)  # 80 (x * 8)
print(x >> 1)  # 5 (x / 2)

Bit shifting is one of the fastest operations a computer can do. x << 3 multiplies by 8 (2³), and x >> 1 divides by 2. In performance-critical code (games, graphics), this can make a real difference!

Practice Questions: Bitwise Operators

Test your understanding of bitwise operations at the binary level.

Problem: What is the result of 12 & 10? Show your work by converting both numbers to binary first.

Show Solution
# Convert to binary
# 12 = 1100
# 10 = 1010

# AND operation (both bits must be 1)
#   1100
# & 1010
# ------
#   1000 = 8

result = 12 & 10
print(result)  # 8

# Verify with Python
print(bin(12))  # 0b1100
print(bin(10))  # 0b1010
print(bin(8))   # 0b1000

Problem: Create a permission system where:

  • READ = 1, WRITE = 2, DELETE = 4, ADMIN = 8
  • Create a user with READ and WRITE permissions
  • Add DELETE permission
  • Check if user has ADMIN permission
  • Remove WRITE permission
Show Solution
# Define permission flags
READ = 1    # 0001
WRITE = 2   # 0010
DELETE = 4  # 0100
ADMIN = 8   # 1000

# Create user with READ and WRITE
user = READ | WRITE  # 0011 = 3
print(f"Initial: {bin(user)}")  # 0b11

# Add DELETE permission
user = user | DELETE  # 0111 = 7
print(f"After adding DELETE: {bin(user)}")  # 0b111

# Check if user has ADMIN
has_admin = (user & ADMIN) != 0
print(f"Has ADMIN: {has_admin}")  # False

# Remove WRITE permission (use XOR or AND with NOT)
user = user & ~WRITE  # 0111 & 1101 = 0101 = 5
print(f"After removing WRITE: {bin(user)}")  # 0b101

# Verify final permissions
print(f"Has READ: {(user & READ) != 0}")    # True
print(f"Has WRITE: {(user & WRITE) != 0}")  # False
print(f"Has DELETE: {(user & DELETE) != 0}")  # True

Problem: Use XOR to swap two variables a = 5 and b = 9 without using a temporary variable. Explain why it works.

Show Solution
a = 5  # 0101
b = 9  # 1001

print(f"Before: a={a}, b={b}")

# XOR swap trick
a = a ^ b  # a = 0101 ^ 1001 = 1100 = 12
b = a ^ b  # b = 1100 ^ 1001 = 0101 = 5 (original a!)
a = a ^ b  # a = 1100 ^ 0101 = 1001 = 9 (original b!)

print(f"After: a={a}, b={b}")

# Why it works:
# 1. a ^ b stores combined info of both values
# 2. (a ^ b) ^ b = a (XOR is self-inverse)
# 3. (a ^ b) ^ a = b

# Note: In Python, you can simply do: a, b = b, a
# But XOR swap is useful in low-level languages!
08

Operator Precedence

When multiple operators appear in an expression, Python follows specific rules to determine which operations happen first. Understanding precedence prevents bugs!

Concept

Operator Precedence

Operator precedence determines the order in which operators are evaluated. Just like in math where multiplication happens before addition, Python has its own hierarchy. Higher precedence operators are evaluated first.

Remember: When in doubt, use parentheses () to make your intentions explicit and your code more readable!

Precedence Table (High to Low)

Priority Operator Description
1 (Highest) () Parentheses
2 ** Exponentiation
3 +x, -x, ~x Unary plus, minus, NOT
4 *, /, //, % Multiplication, division, modulo
5 +, - Addition, subtraction
6 <<, >> Bitwise shifts
7 & Bitwise AND
8 ^ Bitwise XOR
9 | Bitwise OR
10 ==, !=, <, >, <=, >=, is, in Comparisons
11 not Logical NOT
12 and Logical AND
13 (Lowest) or Logical OR

Examples

Let's see how precedence affects the result of expressions and how parentheses can change behavior.

# Math follows standard precedence
print(2 + 3 * 4)      # 14 (not 20!) - multiplication first
print((2 + 3) * 4)    # 20 - parentheses override

Just like in math class, multiplication happens before addition. So 2 + 3 * 4 is 2 + 12 = 14, not 5 * 4 = 20. Parentheses override the default order - (2 + 3) * 4 forces addition first.

# Exponentiation before multiplication
print(2 * 3 ** 2)     # 18 (3² = 9, then 2 * 9)
print((2 * 3) ** 2)   # 36 (6², parentheses first)

Exponentiation (**) has higher precedence than multiplication. So 2 * 3 ** 2 calculates 3 ** 2 = 9 first, then 2 * 9 = 18. With parentheses, we get (2 * 3) ** 2 = 6 ** 2 = 36.

# Comparison before logical
print(5 > 3 and 2 < 4)  # True
# Evaluated as: (5 > 3) and (2 < 4) → True and True → True

Comparison operators like > and < have higher precedence than and. So Python first evaluates both comparisons (both True), then applies and.

# Not before and, and before or
print(True or False and False)   # True
# Evaluated as: True or (False and False) → True or False → True
print((True or False) and False) # False

Among logical operators: not is evaluated first, then and, then or. So True or False and False evaluates False and False = False first, then True or False = True. Parentheses change this behavior completely!

Best Practices

Even if you know the precedence rules, using parentheses makes your code clearer and prevents subtle bugs.

# Unclear - relies on precedence knowledge
result = a + b * c / d - e ** f

# Clear - parentheses show intent
result = a + ((b * c) / d) - (e ** f)

Even if you know all the precedence rules, others reading your code might not. Adding parentheses makes your intentions crystal clear. The second version instantly shows what gets calculated first.

# Real example: BMI calculation
weight = 70  # kg
height = 1.75  # m

# Without parentheses (works, but unclear)
bmi = weight / height ** 2

# With parentheses (clear intent)
bmi = weight / (height ** 2)
print(f"BMI: {bmi:.1f}")  # BMI: 22.9

In the BMI formula, weight is divided by height squared. Both versions work (since ** has higher precedence than /), but the parenthesized version clearly shows "divide weight by (height squared)" at a glance. Always favor clarity!

# Complex condition - always use parentheses
age = 25
is_student = True
income = 30000

# Hard to read
if age < 30 and is_student or income < 40000:
    print("Eligible")

# Easy to read
if (age < 30 and is_student) or (income < 40000):
    print("Eligible")

Complex conditions NEED parentheses for readability. The second version clearly shows two paths to eligibility: either (young AND student) OR (low income). Without parentheses, you have to mentally apply precedence rules to understand the logic - that's a recipe for bugs!

Practice Questions: Operator Precedence

Test your understanding of how Python evaluates complex expressions.

Problem: What is the result of 2 + 3 * 4 - 6 / 2? Show the evaluation order step by step.

Show Solution
# Expression: 2 + 3 * 4 - 6 / 2

# Step 1: Multiplication and division (left to right)
# 3 * 4 = 12
# 6 / 2 = 3.0

# Step 2: Addition and subtraction (left to right)
# 2 + 12 - 3.0 = 11.0

result = 2 + 3 * 4 - 6 / 2
print(result)  # 11.0

# With parentheses showing order:
# 2 + (3 * 4) - (6 / 2)
# = 2 + 12 - 3.0
# = 11.0

Problem: Given x = 5, y = 10, z = 15, what is the result of:

result = x < y and y < z or x > z

Show the evaluation order.

Show Solution
x, y, z = 5, 10, 15

# Expression: x < y and y < z or x > z

# Step 1: Comparison operators (higher precedence)
# x < y  ->  5 < 10  ->  True
# y < z  ->  10 < 15 ->  True
# x > z  ->  5 > 15  ->  False

# Step 2: Logical operators (and before or)
# True and True  ->  True
# True or False  ->  True

result = x < y and y < z or x > z
print(result)  # True

# With parentheses showing order:
# ((x < y) and (y < z)) or (x > z)
# ((True) and (True)) or (False)
# (True) or (False)
# True

Problem: Rewrite the following expression with explicit parentheses that show exactly how Python evaluates it:

result = not a or b and c > d + e * f

Then evaluate it with a=True, b=True, c=10, d=2, e=3, f=2

Show Solution
# Precedence order (highest to lowest):
# 1. * (multiplication)
# 2. + (addition)
# 3. > (comparison)
# 4. not (logical not)
# 5. and (logical and)
# 6. or (logical or)

# With explicit parentheses:
result = (not a) or (b and (c > (d + (e * f))))

# Evaluation with a=True, b=True, c=10, d=2, e=3, f=2
a, b, c, d, e, f = True, True, 10, 2, 3, 2

# Step by step:
# e * f = 3 * 2 = 6
# d + 6 = 2 + 6 = 8
# c > 8 = 10 > 8 = True
# b and True = True and True = True
# not a = not True = False
# False or True = True

result = not a or b and c > d + e * f
print(result)  # True

# Verify with parentheses
result2 = (not a) or (b and (c > (d + (e * f))))
print(result2)  # True
09

Key Takeaways

Arithmetic Operators

+, -, *, /, //, %, **. Regular division always returns float. Floor division rounds down.

Comparison Operators

==, !=, <, >, <=, >=. Return True/False. Use == for comparison, not =!

Logical Operators

and, or, not. Short-circuit evaluation stops early. Use for combining conditions.

Assignment Operators

=, +=, -=, etc. Compound operators are shorthand. Python supports multiple assignment.

Identity & Membership

is checks same object, == checks same value. in checks membership in sequences.

Precedence

Parentheses first, then **, then *//, then +/-. When in doubt, use parentheses!

10

Interactive Demo

Experiment with operators in real-time! Adjust values and see how different operators work together.

Operator Calculator

Enter two numbers and see the results of all arithmetic operators.

a + b = 22
a - b = 12
a * b = 85
a / b = 3.4
a // b = 3
a % b = 2
a ** b = 1419857
a == b = False
a > b = True

Precedence Visualizer

See how operator precedence affects expression evaluation.

Without parentheses:
2 + 3 * 4 = 14
With explicit grouping:
2 + (3 * 4) = 14
Multiplication (*) has higher precedence than addition (+), so 3 * 4 = 12 is calculated first, then 2 + 12 = 14.
11

Knowledge Check

Quick Quiz

Test what you've learned about Python operators

1 What is the result of 10 / 4 in Python 3?
2 What does 17 % 5 return?
3 Which expression checks if x is between 1 and 10 (inclusive)?
4 What is the result of True or False and False?
5 What is the difference between == and is?
6 What does x += 5 do?
Answer all questions to check your score