Skip to content

globals() and locals() Functions Complexity

The globals() and locals() functions return dictionary representations of the current global and local namespaces, providing access to all defined variables in those scopes.

Complexity Reference

Operation Time Space Notes
globals() O(1) O(1) Returns reference to existing global dict
locals() O(1) or O(m) O(1) or O(m) Module/class scope is O(1); optimized function scopes materialize locals mapping
Accessing dict value O(1) avg O(1) Dict key lookup; O(n) worst case with collisions

Understanding Namespaces

Global Namespace

# Access global namespace - O(1)
x = 10
y = 20

global_vars = globals()  # O(1) - returns reference to existing dict
print(global_vars['x'])  # O(1) - access value
print(global_vars['y'])  # O(1)

# globals() includes built-ins and module vars
print('print' in globals())  # Usually True (if imported)

Local Namespace

# Access local namespace - O(m)
def my_func():
    a = 1
    b = 2

    local_vars = locals()  # O(m) - creates dict
    print(local_vars['a'])  # O(1)
    print(local_vars['b'])  # O(1)

    return local_vars

result = my_func()  # O(m)

Common Patterns

Inspecting Variables

# List all global variables - O(n)
x = 10
y = 20
z = 30

all_vars = globals()  # O(1)

# Filter variables (not functions/modules) - O(n)
my_vars = {k: v for k, v in all_vars.items() 
           if not k.startswith('_')}

print(my_vars)  # {'x': 10, 'y': 20, 'z': 30}

Dynamic Variable Access

# Access variable by name - O(1)
var_name = 'x'
x = 42

# Using globals()
value = globals()[var_name]  # O(1) - get value

# Using getattr for objects
class Config:
    timeout = 30
    retries = 3

config = Config()
attr_name = 'timeout'
value = getattr(config, attr_name)  # O(1)

Getting Caller's Locals

import inspect

def get_caller_locals():
    """Get local variables of calling function"""
    # Get calling frame
    frame = inspect.currentframe().f_back  # O(1)

    # Access local variables - O(m)
    caller_locals = frame.f_locals  # O(m)

    return caller_locals

def caller():
    x = 10
    y = 20

    caller_vars = get_caller_locals()  # O(m)
    print(caller_vars)  # {'x': 10, 'y': 20}

caller()

Function Scope

Module Level

# At module level, locals() == globals() - O(1)
X = 100

print(locals() is globals())  # True at module level
print(locals()['X'])  # O(1)

Inside Functions

# Inside function, locals() is different - O(m)
global_x = 10

def func():
    local_y = 20

    # Different namespaces
    print('local_y' in locals())   # True - O(1)
    print('local_y' in globals())  # False - O(1)
    print('global_x' in locals())  # False - O(1)
    print('global_x' in globals()) # True - O(1)

func()

Modifying Namespaces

Setting Global Variables

# Modify global namespace - O(1)
globals()['new_var'] = 100  # O(1)

print(new_var)  # 100 - variable created!

# Better approach: use globals() sparingly
# Prefer explicit assignment
new_var2 = 200

Setting Local Variables

# Modifying locals() has limited effect
def func():
    locals()['x'] = 10  # O(1) - sets in dict

    try:
        print(x)  # NameError! - x not actually in local scope
    except NameError:
        print("x not in local variables")

func()

# Don't use locals() to set variables in function
# Use explicit assignment instead
def func2():
    x = 10  # Proper way
    print(x)  # Works

Practical Examples

Debug Function State

def debug_state(func_locals):
    """Print local variables with types"""
    print("Local variables:")

    for name, value in func_locals.items():  # O(m)
        if not name.startswith('_'):  # Skip special vars
            print(f"  {name}: {type(value).__name__} = {value}")

def example_func():
    x = 42
    s = "hello"
    lst = [1, 2, 3]

    debug_state(locals())  # O(m)

example_func()

Serialize Local Variables

import json

def save_state():
    """Save local variables to JSON"""
    local_data = locals()  # O(m)

    # Filter serializable objects - O(m)
    serializable = {}
    for k, v in local_data.items():
        if isinstance(v, (int, str, float, list, dict)):
            serializable[k] = v

    return json.dumps(serializable)

def task():
    count = 10
    name = "task"
    items = [1, 2, 3]

    state = save_state()  # O(m)
    return state

print(task())

Configuration Registry

# Use globals() as simple registry - O(1) per lookup/update
CONFIG = {}

def register_config(name, value):
    """Register configuration"""
    globals()[f'config_{name}'] = value  # O(1)

def get_config(name):
    """Get configuration"""
    return globals().get(f'config_{name}')  # O(1)

# Usage
register_config('timeout', 30)  # O(1)
register_config('retries', 3)   # O(1)

timeout = get_config('timeout')  # O(1)

Performance Considerations

Frequency of calls

# globals() returns same dict object - O(1) each call
for i in range(1000):
    d = globals()  # O(1) - returns same dict reference
    value = d['x']

# Caching is not necessary for globals(), but good for clarity
g = globals()  # O(1)
for i in range(1000):
    value = g['x']  # O(1) each

Large Namespaces

# globals() remains O(1) regardless of namespace size
# Iterating over the returned dict is O(n)
for i in range(10000):
    globals()[f'var_{i}'] = i  # O(1) each

# Still O(1): returns the same global dict object
g = globals()

# Iteration scales with number of entries - O(n)
names = [k for k in g if k.startswith("var_")]

# Prefer dict for custom data
my_data = {}
for i in range(10000):
    my_data[f'var_{i}'] = i  # O(1) each

Version Notes

  • Python 2.x: Same behavior
  • Python 3.x: Same behavior
  • All versions: globals() is O(1); locals() semantics are scope- and implementation-dependent
  • vars() - Similar to locals() but for objects
  • dir() - List names in namespace
  • inspect.signature() - Get function signature