Previous | Tutorial index | Next

Tutorial 3: A Detailed Guide to Python Operators

Learning Objective

Use the proper operators for different types of data and data models.

1. Introduction

1.1 Arithmetic Operators – More Than Just Math

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).

Beyond Numbers – Lexicographical Comparison: Strings are compared alphabetically (lexicographically) based on Unicode code points. Shorter strings are considered smaller if they are a prefix.

Chaining Comparisons (Python's Elegant Feature): You can chain comparison operators. This is both valid and readable.

1.3 Logical (Boolean) Operators – Combining Truths

Used to combine conditional expressions. They evaluate short-circuitly.

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.

1.4 Membership Operators – Searching in Collections

1.5 Identity Operators – Comparing Memory Addresses

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 ==.

2. Code Examples (Annotated)

# --- 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)

3. Quiz (Check Your Understanding)

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

Answer b) `2.5`, `float` – `/` always returns a float.

Question 2: What is the output of -10 // 3? a) -3 b) -4 c) 3 d) -3.33

Answer b) `-4` – floor division rounds down to the nearest integer (towards negative infinity).

Question 3: What is the output of "abc" < "abcd"? a) True b) False c) TypeError

Answer a) `True` – lexicographically, "abc" is a prefix of "abcd", so it is considered smaller.

Question 4: In the expression x = 5; print(2 < x < 8), what is the output? a) True b) False

Answer a) `True` – chained comparison: `2 < 5and5<8`→bothtrue.

Question 5: Given a = 0; b = 5, what does a and b return? a) True b) False c) 0 d) 5

Answer c) `0` – `and` returns the first falsey value (0) without evaluating the right side.

Question 6: Given data = {"id": 1, "name": "John"}, what does "John" in data return? a) True b) False

Answer b) `False` – `in` on a dictionary checks **keys**, not values.

Question 7: What is the difference between a == b and a is b?

Answer `==` checks value equality; `is` checks object identity (whether both variables refer to the exact same object in memory).

Question 8: Why does 257 is 257 sometimes return False in an interactive shell, while 256 is 256 returns True?

Answer Python caches small integers (‑5 to 256) for performance, so `256` refers to the same object. Integers outside that range are not cached, so each literal creates a new object, making `is` return `False`.

Question 9: What is the value of 2 ** 2 ** 3? a) 64 b) 256 c) 512

Answer c) `512` – `**` is right‑associative: `2 ** (2 ** 3) = 2 ** 8 = 256`.

Question 10: What does the expression 5 != 5 or 10 > 2 evaluate to? a) True b) False

Answer a) `True` – `5 != 5` is `False`, but `10 > 2` is `True`, so the whole `or` is `True`.

4. Exercises (In-Class / Lab Practice)

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.

Sample Solution ```python a = float(input("Enter first number: ")) b = float(input("Enter second number: ")) print(f"{a} + {b} = {a + b}") print(f"{a} - {b} = {a - b}") print(f"{a} * {b} = {a * b}") print(f"{a} / {b} = {a / b}") print(f"{a} // {b} = {a // b}") print(f"{a} % {b} = {a % b}") print(f"{a} ** {b} = {a ** b}") ```

Exercise 2: Logical Condition Composer Given age = 22, has_permit = True, is_insured = False, write a single expression that evaluates to True if:

  1. The person is at least 18 AND has a permit.
  2. The person is under 21 OR is insured.
  3. The person is NOT (under 18 AND not insured).
Sample Solution 1. `age >= 18 and has_permit` 2. `age < 21oris_insured`3.`not(age<18andnotis_insured)`

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:

Sample Solution ```python print("programming" in text) # True print("grape" in fruits) # False print("active" in user) # True (key exists) print(True in user.values()) # True (value is True) ```

