Python NoneType Lab

Lab 1: The Singleton Proof

Task: Prove that two different variables assigned to None share the same memory address.

a = None
b = None
print(f"ID of a: {id(a)}")
print(f"ID of b: {id(b)}")
print(f"Are they the same? {a is b}") 
# Output: True (They are literally the same object)

Lab 2: The Void Function

Task: Create a function that prints “Hello” but returns nothing. Capture the result in a variable and print it.

def say_hello():
    print("Hello DevSecOps!")

result = say_hello()
print(f"The return value is: {result}") # Output: None
print(f"Type: {type(result)}")          # Output: <class 'NoneType'>

Lab 4: Type Hinting (Modern Python)

Task: Write a function signature that accepts a string or None, and returns an integer or None.

from typing import Optional

def process_id(user_id: Optional[str]) -> Optional[int]:
    if user_id is None:
        return None
    return int(user_id)

Lab 1: The Mutable Default Trap (Crucial for Architects)

Scenario: You want a function that adds a user to a list. If no list is provided, start a new one.

  • The Trap
def bad_add_user(name, user_list=[]):
    user_list.append(name)
    return user_list

print(bad_add_user("Alice")) # ['Alice']
print(bad_add_user("Bob"))   # ['Alice', 'Bob'] !! Wait, why is Alice here?

Reason: The default list [] is created once when Python starts. All calls share it.

  • The Master Solution
def good_add_user(name, user_list=None):
    if user_list is None:
        user_list = [] # Created FRESH every time
    user_list.append(name)
    return user_list

print(good_add_user("Alice")) # ['Alice']
print(good_add_user("Bob"))   # ['Bob'] (Correct!)

Lab 2: The Sentinel Pattern

Scenario: You need to check if a user passed a specific argument to a function, but None is a valid input (e.g., they might want to set a value to null).

  • Problem: If you use val=None as default, you can’t tell if the user passed None or if the function used the default.
  • Solution: Create a unique object instance
# Create a unique object in memory
MISSING = object()

def update_config(setting=MISSING):
    if setting is MISSING:
        print("No change requested.")
    elif setting is None:
        print("User explicitly deleted the setting (Set to None).")
    else:
        print(f"Setting updated to: {setting}")

update_config()       # Output: No change requested.
update_config(None)   # Output: User explicitly deleted...

— CONTENT START —

Advanced Practice Labs: Python NoneType Mastery

To achieve “Master” status with None, you must understand how it interacts with data structures, control flow, and external systems like APIs or Databases. These labs focus on the subtleties that often cause bugs in production.


Lab 3: The “Safe Fetch” Pattern (Intermediate)

Scenario: You are parsing a JSON configuration file website. Some optional fields (like “port” or “timeout”) might be missing.

  • The Trap: Accessing a missing key like config["timeout"] raises a KeyError and crashes the app.
  • The Task:
    1. Create a dictionary: config = {"host": "localhost", "retries": 0}.
    2. Try to get the “timeout” value safely. If it’s missing, default to 60.
    3. Try to get the “retries” value safely.
  • The Twist: If you use config.get("retries") or 5, it will erroneously return 5 because 0 is Falsy.
  • The Architect Solution: Explicitly check for None when 0 or False are valid values.

Python

config = {"host": "localhost", "retries": 0}

# 1. Standard safe fetch (Good for non-zero defaults)
timeout = config.get("timeout")
if timeout is None:
    timeout = 60

# 2. The "0" trap
# BAD: This will set retries to 5, because 0 is Falsy!
# retries = config.get("retries") or 5 

# GOOD: Explicit None check preserves the value 0
raw_retries = config.get("retries")
retries = raw_retries if raw_retries is not None else 5

print(f"Timeout: {timeout}, Retries: {retries}")

Lab 4: The Implicit Return Bug (Debugging)

Scenario: A junior developer wrote a function to process logs. The script crashes with TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'.

  • The Code:
def calculate_uptime(logs):
    # ... complex logic ...
    uptime = 100
    print(f"Uptime calculated: {uptime}")
    # Missing return statement!

result = calculate_uptime(["log1", "log2"])
total_score = result + 50  # CRASH HERE

  • The Concept: If a function finishes without a return statement, Python implicitly returns None.
  • The Task: Identify why result is None and fix the code by replacing print with return.

Lab 5: Sanitizing API Data (Advanced)

Scenario: You receive data from an external API where missing values are sent as null (which Python parses as None). You need to perform math on this data.

  • The Problem: You cannot add None + 10.
  • Task: Create a list of incoming values: data = [10, None, 20, None, 50].
    • Write a “List Comprehension” that filters out None values entirely.
    • Write a second version that replaces None with 0.
  • Code:
data = [10, None, 20, None, 50]

# Strategy 1: Filter (Remove invalid data)
clean_data = [x for x in data if x is not None]
print(f"Filtered: {clean_data}") # [10, 20, 50]

# Strategy 2: Sanitize (Replace missing with default)
# Uses ternary operator: (value IF condition ELSE default)
sanitized_data = [x if x is not None else 0 for x in data]
print(f"Sanitized: {sanitized_data}") # [10, 0, 20, 0, 50]

Lab 6: The “None” vs “False” Distinction (Architect Level)

Scenario: You are building a feature flag system.

  • True = Feature Enabled.
  • False = Feature Disabled.
  • None = Feature Not configured (Use System Default).

Task: Write logic that distinguishes between “Disabled” and “Not Configured”.

  • The Trap: if not feature_flag: evaluates to True for both False and None.
  • Code:
def check_feature(flag_value):
    if flag_value is None:
        print("Status: Using System Default")
    elif flag_value:
        print("Status: ENABLED")
    else:
        # Here, flag_value is False, but NOT None
        print("Status: DISABLED explicitly")

check_feature(True)   # ENABLED
check_feature(False)  # DISABLED explicitly
check_feature(None)   # Using System Default

Lab 7: Unpacking with None (Edge Case)

Scenario: A function returns a tuple (result, error). If successful, error is None. If it fails, result is None.

  • Task: Simulate a function api_call(success) that returns ("Data", None) or (None, "404 Error").
  • Logic: Unpack the result and handle the error flow.
  • Code:
def api_call(success):
    return ("Payload", None) if success else (None, "404 Not Found")

# Happy Path
data, error = api_call(True)
if error is None:
    print(f"Success: {data}")

# Error Path
data, error = api_call(False)
if error is not None:
    print(f"Failed: {error}")

Leave a Comment

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

Scroll to Top