Of course! Let's break down locals() and vars() in Python. They are very similar built-in functions, but understanding their nuances is key to using them effectively.

The Short Answer (TL;DR)
vars(): If you pass it an object, it returns the__dict__attribute for that object. If you call it with no arguments, it's an alias forlocals().locals(): Returns a dictionary representing the current local symbol table. This dictionary is a live reference, meaning changes to it can affect your code's execution.
In-Depth Explanation
Both locals() and vars() return a dictionary that maps variable names (as strings) to their corresponding values in a specific scope.
locals(): The Local Symbol Table
locals() gives you access to the variables in the local scope. The "local scope" is the current block of code you are in. This could be a function, a method, or the main module (the global scope, technically).
Key Characteristic: locals() returns a live dictionary.
This is the most important and potentially dangerous feature of locals(). You can modify the dictionary, and those modifications will be reflected in your code's execution.

Example 1: locals() in a Function
def my_function(name, age):
# Inside the function, 'name' and 'age' are local variables
local_vars = locals()
print("Inside function, before modification:")
print(local_vars)
# Output: {'name': 'Alice', 'age': 30}
# Now, let's MODIFY the live dictionary
local_vars['city'] = 'New York'
# This is equivalent to creating a new local variable `city`
print("\nInside function, after modification:")
print(local_vars)
# Output: {'name': 'Alice', 'age': 30, 'city': 'New York'}
# The variable 'city' now exists in the function's scope
print(f"City is: {city}")
# Call the function
my_function('Alice', 30)
Example 2: locals() in the Global Scope
In the main module, the local scope is the same as the global scope.
global_var = "I am global"
def show_globals():
# Inside a function, locals() does NOT see global variables by default
# unless they are accessed via the global keyword.
print("Inside function:", locals()) # Output: {}
show_globals()
print("In main module:", locals())
# Output (will vary slightly, but will include 'global_var' and 'show_globals'):
# {'__name__': '__main__', '__doc__': None, ... , 'global_var': 'I am global', 'show_globals': <function show_globals at 0x...>}
vars(): The Flexible Object Inspector
vars() is more flexible. Its behavior depends on whether you pass it an argument.
Case A: vars(object)
If you pass an object to vars(), it attempts to return that object's __dict__ attribute. The __dict__ is the dictionary that stores an object's instance attributes.
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("Bob", 42)
# vars(p) is equivalent to p.__dict__
person_attributes = vars(p)
print(person_attributes)
# Output: {'name': 'Bob', 'age': 42}
# You can modify the object's attributes by modifying its __dict__
person_attributes['city'] = 'London'
print(p.city) # Output: London
Case B: vars() (no arguments)
If you call vars() with no arguments, it behaves exactly like locals().

def another_function(x):
y = x * 2
# These two lines are identical
print("Using locals():", locals())
print("Using vars(): ", vars())
another_function(10)
Output:
Using locals(): {'x': 10, 'y': 20}
Using vars(): {'x': 10, 'y': 20}
Key Differences & Summary
| Feature | locals() |
vars(object) |
vars() (no args) |
|---|---|---|---|
| Purpose | Access the local symbol table. | Access the __dict__ of a specific object. |
Alias for locals(). |
| Arguments | None. | Exactly one object. | None. |
| Returns | A live dictionary of local variables. | A dictionary of the object's attributes. | A live dictionary of local variables. |
| Mutability | Live: Changes affect the local scope. | Live: Changes affect the object's attributes. | Live: Changes affect the local scope. |
| Typical Use Case | Debugging, dynamically creating local variables. | Inspecting object attributes, serialization. | Debugging, same as locals(). |
Common Use Cases & Best Practices
Debugging
This is the most common and safest use case. When you're trying to figure out what variables are available in a function or block, locals() is your best friend.
def complex_calculation(a, b, c):
# ... many lines of complex code ...
intermediate_result = a * b + c
# Oops, what's in scope here? Let's check.
# print(locals()) # Uncomment to see all local variables
return intermediate_result / (a + b)
# Using a debugger like `pdb` is often better, but `locals()` is a quick check.
Dynamic Variable Creation (Use with Caution!)
Because locals() is live, you can use it to create variables dynamically. This is generally considered bad practice because it makes code hard to read, understand, and maintain.
# --- ANTI-PATTERN: Avoid this kind of code ---
data = {'user_id': 123, 'status': 'active'}
def process_data(data_dict):
# Dynamically create variables from a dictionary
for key, value in data_dict.items():
locals()[key] = value # This creates `user_id` and `status` variables
# Now you can use them, but it's not clear where they came from
print(f"Processing user ID: {user_id}")
print(f"Status is: {status}")
process_data(data)
A much better, more explicit, and Pythonic way to achieve this is with keyword arguments (**kwargs):
# --- PYTHONIC PATTERN: Do this instead ---
data = {'user_id': 123, 'status': 'active'}
def process_data_better(**kwargs): # The **kwargs collects all keyword arguments
# The variables are now explicitly available in kwargs
print(f"Processing user ID: {kwargs['user_id']}")
print(f"Status is: {kwargs['status']}")
# You can also loop through them
for key, value in kwargs.items():
print(f" - {key}: {value}")
process_data_better(**data) # The ** unpacks the dictionary into keyword arguments
Serialization (Pickling Objects)
vars() is very useful for converting an object's state into a dictionary, which you can then easily save to a file (JSON, pickle, etc.).
import json
class Config:
def __init__(self):
self.host = 'localhost'
self.port = 8080
self.debug = True
config = Config()
# Convert the object to a dictionary
config_dict = vars(config)
# Save the dictionary to a JSON file
with open('config.json', 'w') as f:
json.dump(config_dict, f, indent=4)
# Check the file content:
# {
# "host": "localhost",
# "port": 8080,
# "debug": true
# }
Final Recommendation
- For debugging, both
locals()andvars()(with no args) are excellent tools. - For inspecting an object's attributes, always use
vars(my_object). - Avoid using
locals()to dynamically create variables in production code. It's clever, but it harms readability and maintainability. Use explicit variable assignment or**kwargsinstead.
