Understanding the LEGB Rule for Variable Scope in Python
What is Scope? #
In Python, scope refers to the visibility and lifetime of variables. It determines where a variable can be accessed or modified within the code. The various scope levels in Python can be summarized using the LEGB acronym.
- Local scope: Variables defined within a function are local to that function and cannot be accessed from outside.
- Enclosing scope: Variables defined in the enclosing function (if any) can be accessed by nested functions.
- Global scope: Variables defined at the top level of a module or script are global and can be accessed from anywhere within that module.
- Built-in scope: Python has a set of built-in functions and variables that are always available.
Understanding scope is crucial for writing clean and maintainable code, as it helps prevent naming conflicts and unintended side effects.
Local Scope #
When you define a variable inside a function, it is considered to be in the local scope of that function. This means that the variable can only be accessed and modified within that function.
def my_function():
local_var = "I'm a local variable"
print(local_var) # This works
my_function()
print(local_var) # This will raise a NameError
Enclosing Scope #
Nested functions can access variables from their enclosing function’s scope.
def outer_function():
outer_var = "I'm in the outer function"
def inner_function():
print(outer_var) # This works
inner_function()
outer_function()
Global Scope #
Variables defined at the top level of a module or script are in the global scope. They can be accessed from anywhere within that module.
global_var = "I'm a global variable"
def my_function():
print(global_var) # This works
my_function()
print(global_var) # This also works
Built-in Scope #
Common built-in functions are always available such as print(), range(), etc.
print("Hello, World!") # This works
Using globals()
and locals()
#
The globals()
function returns a dictionary representing the current global variable table, while the locals()
function returns a dictionary representing the current local variable table.
Whenever you are unsure about the scope of a variable, you can use these functions to help you inspect the current state.
global_var = "I'm a global variable"
def my_function():
local_var = "I'm a local variable"
print("Local variables:", locals())
print("Global variables:", globals())
my_function()
Output:
Local variables: {'local_var': "I'm a local variable"}
Global variables: {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f9c8c0c1d90>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'global_var': "I'm a global variable", 'my_function': <function my_function at 0x7f9c8c0c1ee0>}
Scopes with Nested Functions #
When you have nested functions, the inner function can access variables from the outer function’s scope. But also it has access to the global scope.
var_g = "I'm a global variable"
def outer_function():
var_o = "I'm in the outer function"
def inner_function():
var_i = "I'm in the inner function"
print(var_i) # This will print the inner function's variable
print(var_o) # This will print the outer function's variable
print(var_g) # This will print the global variable
inner_function()
print(var_o) # This will print the outer function's variable
outer_function()
print(var_g) # This will print the global variable
Shadowing Variables #
When a variable defined in a local scope has the same name as a variable in an enclosing or global scope, the local variable “shadows” the outer variable. This means that within the local scope, the local variable takes precedence.
def my_function():
var = "I'm a local variable"
print(var) # This will print the local variable
var = "I'm a global variable"
my_function()
print(var) # outputs "I'm a global variable"
Typically, shadowing a global variable is usually accidental, and can lead to odd behavior in your code.
If you are using lots of global variables, a convention of prefixing them with g_
can help avoid confusion.
g_var = "I'm a global variable"
The global
Keyword #
If you need to modify a global variable inside a function, you can use the global
keyword.
count = 0 # Global variable
def my_function():
global count
count += 1
print("Inside function:", count)
my_function()
print("Outside function:", count)
Output:
Inside function: 1
Outside function: 1
The nonlocal
Keyword #
If you want to modify a variable in the nearest enclosing scope (but not global), you can use the nonlocal
keyword. This is useful in nested functions.
def outer_function():
count = 0 # Enclosing variable
def inner_function():
nonlocal count
count += 1
print("Inside inner function:", count)
inner_function()
print("Inside outer function:", count)
outer_function()
Output:
Inside inner function: 1
Inside outer function: 1
Note: If you specify a variable as nonlocal
, it must already exist in the nearest enclosing scope; otherwise, you’ll get a SyntaxError
.
def outer_function():
def inner_function():
nonlocal count
count += 1
print("Inside inner function:", count)
SyntaxError: no binding for nonlocal 'count' found
This will this be an error even if there is a global variable with the same name.
Nested functions with nonlocal
and global
#
When using nested functions, you can use both nonlocal
and global
keywords to specify the required scope for variables.
def outer_function():
var = "x" # Enclosing variable
def inner_function():
nonlocal var
def inner_function2():
nonlocal var
print("Inside inner function2:", var)
def inner_function3():
global var
print("Inside inner function3:", var)
inner_function2()
inner_function3()
inner_function()
outer_function()
Outputs:
Inside inner function2: x
Inside inner function3: y
Avoiding Global Variables #
Although we covered how to use the global keyword to help modify variables in the global scope, it’s generally best to avoid using global variables when possible.
Global variables can make code harder to track changes to the global state, leading to a maintenance nightmare when there are many global variables.
Here are some tips to avoid using global variables:
- Use function parameters and return values to pass data between functions. Makes it easier to track data flow.
- Encapsulate related data and behavior in classes. Reduces name clashes and puts related data together.
- Use modules to group related functions and variables together. The module namespace helps avoid name clashes.
- Limit the use of global variables to constants that do not change. E.g. Settings and defaults.