Lab 1: The Shapeshifter (Dynamic Reassignment) Observe how the ID changes when the Type changes.
var = 100
print(f"Value: {var}, Type: {type(var)}, ID: {id(var)}")
var = "DevOps"
print(f"Value: {var}, Type: {type(var)}, ID: {id(var)}")
# Notice the ID is totally different. It's a new object.
Lab 2: The “Type Guard” (Architect Pattern) How to safely handle dynamic types in a function.
def process_data(input_data):
if isinstance(input_data, list):
print(f"Processing list with {len(input_data)} items.")
elif isinstance(input_data, str):
print(f"Processing string: {input_data.upper()}")
else:
print("Error: Unsupported type!")
process_data(["Server1", "Server2"]) # Handles List
process_data("localhost") # Handles String
process_data(12345) # Handles Error safelyLab 3: The “Duck Typing” Logger (Polymorphism)
Scenario: You are writing a logging function for your cloud infrastructure. You want this function to write logs to any destination—a physical file, a terminal, or a network stream—without changing the code.
- Concept: Duck Typing. Python doesn’t care if the object is officially a “File”. It only asks: “Does this object have a
.write()method?” If yes, it works.
class FakeNetworkStream:
def write(self, message):
print(f"[Network Packet Sent]: {message}")
def secure_logger(output_device, message):
"""
I don't care what 'output_device' is.
I only care if I can .write() to it.
"""
output_device.write(f"LOG: {message}\n")
# 1. Use a Real File (Standard)
with open("system.log", "w") as f:
secure_logger(f, "System started.")
# 2. Use the Terminal (Standard Output)
import sys
secure_logger(sys.stdout, "System running...")
# 3. Use a Custom Class (Duck Typing)
network = FakeNetworkStream()
secure_logger(network, "System alert!")
Output:
system.logfile is created.- Terminal shows:
LOG: System running... - Terminal shows:
[Network Packet Sent]: LOG: System alert! - Architect Insight: This makes Python incredibly flexible for testing. You can swap a real AWS S3 uploader with a “Fake” uploader class during unit tests easily.
Lab 4: The “Mixed List” Crash (Handling Dynamic Data)
Scenario: You scraped a website for prices, but the data is messy. Some are numbers (100), some are strings ("200"), and some are missing (None). You need to calculate the total cost.
- The Trap: A simple loop will crash with a
TypeError. - The Fix: Handle types dynamically inside the loop.
raw_prices = [100, "200", None, "50", 400, "Free"]
total_cost = 0
for item in raw_prices:
# Check the type before acting
if isinstance(item, int):
total_cost += item
elif isinstance(item, str):
# Try to convert string to int
if item.isdigit():
total_cost += int(item)
else:
print(f"Skipping text: '{item}'")
elif item is None:
print("Skipping missing data...")
print(f"Total Cost: ${total_cost}")
Output:
Skipping missing data...
Skipping text: 'Free'
Total Cost: $750
Lab 5: The “API Response” Switcher
Scenario: You are querying a Cloud API.
- If you search for one server, it returns a Dictionary.
- If you search for all servers, it returns a List.
- Challenge: Your script must handle both return types dynamically to avoid crashing.
def process_api_response(response):
# Dynamic Type Checking
if isinstance(response, dict):
# Handle Single Object
print(f"Processing Single Server: {response.get('id')}")
elif isinstance(response, list):
# Handle List of Objects
print(f"Processing Batch of {len(response)} Servers:")
for server in response:
print(f" - {server.get('id')}")
else:
print("Error: Unknown API format.")
# Simulating API Calls
api_single = {"id": "i-12345", "state": "running"}
api_batch = [{"id": "i-67890"}, {"id": "i-11223"}]
process_api_response(api_single) # Works
process_api_response(api_batch) # Works too!
- Architect Insight: This pattern is everywhere in AWS Boto3 scripts. Always check
isinstance()before iterating!
Lab 6: Type Hinting (The “Modern” Python)
Scenario: You are working on a large team. You write a function to calculate server uptime. You want to tell your teammates explicitly that start_time must be an integer (timestamp), not a string.
# The syntax: variable: type
# The arrow -> type: indicates return type
def calculate_uptime(start_time: int, current_time: int) -> int:
return current_time - start_time
# Correct Usage
uptime = calculate_uptime(1600000000, 1600003600)
print(f"Uptime: {uptime} seconds")
# Incorrect Usage (Python runs it, but IDEs like VS Code will underline it in RED)
# This helps catch bugs before you even run the code!
# crash = calculate_uptime("yesterday", "today")
Lab 7: The id() Reassignment Proof
Scenario: Visually proving that “Changing a variable” is actually “Moving a tag to a new box.”
# Tag 'x' is on Box 50
x = 50
addr1 = id(x)
print(f"x is {x} at address {addr1}")
# Move Tag 'x' to Box 60
x = 60
addr2 = id(x)
print(f"x is {x} at address {addr2}")
# Check if they are the same spot in memory
if addr1 != addr2:
print("Proof: The variable moved to a completely new memory location!")