Python Mutable vs. Immutable Types Lab

Lab 1: The “Memory Address” Detective Prove that Strings are immutable.

text = "DevOps"
print(f"Original ID: {id(text)}")

text = text + " Guru"
print(f"New ID:      {id(text)}") 
# Result: IDs are different. Python created a new string object.

Lab 2: The “Tuple Trap” (The Loophole) Try to hack a tuple.

# A Tuple containing a List
secure_config = ("127.0.0.1", [80, 443])

# secure_config[0] = "10.0.0.1" # Error: Tuple allows no change.

# BUT, we can change the list inside!
secure_config[1].append(8080)
print(secure_config) 
# Output: ('127.0.0.1', [80, 443, 8080]) -> Modified!

Lab 3: Safe Type Casting Convert User Input (String) to Integer safely.

user_input = " 500 " # Note the spaces

try:
    # 1. Strip spaces, 2. Convert
    value = int(user_input.strip())
    print(f"Processing amount: {value}")
except ValueError:
    print("Error: Please enter a valid number.")

Lab 4: The “Performance Bottleneck” (String Concatenation)

Scenario: You are writing a script to generate a massive report string from thousands of log lines.

  • The “Bad” Way: Using += on a string. Since strings are Immutable, Python has to destroy and recreate the entire string variable for every single line. This kills the CPU.
  • The “Architect” Way: Use a List (Mutable) to collect parts, then join them once.
import time

# Simulation: 100,000 log lines
log_lines = ["Error: Connection timeout" for _ in range(100000)]

# --- METHOD 1: The Slow Way (Immutable Concatenation) ---
start_time = time.time()
report = ""
for line in log_lines:
    report += line + "\n"  # DANGER: Creates a NEW object 100,000 times!
print(f"Bad Method took: {time.time() - start_time:.4f} seconds")

# --- METHOD 2: The Fast Way (Mutable List Buffer) ---
start_time = time.time()
buffer_list = []
for line in log_lines:
    buffer_list.append(line) # Fast! Modifies list in-place.
final_report = "\n".join(buffer_list) # Creates string ONCE at the end.
print(f"Architect Method took: {time.time() - start_time:.4f} seconds")

Expected Output: You will see the “Architect Method” is significantly faster (often 100x faster for large datasets).


Lab 5: The “Unhashable Key” Crash

Scenario: You want to create a dictionary that maps a specific set of Open Ports (e.g., [80, 443]) to a Security Group Name.

  • The Rule: Dictionary keys must be Immutable (Hashable).
  • The Error: Using a List as a key triggers a TypeError.
# Goal: Map a list of ports to a Profile Name
# key: [80, 443] -> value: "Web-Server-Profile"

try:
    # ATTEMPT 1: Using a List (Mutable) as a Key
    port_list = [80, 443]
    security_map = {
        port_list: "Web-Server-Profile" 
    }
except TypeError as e:
    print(f"CRASH: {e}")
    # Output: unhashable type: 'list'

# ATTEMPT 2: The Fix - Cast to Tuple (Immutable)
port_tuple = tuple([80, 443]) # Explicit Casting
security_map = {
    port_tuple: "Web-Server-Profile"
}
print(f"Success! Profile mapped: {security_map[port_tuple]}")
  • Architect Insight: This happens constantly when parsing JSON. If you need to use a complex object as a unique identifier (key), always convert it to a tuple or frozenset first.

Lab 6: The “Input Sanitizer” (Casting & Cleaning)

Scenario: You are reading data from a chaotic CSV file where numbers are written as strings like " 500ms " or "1,000". If you cast directly, it crashes.

raw_data = [" 500 ", "1,000", "N/A", "200"]

cleaned_values = []

for entry in raw_data:
    try:
        # Step 1: Clean the string (Remove spaces, remove commas)
        clean_str = entry.strip().replace(",", "")
        
        # Step 2: Explicit Cast to Integer
        val = int(clean_str)
        cleaned_values.append(val)
        
    except ValueError:
        # Handle cases like "N/A" gracefully
        print(f"Skipping invalid data: '{entry}'")

print(f"Cleaned Data: {cleaned_values}")
# Output: [500, 1000, 200]
  • Architect Insight: Explicit casting (int()) is strict. It does not guess. You must “sanitize” the string (remove commas, spaces) before asking Python to convert it.

Lab 7: The “Tuple Backdoor” (Nested Mutability)

Scenario: You defined an “Immutable” configuration tuple to protect your server list. But a hacker (or a buggy function) modified the data anyway.

  • Concept: Immutability is shallow. If a tuple holds a list, the list can still change.
# A tuple representing (Datacenter_Name, [Server_List])
dc_config = ("US-East-1", ["Server A", "Server B"])

print(f"Original Config: {dc_config}")

# 1. Try to change the Datacenter Name (Immutable String)
try:
    dc_config[0] = "US-West-2"
except TypeError:
    print("Blocked: Cannot change Datacenter Name.")

# 2. Try to add a server to the list (Mutable List inside Tuple)
dc_config[1].append("Malicious-Server-X") 

print(f"Hacked Config:   {dc_config}")
# Output: The list inside is now modified!
  • Architect Fix: If you want total immutability, the inner list must also be a tuple: ("US-East-1", ("Server A", "Server B")).

Lab 8: The “Boolean Truth” Trap

Scenario: A deployment script reads ENABLE_DELETION = "False" from a text file. The script accidentally deletes the database because it thought “False” was True.

config_value = "False"

# BAD PRACTICE (Implicit Casting)
# Any non-empty string is evaluated as True in a boolean context
if config_value:
    print("DANGER: Deletion Enabled! (The string is not empty)")

# ALSO BAD (Explicit Casting)
# bool("False") is True because the string has characters in it
if bool(config_value):
    print("DANGER: bool('False') is actually True!")

# CORRECT PRACTICE (Comparison)
if config_value.lower() == "true":
    print("Deletion Enabled.")
else:
    print("SAFE: Deletion Disabled.")

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top