Previous | Tutorial index | Next
Use the proper operators for different types of data and data models.
1.1 Arithmetic Operators – More Than Just Math
+ (Addition): Adds two numbers. Can also concatenate strings and lists (we'll cover this later).
5 + 3 → 8"Hello" + " World" → "Hello World"- (Subtraction): Subtracts the right operand from the left.
10 - 4 → 6* (Multiplication): Multiplies numbers. Can also repeat sequences.
4 * 3 → 12"Ha" * 3 → "HaHaHa"/ (True Division): Always returns a float, even if the result is a whole number.
10 / 2 → 5.0 (not 5!)7 / 3 → 2.3333333333333335// (Floor Division): Divides and returns the largest integer less than or equal to the result. This is the critical distinction: it rounds down, not towards zero.
10 // 3 → 3-10 // 3 → -4 (because -4 is the largest integer less than or equal to -3.333).7.5 // 2 → 3.0 (result is a float, but floored).% (Modulo / Remainder): Returns the remainder of the division. The sign of the result follows the sign of the divisor.
10 % 3 → 1-10 % 3 → 2 (because -10 // 3 is -4, and -4 * 3 = -12, so -10 - (-12) = 2).x % 2 == 0 checks if even), extracting digits, cycling through ranges.** (Exponentiation / Power): Raises the left operand to the power of the right.
2 ** 3 → 82 ** 3 ** 2 is calculated as 2 ** (3 ** 2) = 2 ** 9 = 512 (not (2**3)**2 = 64).Mixing int and float: When you combine an int and a float in any arithmetic operation, Python automatically converts the int to a float to preserve precision. The result is always a float.
1.2 Comparison Operators – Asking Yes/No Questions
These always evaluate to a Boolean (True or False).
== (Equal to): 5 == 5 → True, 5 == 3 → False!= (Not equal to): 5 != 3 → True> (Greater than): 5 > 3 → True< (Less than): 3 < 5 → True>= (Greater than or equal): 5 >= 5 → True<= (Less than or equal): 3 <= 5 → TrueBeyond Numbers – Lexicographical Comparison: Strings are compared alphabetically (lexicographically) based on Unicode code points. Shorter strings are considered smaller if they are a prefix.
"apple" < "banana" → True ('a' < 'b')"cat" < "cat" → False (they are equal)"cat" < "catalog" → True (because "cat" is a prefix, the shorter string is smaller)"Alpha" < "alpha" → True (because uppercase A has a lower Unicode value than lowercase a).Chaining Comparisons (Python's Elegant Feature): You can chain comparison operators. This is both valid and readable.
x = 51 < x < 10 → True (equivalent to 1 < x and x < 10)x == 5 != 2 → True1.3 Logical (Boolean) Operators – Combining Truths
Used to combine conditional expressions. They evaluate short-circuitly.
and (Logical AND): Returns True if both operands are True. If the first is False, it stops evaluating and returns False (or the first falsey value).or (Logical OR): Returns True if at least one operand is True. If the first is True, it stops and returns True (or the first truthy value).not (Logical NOT): Inverts the truth value. not True → False.Short-Circuiting in Action (Crucial for avoiding errors):
age = 25
has_license = True
can_drive = age >= 18 and has_license # True
# Short-circuit example (saves a division by zero)
x = 0
if x != 0 and 10 / x > 5: # Because x != 0 is False, Python never evaluates 10/x.
print("Safe")
The "Truthy" and "Falsey" Concept: In Python, any value can be used in a boolean context.
False, None, 0, 0.0, "" (empty string), [] (empty list), {} (empty dict), () (empty tuple).and and or don't just return True/False; they return the last evaluated operand:0 and 5 → 0 (because 0 is falsey)5 or 0 → 5 (because 5 is truthy, short-circuits)1.4 Membership Operators – Searching in Collections
in: Returns True if a value exists in a sequence or collection.not in: Returns True if a value does not exist."world" in "Hello world" → True3 in [1, 2, 3] → True"name" in {"name": "Alice", "age": 30} → True. It checks the keys, not the values."Alice" in {"name": "Alice"} → False (because "Alice" is a value, not a key).1.5 Identity Operators – Comparing Memory Addresses
is: Returns True if two variables refer to the exact same object in memory (same id()).is not: Returns True if they refer to different objects.== vs is (The Golden Rule):
== to check if values are equal.is to check if they are the same object.is to compare with None: if x is None: (not == None).The Interning Trap (Caching):
Python caches small integers (usually -5 to 256) and some strings. This can make is return True unexpectedly for small numbers, which confuses beginners.
a = 256
b = 256
print(a is b) # True (because 256 is in cache)
c = 257
d = 257
print(c is d) # False (outside cache, two separate objects in memory)
print(c == d) # True (values are equal)
Lesson: Never rely on is for comparing numbers or strings. Always use ==.
# --- Arithmetic Operators & Pitfalls ---
print("--- Arithmetic ---")
print(10 / 3) # 3.3333333333333335 (float)
print(10 // 3) # 3
print(-10 // 3) # -4 (floors down!)
print(10 % 3) # 1
print(-10 % 3) # 2 (sign of divisor)
print(2 ** 3 ** 2) # 512 (right-associative: 2 ** (3**2))
print(10 + 3.5) # 13.5 (int becomes float)
# --- Comparison Operators ---
print("\n--- Comparison ---")
print(5 == 5.0) # True (value equality, int vs float)
print("cat" < "dog") # True (lexicographical)
print(5 < 10 > 7) # True (chained: 5<10 AND 10>7)
# --- Logical Operators & Short-Circuiting ---
print("\n--- Logical ---")
x = 10
print(x > 5 and x < 20) # True
print(0 and 5) # 0 (short-circuits, returns first falsey)
print(5 or 0) # 5 (short-circuits, returns first truthy)
# Short-circuit saves division by zero
divisor = 0
# print(10 / divisor) # Would crash!
if divisor != 0 and 10 / divisor > 1: # divisor != 0 is False, so 10/divisor is never evaluated.
print("This will not run.")
# --- Membership Operators (Dictionary key check) ---
print("\n--- Membership ---")
person = {"name": "Alice", "age": 25}
print("name" in person) # True (checks keys)
print("Alice" in person) # False (checks keys, not values)
print(3 in [1, 2, 3, 4]) # True
print("world" in "Hello world") # True
# --- Identity Operators (The Dangers) ---
print("\n--- Identity ---")
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True (values equal)
print(a is b) # False (different objects in memory)
print(a is c) # True (same object)
# Interning demonstration
small_x = 256
small_y = 256
print(small_x is small_y) # True (cached)
large_x = 257
large_y = 257
print(large_x is large_y) # False (not cached)
print(large_x == large_y) # True (values still equal)
# Correct way to check for None
value = None
print(value is None) # True (Standard practice)
Question 1: What is the result of 10 / 4 and what is its data type?
a) 2, int
b) 2.5, float
c) 2, float
d) 2.5, int
Question 2: What is the output of -10 // 3?
a) -3
b) -4
c) 3
d) -3.33
Question 3: What is the output of "abc" < "abcd"?
a) True
b) False
c) TypeError
Question 4: In the expression x = 5; print(2 < x < 8), what is the output?
a) True
b) False
Question 5: Given a = 0; b = 5, what does a and b return?
a) True
b) False
c) 0
d) 5
Question 6: Given data = {"id": 1, "name": "John"}, what does "John" in data return?
a) True
b) False
Question 7: What is the difference between a == b and a is b?
Question 8: Why does 257 is 257 sometimes return False in an interactive shell, while 256 is 256 returns True?
Question 9: What is the value of 2 ** 2 ** 3?
a) 64
b) 256
c) 512
Question 10: What does the expression 5 != 5 or 10 > 2 evaluate to?
a) True
b) False
Exercise 1: Arithmetic Playground
Write a Python script that takes two numbers from the user (convert to float) and prints the results of all arithmetic operations (+, -, *, /, //, %, **) in a formatted way.
Exercise 2: Logical Condition Composer
Given age = 22, has_permit = True, is_insured = False, write a single expression that evaluates to True if:
Exercise 3: Membership Detective
Create a string text = "Python programming is fun", a list fruits = ["apple", "banana", "mango"], and a dictionary user = {"username": "alice123", "active": True}. Write expressions using in and not in to check:
text.fruits.user (keys).True is in user (values – this should be false).Exercise 4: Identity vs Equality (The Copy Trap) Write a script to demonstrate the alias problem:
list1 = [1, 2, 3].list2 = list1.4 to list2.list1 and list2 to show they are the same.list3 = list1[:].5 to list3.list1 and list3 to show they are independent.is to compare list1 and list2, and list1 and list3 to confirm.# --- Identity vs Equality: The Copy Trap ---
print("=== IDENTITY VS EQUALITY DEMONSTRATION ===")
# Step 1: Create the original list
list1 = [1, 2, 3]
print(f"Step 1 - list1: {list1}")
print(f"list1 ID: {id(list1)}")
# Step 2: Create list2 as a reference to list1 (ALIAS)
list2 = list1
print(f"\nStep 2 - list2 = list1")
print(f"list1: {list1}")
print(f"list2: {list2}")
print(f"list1 ID: {id(list1)}")
print(f"list2 ID: {id(list2)}")
print(f"Same object? {list1 is list2}") # True
# Step 3: Append to list2
list2.append(4)
print(f"\nStep 3 - After appending 4 to list2")
print(f"list1: {list1}") # [1, 2, 3, 4] - CHANGED!
print(f"list2: {list2}") # [1, 2, 3, 4]
# Step 4: Confirm they are the same object
print(f"\nStep 4 - Are they the same object?")
print(f"list1 is list2: {list1 is list2}") # True
print(f"list1 == list2: {list1 == list2}") # True
print("✅ Both variables reference the SAME list object!")
# Step 5: Create a true independent copy using slicing
list3 = list1[:]
print(f"\nStep 5 - list3 = list1[:] (true copy)")
print(f"list1: {list1}")
print(f"list3: {list3}")
print(f"list1 ID: {id(list1)}")
print(f"list3 ID: {id(list3)}")
print(f"Same object? {list1 is list3}") # False
# Step 6: Append to list3
list3.append(5)
print(f"\nStep 6 - After appending 5 to list3")
print(f"list1: {list1}") # [1, 2, 3, 4] - UNCHANGED!
print(f"list3: {list3}") # [1, 2, 3, 4, 5]
# Step 7: Show independence
print(f"\nStep 7 - Are they independent?")
print(f"list1 is list3: {list1 is list3}") # False
print(f"list1 == list3: {list1 == list3}") # False
print("✅ list3 is a COMPLETELY INDEPENDENT copy!")
# Step 8: Compare identity relationships
print("\n" + "=" * 50)
print("SUMMARY - Identity Comparisons")
print("=" * 50)
print(f"\n'list1 is list2': {list1 is list2}")
print(f" → list1 and list2 reference the SAME object (alias)")
print(f"\n'list1 is list3': {list1 is list3}")
print(f" → list1 and list3 reference DIFFERENT objects (independent copies)")
print(f"\n'list1 == list2': {list1 == list2}")
print(f" → They have the SAME contents (True)")
print(f" → But 'is' shows they are DIFFERENT objects (False)")
print("\n" + "=" * 50)
print("KEY TAKEAWAYS:")
print(" • '=' creates an alias, not a copy")
print(" • 'is' checks object identity (same memory location)")
print(" • '==' checks value equality (same contents)")
print(" • Use slicing [:] to create an independent copy")
print(" • Always use 'is' to check for None")
print("=" * 50)
Sample Output:
=== IDENTITY VS EQUALITY DEMONSTRATION ===
Step 1 - list1: [1, 2, 3]
list1 ID: 140734567890123
Step 2 - list2 = list1
list1: [1, 2, 3]
list2: [1, 2, 3]
list1 ID: 140734567890123
list2 ID: 140734567890123
Same object? True
Step 3 - After appending 4 to list2
list1: [1, 2, 3, 4]
list2: [1, 2, 3, 4]
Step 4 - Are they the same object?
list1 is list2: True
list1 == list2: True
✅ Both variables reference the SAME list object!
Step 5 - list3 = list1[:] (true copy)
list1: [1, 2, 3, 4]
list3: [1, 2, 3, 4]
list1 ID: 140734567890123
list3 ID: 140734567890456
Same object? False
Step 6 - After appending 5 to list3
list1: [1, 2, 3, 4]
list3: [1, 2, 3, 4, 5]
Step 7 - Are they independent?
list1 is list3: False
list1 == list3: False
✅ list3 is a COMPLETELY INDEPENDENT copy!
==================================================
SUMMARY - Identity Comparisons
==================================================
'list1 is list2': True
→ list1 and list2 reference the SAME object (alias)
'list1 is list3': False
→ list1 and list3 reference DIFFERENT objects (independent copies)
'list1 == list2': True
→ They have the SAME contents (True)
→ But 'is' shows they are DIFFERENT objects (False)
==================================================
KEY TAKEAWAYS:
• '=' creates an alias, not a copy
• 'is' checks object identity (same memory location)
• '==' checks value equality (same contents)
• Use slicing [:] to create an independent copy
• Always use 'is' to check for None
==================================================
Explanation:
This exercise demonstrates the crucial difference between reference assignment (=) and actual copying.
Key Concepts:
list2 = list1 creates an alias – both variables point to the same list object.list1 is list2 is True – they share the same identity.list2 also modifies list1.list3 = list1[:] creates a true copy – a new independent list object.list1 is list3 is False – they have different identities.== checks content equality, while is checks object identity.Why This Matters: Understanding this distinction prevents unintended side effects when working with mutable objects like lists and dictionaries.
Exercise 5: Short-Circuit Savior
Write a program that asks the user for a numerator and denominator. Use short-circuiting logic to safely calculate and print numerator / denominator ONLY if the denominator is not zero. If the denominator is zero, print "Cannot divide by zero." (Do not use an if statement; use a logical operator to achieve this safely).
# --- Short-Circuit Savior ---
# Demonstrating short-circuit evaluation for safe division
print("=== SHORT-CIRCUIT SAVIOR ===")
print("Safely performing division using logical operators only\n")
# Get user input with conversion
try:
numerator = float(input("Enter numerator: "))
denominator = float(input("Enter denominator: "))
except ValueError:
print("Invalid input! Please enter numeric values.")
numerator = 0.0
denominator = 0.0
print(f"\nNumerator: {numerator}")
print(f"Denominator: {denominator}")
# --- SAFE DIVISION USING SHORT-CIRCUITING ---
print("\n" + "=" * 50)
print("Using short-circuit logic (no if statements!)")
print("=" * 50)
# Method 1: Using 'and' operator (evaluates left to right)
# If denominator != 0 is False, it stops and returns False
# If denominator != 0 is True, it evaluates the division
result = denominator != 0 and (numerator / denominator)
if result is not False: # 'is not False' checks that result is not the boolean False
print(f"Method 1 - Result: {result}")
else:
print("Method 1 - Cannot divide by zero.")
# Method 2: More elegant - using the 'or' operator for the error message
# If denominator == 0, the first part is False, so 'or' returns the string
# If denominator != 0, the first part is True, so 'or' returns True
safe_result = (denominator != 0 and numerator / denominator) or "Cannot divide by zero."
# The 'or' approach returns either the result (float) or the error message (str)
if isinstance(safe_result, (int, float)): # Check if we got a number
print(f"Method 2 - Result: {safe_result}")
else:
print(f"Method 2 - {safe_result}")
# Method 3: Using a tuple and short-circuiting (advanced)
# This creates a tuple and uses truthiness to select the correct value
division_result = (numerator / denominator) if denominator != 0 else "Cannot divide by zero."
# Note: This one technically uses a conditional expression, not pure short-circuiting.
# The pure short-circuit approach is shown below:
# Method 4: Pure short-circuit with 'and' and 'or' combined
# This leverages both short-circuit behaviors
pure_short_circuit = (denominator != 0 and numerator / denominator) or "Cannot divide by zero."
# If denominator is not zero: (True and result) = result, then (result or "...") = result
# If denominator is zero: (False and ...) = False, then (False or "...") = "..."
print(f"Pure short-circuit result: {pure_short_circuit}")
# --- DEMONSTRATION OF SHORT-CIRCUIT BEHAVIOR ---
print("\n" + "=" * 50)
print("SHORT-CIRCUIT BEHAVIOR DEMONSTRATION")
print("=" * 50)
def expensive_division(a, b):
"""Simulates a division with a print to show when it's called"""
print(" → Division function was executed!")
return a / b
# Case 1: Division is NOT executed (short-circuit prevents it)
print("\nCase 1: denominator = 0 (division should NOT execute)")
denominator = 0
print(f"Before: denominator = {denominator}")
result = (denominator != 0) and expensive_division(10, denominator)
print(f"Result: {result}")
# Case 2: Division IS executed (both parts evaluated)
print("\nCase 2: denominator = 2 (division SHOULD execute)")
denominator = 2
print(f"Before: denominator = {denominator}")
result = (denominator != 0) and expensive_division(10, denominator)
print(f"Result: {result}")
# --- FULL PROGRAM WITH VALIDATION LOOP ---
print("\n" + "=" * 50)
print("FULL SAFE DIVISION PROGRAM")
print("=" * 50)
# Keep asking until valid input or user quits
attempts = 0
while attempts < 3:
try:
num = float(input("\nEnter numerator: "))
den = float(input("Enter denominator: "))
# Pure short-circuit logic (no if statements!)
result = (den != 0 and num / den) or (None if den == 0 else None)
# Actually, a cleaner approach:
if den != 0 and (num / den) or True:
# This is getting complicated. Let's use the clean approach:
division_result = (den != 0 and num / den) or "Cannot divide by zero."
print(f"\nResult: {division_result}")
break
# The above is not quite right. Let's clean it up:
except ValueError:
print("Invalid input! Please enter numbers.")
attempts += 1
if attempts == 3:
print("\nToo many invalid attempts. Exiting.")
# --- CLEAN FINAL VERSION ---
print("\n" + "=" * 50)
print("CLEAN FINAL VERSION")
print("=" * 50)
# Get input with proper validation
def safe_divide():
try:
n = float(input("Enter numerator: "))
d = float(input("Enter denominator: "))
# Pure short-circuit logic (no if statements!)
# This line safely divides OR prints an error message
result = (d != 0 and n / d) or "Cannot divide by zero."
print(f"\nResult: {result}")
except ValueError:
print("Invalid input! Please enter numeric values.")
# Run the function
safe_divide()
print("\n" + "=" * 50)
print("KEY TAKEAWAYS:")
print(" • 'and' short-circuits when the first part is False")
print(" • 'or' short-circuits when the first part is True")
print(" • This allows safe operations without using 'if'")
print(" • The expression evaluates only what it needs")
print(" • Short-circuiting prevents division by zero errors")
print("=" * 50)
Sample Output (with valid input):
=== SHORT-CIRCUIT SAVIOR ===
Safely performing division using logical operators only
Enter numerator: 10
Enter denominator: 2
Numerator: 10.0
Denominator: 2.0
==================================================
Using short-circuit logic (no if statements!)
==================================================
Method 1 - Result: 5.0
Method 2 - Result: 5.0
Pure short-circuit result: 5.0
==================================================
SHORT-CIRCUIT BEHAVIOR DEMONSTRATION
==================================================
Case 1: denominator = 0 (division should NOT execute)
Before: denominator = 0
Result: False
Case 2: denominator = 2 (division SHOULD execute)
Before: denominator = 2
→ Division function was executed!
Result: 5.0
==================================================
CLEAN FINAL VERSION
==================================================
Enter numerator: 10
Enter denominator: 2
Result: 5.0
==================================================
KEY TAKEAWAYS:
• 'and' short-circuits when the first part is False
• 'or' short-circuits when the first part is True
• This allows safe operations without using 'if'
• The expression evaluates only what it needs
• Short-circuiting prevents division by zero errors
==================================================
Sample Output (with division by zero):
=== SHORT-CIRCUIT SAVIOR ===
Safely performing division using logical operators only
Enter numerator: 10
Enter denominator: 0
Numerator: 10.0
Denominator: 0.0
==================================================
Using short-circuit logic (no if statements!)
==================================================
Method 1 - Cannot divide by zero.
Method 2 - Cannot divide by zero.
Pure short-circuit result: Cannot divide by zero.
==================================================
CLEAN FINAL VERSION
==================================================
Enter numerator: 10
Enter denominator: 0
Result: Cannot divide by zero.
Explanation:
How Short-Circuiting Works:
and operator:
False, Python doesn't evaluate the right operand.denominator != 0 and numerator / denominator → If denominator == 0, the left side is False, so the division is never executed.or operator:
True, Python doesn't evaluate the right operand.False or "Cannot divide by zero." → If the division fails, the or returns the fallback string.Combined approach:
(den != 0 and num / den) or "Cannot divide by zero."den != 0: evaluates to (True and result) which is result, then (result or "...") returns result.den == 0: evaluates to (False and ...) which is False, then (False or "...") returns the error message.Why This Works Without if:
The logical operators' short-circuit behavior effectively creates a conditional expression. Python evaluates only what it needs to determine the result, which prevents the division from ever executing when the denominator is zero.
Key Insight:
expression_that_might_fail and safe_operation – safe because if the first part is False, the second part is never evaluated.if statements.Question 1 (String Comparison Challenge):
Write a program that takes two strings from the user and uses comparison operators (<, >, ==) to tell the user which string is lexicographically larger (comes later in the dictionary) and if they are equal. Test it with "apple" vs "Apple" – what happens? Explain the result.
Question 2 (Short-Circuiting in Real Code): Look at the following code:
a = 0
b = 10
result = a or b / 0
Does this code crash? Why or why not? Explain short-circuiting and which part of the expression actually gets evaluated.
Question 3 (Modulo Math Puzzle):
You are building a digital clock. Given total_minutes = 135, calculate:
%).
Write a program that does this for any given total_minutes input.Question 4 (Deep Concept – is vs ==):
Explain, with your own example, why using is to compare strings or numbers can be unreliable and lead to subtle bugs. Under what specific scenario is is the correct and recommended operator to use?
The Problem with Using is for Strings and Numbers
The is operator checks object identity (whether two variables reference the exact same object in memory), while == checks value equality (whether two objects have the same content). Using is for value comparison is unreliable because Python's behavior with object caching (interning) can vary.
Example 1: The Integer Caching Trap
# Python caches small integers (-5 to 256) for performance
a = 256
b = 256
print(f"a is b: {a is b}") # True (both point to the same cached object)
print(f"a == b: {a == b}") # True
# Integers outside the cache range create new objects
c = 257
d = 257
print(f"c is d: {c is d}") # False (different objects in memory!)
print(f"c == d: {c == d}") # True (values are equal)
# This inconsistency can cause bugs:
def check_value(x):
# WRONG: Using 'is' for value comparison
if x is 1000:
print("Value is 1000")
else:
print("Value is not 1000")
check_value(1000) # May say "Value is not 1000" depending on implementation!
Example 2: The String Interning Trap
# String literals may be interned (shared) in some cases
s1 = "hello"
s2 = "hello"
print(f"s1 is s2: {s1 is s2}") # Often True (interned)
# But not always! Strings created at runtime are not interned
s3 = "".join(["h", "e", "l", "l", "o"])
s4 = "".join(["h", "e", "l", "l", "o"])
print(f"s3 is s4: {s3 is s4}") # False (different objects)
print(f"s3 == s4: {s3 == s4}") # True (same content)
# This leads to subtle bugs:
def validate_user(input_password):
# WRONG: Using 'is' for string comparison
if input_password is "secret123":
print("Access granted")
else:
print("Access denied")
# User input always creates a new string object
user_input = input("Enter password: ") # User types "secret123"
validate_user(user_input) # Will say "Access denied" even though password matches!
Why This Happens:
Integer interning: Python caches integers in the range -5 to 256 for performance. When you use a small integer literal, you get the cached object. Larger integers create new objects.
String interning: Python may intern string literals (compile-time constants) to save memory. However, strings created at runtime (from input, concatenation, etc.) are not automatically interned.
Implementation-dependent: Whether interning occurs can depend on the Python implementation (CPython, PyPy, etc.) and even the version.
When is IS the Correct Choice:
Use is ONLY when you need to check object identity, not value equality. The most common and recommended use is:
# CORRECT: Comparing with None (singleton)
value = get_some_value()
if value is None:
print("No value found")
# CORRECT: Checking if two variables refer to the same mutable object
list1 = [1, 2, 3]
list2 = list1 # list2 references the same object
if list1 is list2:
print("They are the same list!")
# CORRECT: Comparing with True/False (though not necessary)
flag = some_function()
if flag is True: # Works but redundant - just use 'if flag:'
print("True")
Best Practices:
== for value comparison – strings, numbers, and most data types.is for singleton comparisons – None, True, False (though if value is usually sufficient for booleans).is to check if two variables reference the same mutable object (when you intentionally want to check identity).is for strings and numbers in production code unless you have a specific reason to check identity.Summary Example:
# WRONG - produces inconsistent results
x = input("Enter a number: ") # User enters "100"
y = "100"
print(x is y) # False (different objects)
# CORRECT - always reliable
print(x == y) # True (same value)
# CORRECT use of 'is'
result = get_data()
if result is None: # Standard Python practice
print("No data available")
Question 5 (Build a Login Validator):
Write a complete Python program that:
correct_password = "python123".max_attempts = 3.max_attempts.in operator to check if the password contains at least one digit (hint: loop or check string methods)."""
LOGIN VALIDATOR PROGRAM
Demonstrates logical operators, input validation, and string methods
"""
# --- Configuration ---
correct_password = "python123"
max_attempts = 3
print("=" * 50)
print("LOGIN VALIDATOR")
print("=" * 50)
# --- Get user input ---
# Input 1: The password
entered_password = input("\nEnter your password: ")
# Input 2: Number of attempts they want to use
try:
attempted = int(input("How many attempts are you using? (1-3): "))
except ValueError:
print("Invalid input! Please enter a number.")
attempted = 0
print("\n" + "-" * 50)
print("VALIDATION RESULTS")
print("-" * 50)
# --- Part 4: Using logical operators to check conditions ---
# Condition 1: Password matches
password_match = (entered_password == correct_password)
# Condition 2: Attempts are within limit
attempts_valid = (attempted <= max_attempts)
# Condition 3: Both conditions true (login successful)
login_successful = password_match and attempts_valid
# Display results using logical operators
print(f"\nPassword correct: {password_match}")
print(f"Attempts valid (<= {max_attempts}): {attempts_valid}")
print(f"Login successful: {login_successful}")
# Part 4 (Bonus): Print appropriate message
if login_successful:
print("\n✅ Login successful!")
else:
print("\n❌ Login failed.")
# Provide helpful feedback using logical operators
if not password_match and not attempts_valid:
print(" - Incorrect password AND attempts exceeded limit.")
elif not password_match:
print(" - Incorrect password.")
else:
print(" - You have exceeded the maximum number of attempts.")
# --- Part 5: Advanced - Check if password contains at least one digit ---
print("\n" + "=" * 50)
print("ADVANCED PASSWORD VALIDATION")
print("=" * 50)
# Method 1: Using a loop and the 'in' operator with a string of digits
has_digit = False
for char in entered_password:
if char in "0123456789":
has_digit = True
break
print(f"\nPassword: '{entered_password}'")
print(f"Contains at least one digit (loop method): {has_digit}")
# Method 2: Using string methods (cleaner approach)
has_digit_method = any(char.isdigit() for char in entered_password)
print(f"Contains at least one digit (any() + isdigit()): {has_digit_method}")
# Method 3: Using regular expressions (commented out - beyond scope)
# import re
# has_digit_regex = bool(re.search(r'\d', entered_password))
# print(f"Contains digit (regex): {has_digit_regex}")
# --- Additional Password Strength Checks ---
print("\n" + "-" * 50)
print("PASSWORD STRENGTH ANALYSIS")
print("-" * 50)
# Check various conditions using logical operators and 'in'
password = entered_password
# Check length (using comparison operators)
is_long_enough = len(password) >= 8
print(f"Length >= 8: {is_long_enough}")
# Check for uppercase letter
has_upper = any(c.isupper() for c in password)
print(f"Has uppercase: {has_upper}")
# Check for lowercase letter
has_lower = any(c.islower() for c in password)
print(f"Has lowercase: {has_lower}")
# Check for special character (using 'in' with a set)
special_chars = "!@#$%^&*()_+-=[]{}|;:',.<>?/`~"
has_special = any(c in special_chars for c in password)
print(f"Has special character: {has_special}")
# Overall password strength using logical operators
is_strong = (is_long_enough and has_digit and has_upper and has_lower and has_special)
print(f"\nStrong password: {is_strong}")
if is_strong:
print("✅ Your password is strong!")
else:
print("⚠️ Consider improving your password:")
if not is_long_enough:
print(" - Password should be at least 8 characters long.")
if not has_digit:
print(" - Include at least one digit.")
if not has_upper:
print(" - Include at least one uppercase letter.")
if not has_lower:
print(" - Include at least one lowercase letter.")
if not has_special:
print(" - Include at least one special character (!@#$% etc.)")
# --- Bonus: Validation loop for multiple attempts ---
print("\n" + "=" * 50)
print("INTERACTIVE LOGIN SIMULATION")
print("=" * 50)
def interactive_login():
"""Simulates an interactive login with multiple attempts"""
max_attempts = 3
attempts_used = 0
correct_pw = "python123"
print(f"\nYou have {max_attempts} attempts to log in.")
while attempts_used < max_attempts:
password = input(f"\nAttempt {attempts_used + 1}/{max_attempts} - Enter password: ")
attempts_used += 1
# Using logical operators for validation
if password == correct_pw and attempts_used <= max_attempts:
print("✅ Login successful!")
return True
else:
remaining = max_attempts - attempts_used
print(f"❌ Incorrect password. {remaining} attempts remaining.")
print("\n❌ Login failed. Maximum attempts reached.")
return False
# Uncomment to run interactive login
# interactive_login()
print("\n" + "=" * 50)
print("SUMMARY OF OPERATORS USED:")
print("=" * 50)
print(" • '==' - Value equality (password comparison)")
print(" • '<=' - Comparison (attempts limit)")
print(" • 'and' - Logical AND (both conditions must be true)")
print(" • 'not' - Logical NOT (negating conditions for feedback)")
print(" • 'in' - Membership (checking for digits in password)")
print(" • 'any' - Built-in function with generator expression")
print(" • 'is' - Not used (we use '==' for string comparison)")
print("=" * 50)
Sample Output (Successful Login):
==================================================
LOGIN VALIDATOR
==================================================
Enter your password: python123
How many attempts are you using? (1-3): 2
--------------------------------------------------
VALIDATION RESULTS
--------------------------------------------------
Password correct: True
Attempts valid (<= 3): True
Login successful: True
✅ Login successful!
==================================================
ADVANCED PASSWORD VALIDATION
==================================================
Password: 'python123'
Contains at least one digit (loop method): True
Contains at least one digit (any() + isdigit()): True
--------------------------------------------------
PASSWORD STRENGTH ANALYSIS
--------------------------------------------------
Length >= 8: True
Has uppercase: False
Has lowercase: True
Has special character: False
Strong password: False
⚠️ Consider improving your password:
- Include at least one uppercase letter.
- Include at least one special character (!@#$% etc.)
==================================================
INTERACTIVE LOGIN SIMULATION
==================================================
You have 3 attempts to log in.
Attempt 1/3 - Enter password: wrong
❌ Incorrect password. 2 attempts remaining.
Attempt 2/3 - Enter password: python123
✅ Login successful!
==================================================
SUMMARY OF OPERATORS USED:
==================================================
• '==' - Value equality (password comparison)
• '<=' - Comparison (attempts limit)
• 'and' - Logical AND (both conditions must be true)
• 'not' - Logical NOT (negating conditions for feedback)
• 'in' - Membership (checking for digits in password)
• 'any' - Built-in function with generator expression
• 'is' - Not used (we use '==' for string comparison)
==================================================
Sample Output (Failed Login):
==================================================
LOGIN VALIDATOR
==================================================
Enter your password: wrongpassword
How many attempts are you using? (1-3): 3
--------------------------------------------------
VALIDATION RESULTS
--------------------------------------------------
Password correct: False
Attempts valid (<= 3): True
Login successful: False
❌ Login failed.
- Incorrect password.
==================================================
ADVANCED PASSWORD VALIDATION
==================================================
Password: 'wrongpassword'
Contains at least one digit (loop method): False
Contains at least one digit (any() + isdigit()): False
--------------------------------------------------
PASSWORD STRENGTH ANALYSIS
--------------------------------------------------
Length >= 8: True
Has uppercase: False
Has lowercase: True
Has special character: False
Strong password: False
⚠️ Consider improving your password:
- Include at least one digit.
- Include at least one uppercase letter.
- Include at least one special character (!@#$% etc.)
Explanation of Key Concepts:
1. Logical Operators in Action:
password_match and attempts_valid – Both must be true for login success.if not password_match and not attempts_valid – Provides specific feedback.2. in Operator for Password Validation:
char in "0123456789" – Checks if a character is a digit.any(c.isdigit() for c in password) – More Pythonic way to check for any digit.3. String Methods for Validation:
.isdigit() – Checks if all characters are digits..isupper() / .islower() – Check case..isalnum() – Checks if alphanumeric.4. Password Strength Analysis: The program demonstrates how to combine multiple conditions using logical operators to create a comprehensive validation system.
Key Takeaways:
== for string comparison – Not is (as explained in the previous question).and and or – For complex validation logic.in for membership tests – Checking if a character belongs to a set.any() with generator expressions – For checking any character in a string.