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 | &, |, ^, ~, <<, >> |
Arithmetic Operators
Arithmetic operators perform mathematical calculations. These are the operators you'll use most often - from simple addition to complex mathematical expressions.
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.
/) 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.
%) 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
Comparison Operators
Comparison operators compare two values and return a boolean (True or False).
They're essential for making decisions in your code.
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?"
= 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
Logical Operators
Logical operators combine multiple conditions into one expression. They're essential for making complex decisions in your code.
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!
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
Assignment Operators
Assignment operators assign values to variables. Beyond the simple =, Python offers
compound operators that combine assignment with arithmetic.
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
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.
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
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.
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!
Operator Precedence
When multiple operators appear in an expression, Python follows specific rules to determine which operations happen first. Understanding precedence prevents bugs!
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
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!
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.
2 + 3 * 4 = 14
2 + (3 * 4) = 14
Knowledge Check
Quick Quiz
Test what you've learned about Python operators