Exercise 4: Identity vs Equality (The Copy Trap) Write a script to demonstrate the alias problem:

  1. Create list1 = [1, 2, 3].
  2. Create list2 = list1.
  3. Append 4 to list2.
  4. Print both list1 and list2 to show they are the same.
  5. Now create a true copy: list3 = list1[:].
  6. Append 5 to list3.
  7. Print list1 and list3 to show they are independent.
  8. Use is to compare list1 and list2, and list1 and list3 to confirm.
Sample Answer
# --- 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:

  1. list2 = list1 creates an alias – both variables point to the same list object.
  2. list1 is list2 is True – they share the same identity.
  3. Modifying one affects the other – appending to list2 also modifies list1.
  4. list3 = list1[:] creates a true copy – a new independent list object.
  5. list1 is list3 is False – they have different identities.
  6. == 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).

Sample Answer
# --- 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:

  1. and operator:

    • If the left operand is 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.
  2. or operator:

    • If the left operand is True, Python doesn't evaluate the right operand.
    • False or "Cannot divide by zero." → If the division fails, the or returns the fallback string.
  3. Combined approach:

    • (den != 0 and num / den) or "Cannot divide by zero."
    • If den != 0: evaluates to (True and result) which is result, then (result or "...") returns result.
    • If 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:

5. Homework Questions (Deep Thinking)

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.

Sample Answer ```python s1 = input("Enter first string: ") s2 = input("Enter second string: ") if s1 > s2: print(f"{s1} is larger") elif s1 < s2:print(f"{s2}islarger")else:print("Equal")```With"apple"vs"Apple","apple"islargerbecauselowercaselettershavehigherUnicodecodepointsthanuppercaseletters.Thiscanbesurprising,soweshouldnotethatlexicographicorderfollowsUnicodeorder,notdictionaryorder.

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.

Sample Answer No, it does not crash. `or` short‑circuits: since `a` is `0` (falsey), it evaluates the right side `b / 0`, which would normally raise `ZeroDivisionError`. However, because `0` is falsey, Python must evaluate the right side to determine the result. Actually, wait: `a` is 0, which is falsey, so `or` will evaluate the right operand. That means `b / 0` will be evaluated, causing a `ZeroDivisionError`. So it **does crash**. Correction: short‑circuiting only works when the left operand is truthy for `or`; for `and`, if left is falsey. Here, left is falsey, so right is evaluated. So the code crashes.

Question 3 (Modulo Math Puzzle): You are building a digital clock. Given total_minutes = 135, calculate:

Sample Answer ```python total_minutes = 135 hours = total_minutes // 60 minutes = total_minutes % 60 print(f"{hours} hours and {minutes} minutes") ```

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?

Sample Answer

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:

  1. 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.

  2. 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.

  3. 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:

  1. Always use == for value comparison – strings, numbers, and most data types.
  2. Use is for singleton comparisonsNone, True, False (though if value is usually sufficient for booleans).
  3. Use is to check if two variables reference the same mutable object (when you intentionally want to check identity).
  4. Avoid 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:

  1. Defines a variable correct_password = "python123".
  2. Defines a variable max_attempts = 3.
  3. Asks the user to input a password and a number of attempts they want to use (e.g., 1, 2, or 3).
  4. Uses logical operators to check if:
  5. Advanced: Use the in operator to check if the password contains at least one digit (hint: loop or check string methods).
Sample Answer
""" 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:

2. in Operator for Password Validation:

3. String Methods for Validation:

4. Password Strength Analysis: The program demonstrates how to combine multiple conditions using logical operators to create a comprehensive validation system.

Key Takeaways:

  1. Use == for string comparison – Not is (as explained in the previous question).
  2. Combine conditions with and and or – For complex validation logic.
  3. Use in for membership tests – Checking if a character belongs to a set.
  4. Use any() with generator expressions – For checking any character in a string.
  5. Provide specific error messages – Helps users understand what went wrong.

6. Summary Checklist (For Student Self-Review)

Previous | Tutorial index | Next