Skip to main content

Python Global vs. Local Variables Lab

· 4 min read

Lab 1: The "Global" Keyword Switch Try to modify a configuration setting from inside a function.​

config_mode = "DEFAULT"

def switch_to_debug():
global config_mode # Unlock access to the global variable
config_mode = "DEBUG"
print("Inside function: Mode switched to", config_mode)

print("Before:", config_mode)
switch_to_debug()
print("After:", config_mode)
# Output: After: DEBUG (It permanently changed)

Lab 2: The "Shadowing" Experiment See how local variables protect themselves from the outside world.​

user = "Admin" # Global

def login_guest():
user = "Guest" # This is a NEW local variable. It does not touch the global 'user'.
print(f"Logged in as: {user}")

login_guest() # Output: Logged in as: Guest
print(f"Global User is still: {user}") # Output: Global User is still: Admin

Lab 3: The nonlocal Keyword (Nested Functions)​

Scenario: You are writing a "Retry Logic" wrapper for a shaky API connection. You need a counter that tracks retries, but this counter lives inside a helper function, not globally.

Concept: The nonlocal keyword allows a nested function to modify a variable in the outer function (not the global scope).

def connection_manager():
# Enclosing scope variable
attempts = 0

def try_connect():
nonlocal attempts # Unlock access to the outer variable
attempts += 1
print(f"Attempt #{attempts}: Connecting to AWS...")

return try_connect

# Create the specific connection instance
retry = connection_manager()

retry() # Output: Attempt #1: Connecting to AWS...
retry() # Output: Attempt #2: Connecting to AWS...
retry() # Output: Attempt #3: Connecting to AWS...
  • Architect Note: If we used global, it would mess up other connection managers running in parallel. nonlocal keeps the state isolated to this specific instance. This is how "Closures" work in Python.

Lab 4: The "Mutable Default Argument" Trap (Critical!)​

Scenario: You have a function to add a security tag to a list of servers. You set an empty list [] as the default value.

The Bug: Python creates default arguments once when the function is defined, not every time it runs. This leads to data leaking between function calls.

# BAD PRACTICE (Don't do this)
def register_server(name, server_list=[]):
server_list.append(name)
return server_list

# First call - looks fine
print(register_server("Web-01"))
# Output: ['Web-01']

# Second call - checking a totally different server...
# WAIT! Why is Web-01 still there?
print(register_server("DB-01"))
# Output: ['Web-01', 'DB-01'] -> Data Leak!

# CORRECT PRACTICE (Do this)
def register_server_safe(name, server_list=None):
if server_list is None:
server_list = [] # Create a fresh list every time
server_list.append(name)
return server_list

print(register_server_safe("Web-01")) # ['Web-01']
print(register_server_safe("DB-01")) # ['DB-01'] -> Clean!
  • Architect Note: This specific bug has caused major security incidents where one user sees another user's data. Always use None as a default for lists or dictionaries.

Lab 5: Scope in Loops (The "Leaky Variable")​

Scenario: You are iterating through a list of old log files to delete them. You print the last file name after the loop finishes.

Concept: In Python, variables defined inside a for loop leak out to the surrounding scope (unlike C++ or Java).

file_name = "System-Critical-Config.xml" # Important file

files_to_delete = ["temp1.log", "temp2.log", "error.log"]

for file_name in files_to_delete:
print(f"Deleting {file_name}...")

# The loop is finished. What is 'file_name' now?
print(f"Warning! The variable 'file_name' is now: {file_name}")
# Output: Warning! The variable 'file_name' is now: error.log
  • Architect Note: Be careful! The loop variable file_name overwrote your important file_name variable from the top. Always choose unique names for loop variables (e.g., for target_file in files_to_delete:).

Lab 6: The globals() and locals() Debugger​

Scenario: You are debugging a complex script and want to see everything that is currently alive in memory.

Concept: Python has built-in functions that return a dictionary of all current variables.

x = 100
y = "DevOps"

def debugger_tool():
z = 3.14
print("--- Local Variables (Inside Function) ---")
print(locals())

debugger_tool()

print("\n--- Global Variables (Selected) ---")
# globals() is huge, so let's just check for our specific variable
current_globals = globals()
if 'x' in current_globals:
print(f"Found Global x: {current_globals['x']}")

#Output
--- Local Variables (Inside Function) ---
{'z': 3.14}

--- Global Variables (Selected) ---
Found Global x: 100