Skip to content

Understanding Variable Scope in Python: Local vs. Global Variables

Updated: at 03:12 AM

Variable scope refers to the part of a program where a variable is accessible. Python has two main types of variable scope - local and global. Understanding the differences between local and global variable scopes is crucial for writing clean, modular Python code and avoiding subtle bugs.

This guide will provide a detailed explanation of Python’s variable scoping rules, including:

Table of Contents

Open Table of Contents

Defining Local and Global Variable Scopes

In Python, variables defined inside a function are considered local variables, while those defined outside of functions are called global variables.

The scope of a variable determines where it is accessible in a program. Let’s examine this in more detail:

Local Scope

A variable declared inside a function has a local scope, meaning it is only accessible within that function. Here is an example:

# This is a global variable
x = 5

def my_func():
  # This is a local variable
  y = 3

  print(y)

my_func()

print(x) # Prints 5
print(y) # Error, y is not defined

The variable y is local and can only be referenced inside my_func. It is unavailable outside the function.

Local variables are created when a function starts executing and deleted after it finishes. Each call to the function creates a new local scope, so that the prior function calls have no effect.

Global Scope

A variable declared outside of a function has a global scope. Global variables can be accessed by any function. For example:

# This is a global variable
x = 5

def my_func():
  print(x)

my_func() # Prints 5
print(x) # Prints 5

Here, x is declared outside the function my_func(), so it is global and can be printed both inside and outside the function.

In Python, global variables are available across modules as well. Any changes made to global variables inside functions are reflected globally.

Nested Functions and Enclosing Scopes

Python supports nested functions, where you define functions inside another function. In this case, variables of the enclosing scope are available to nested functions. Consider this example:

x = 5

def outer_func():
  y = 4

  def inner_func():
    print(x) # Accessible
    print(y) # Accessible

  inner_func()

outer_func()

Here, inner_func() can access both x and y, since it encloses the scope of outer_func.

Modifying Global and Local Variables

In Python, assigning a value to a variable inside a function makes it local automatically. To modify a global variable inside a function, we need to use the global keyword.

Modifying Global Variables

To modify a global variable inside a function, use global:

count = 0

def increment():
  global count

  count += 1

increment()
print(count) # 1

Here, count is declared as global, so changes made inside increment() alter the global variable.

Without the global keyword, Python treats count as a local variable:

count = 0

def increment():
  count += 1 # Modifies local count

increment()
print(count) # 0

Since we did not declare count as global inside the function, changes made to count remain local and do not affect the global variable.

Modifying Variables in Enclosing Scope

For nested functions, variables in the enclosing scope can be modified directly. The nonlocal keyword is not needed.

def outer():
  x = 5

  def inner():
    x = 10 # Modifies x in enclosing scope

  inner()
  print(x) # 10

Here, inner() assigns a new value to x directly, modifying the variable in the enclosing scope.

Modifying Local Variables

A function can modify its own local variables directly - no special keywords are needed:

def my_func():
  y = 10
  y += 1

  print(y) # 11

The variable y is local to my_func, so it can be modified without global or nonlocal keywords.

Common Errors and Gotchas

Here are some common mistakes with variable scopes in Python and how to avoid them:

Accessing Undefined Variables

Attempting to use a variable that hasn’t been defined causes a NameError:

def my_func():
  print(x) # Error, x is not defined

my_func()

This occurs frequently while accessing local variables from the global scope or vice versa. Always declare variables before using them.

Forgetting to Declare Global Variables

Forgetting the global keyword while modifying globals inside functions is a frequent cause of bugs:

count = 0

def increment():
  count += 1 # Modifies local count

increment()
print(count) # 0, global unchanged

Ensure global is used to avoid unintended local variables.

UnboundLocalError

Accessing a local variable before assignment raises an UnboundLocalError:

def my_func():
  print(y) # Error, local y referenced before assignment
  y = 5

Python detects y is local, but it is used before being assigned a value. Declare locals first before using them.

Modifying Mutable Globals In-place

For mutable objects like lists, modifying them directly inside functions modifies the global value:

global_list = [1, 2]

def add_element():
  global_list.append(3) # Modifies global list directly

add_element()
print(global_list) # [1, 2, 3]

This catches some new Python programmers off guard. Be careful when mutating global objects in functions.

Best Practices for Using Scopes

Here are some key best practices to use local and global variables effectively:

Well-structured programs minimize use of global state. Python’s scoping rules encourage clean code with minimal side effects between functions.

Python Variable Scopes in Action

Let’s look at some practical examples demonstrating how variable scopes work in real Python code:

Accessing Globals across Modules

Global variables are available across modules. This allows sharing state between different parts of a program:

# module1.py

bottle_count = 5 # Global variable

# module2.py

import module1

print(module1.bottle_count) # Access module1's global

While convenient, be careful not to overuse globals this way for shared state.

Closures to Avoid Globals

Using closures is a safer way to share state between functions:

def counter_builder():
  count = 0

  def counter():
    nonlocal count

    count += 1
    return count

  return counter

counter = counter_builder()
print(counter()) # 1
print(counter()) # 2

This counter_builder factory function avoids polluting the global scope.

Changing Mutable Globals

Here, a global list is modified by multiple functions:

shopping_cart = []

def add_item(item):
  global shopping_cart
  shopping_cart.append(item)

add_item("shoes")
add_item("gym bag")

print(shopping_cart) # ['shoes', 'gym bag']

While this works, it is often safer to pass the cart as a parameter.

Thread-safe Global Modification

For threaded programs, shared mutable globals require locks or other synchronization primitives:

import threading

count = 0
lock = threading.Lock()

def increment():
  global count
  with lock:
    count += 1

threads = []
for i in range(100):
  thread = threading.Thread(target=increment)
  thread.start()
  threads.append(thread)

for thread in threads:
  thread.join()

print(count) # 100

Here the global count is modified atomically to ensure consistent behavior.

Conclusion

Understanding Python’s local and global variable scopes is essential to writing clean, robust programs. Follow the scoping rules carefully to avoid subtle bugs.

Key takeaways include:

Mastering Python variable scopes will help you write well-structured programs minimizing nasty surprises!