Back to List

Function Namespace and Scope

Understand the rules for finding variables in Python (LEGB) and how namespaces work.

Intermediate
|
10min
|
Verified (2026-07)
namespacescopeLEGB ruleglobalnonlocalvariable lookup
Progress0/18 (0%)

Function Namespaces and Scope

After completing this topic

You will be able to explain how Python searches for variable names (the LEGB rule) and handle situations where local and global variables conflict correctly.


Same Name, Different Value

python
x = 10
def foo():
x = 20
print(x)
foo() # 20
print(x) # 10

The x inside the function and the x outside are different variables, even though they have the same name. To understand this, you need to know about namespaces and scope.


What is a Namespace?

A namespace is a space that holds mappings between names and objects. In Python, it acts like a dictionary in the form of {name: object}.

python
x = 10
name = "철수"
# Module namespace: {"x": 10, "name": "철수", ...}
python
def greet():
msg = "μ•ˆλ…•"
# Function namespace: {"msg": "μ•ˆλ…•"}

When a function is called, a new namespace is created, and it disappears when the function ends. Therefore, variables created inside a function cannot be used outside of it.


Scope and the LEGB Rule

Scope is the region of a code where a name is visible. When searching for a variable, Python explores in the LEGB order:

text
L β€” Local        Inside the function
E β€” Enclosing    Enclosing function (nested function)
G β€” Global       Top level of the module
B β€” Built-in     Python built-in (print, len, ...)
python
x = "global" # G
def outer():
x = "enclosing" # E
def inner():
x = "local" # L
print(x)
inner()
outer() # "local" β€” Because it was found in L, it stops searching

It searches from the innermost (L) and goes outward if it's not found. It stops as soon as it finds it.


Local β€” Variables Inside a Function

python
def calculate():
result = 42 # Local variable
temp = result * 2
return temp
calculate()
print(result) # NameError: name 'result' is not defined

Variables assigned (=) inside a function are automatically Local variables. They do not exist outside the function.


Global β€” Variables at the Top Level of a Module

python
counter = 0 # Global
def increment():
print(counter) # βœ… Reading is possible (found in G by LEGB)
def reset():
counter = 0 # ⚠️ A new Local variable is created! Independent of the Global counter
reset()
print(counter) # Still 0 (Global is not changed)

It is free to read Global variables inside a function. However, assigning to them creates a new Local variable, independent of the Global variable.

The global Keyword

To modify a Global variable inside a function, use the global keyword:

python
counter = 0
def increment():
global counter
counter += 1
increment()
increment()
print(counter) # 2

global counter declares that counter inside this function refers to the Global variable, not a Local one.

However, abusing global makes it difficult to track where values are changing. It is best to pass data using function parameters and return values whenever possible.


Enclosing β€” Nested Functions and nonlocal

python
def outer():
count = 0
def inner():
nonlocal count
count += 1
return count
print(inner()) # 1
print(inner()) # 2
outer()

nonlocal declares that a variable is not Local, but belongs to the immediately enclosing function. It is used when creating closures.


Common Mistake β€” UnboundLocalError

python
x = 10
def broken():
print(x) # ❌ UnboundLocalError!
x = 20
broken()

An error occurs. Python analyzes the entire function in advance, and if it sees x = 20, it decides that x is a Local variable. However, when print(x) is called, the Local x has not yet been assigned a value, so an error occurs.

Solution:

python
def fixed_1():
x = 20 # Assign first
print(x)
def fixed_2():
global x # Explicitly declare that it is Global
print(x)
x = 20

Built-in β€” Python Built-in Names

python
print(len([1, 2, 3])) # 3 β€” len is Built-in
# If you accidentally overwrite a built-in name
list = [1, 2, 3] # ⚠️ Overwrites the Built-in list
new_list = list("abc") # TypeError!

Names like list, dict, print, len are in the Built-in namespace. If you use these names as variables, you will overwrite the Built-in, and you will no longer be able to use the original functionality.

python
# Restore
del list # Deleting the Local/Global list makes the Built-in visible again

Closures β€” Practical Use of Enclosing

python
def make_multiplier(factor):
def multiply(x):
return x * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15

The multiply function remembers factor even after it is returned. This is a closure β€” a function that "closes" and carries variables from the scope in which it was defined.

python
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3

You can create functions that maintain state without creating classes.


Debugging Namespaces

python
def debug_scope():
x = 10
y = 20
print(locals()) # {'x': 10, 'y': 20}
debug_scope()
print(globals().keys()) # All names in the global namespace

locals() and globals() return the namespace of the current scope as a dictionary. They are useful when you want to check where a variable belongs.

python
import builtins
print(dir(builtins)) # All names in the Built-in namespace

Key Takeaways

KeywordMeaning
(None)Assignment β†’ Local, reference β†’ Search in LEGB order
global"This variable belongs to the Global"
nonlocal"This variable belongs to the Enclosing function"
text
Search order: Local β†’ Enclosing β†’ Global β†’ Built-in
Assignment rule: If you use = inside a function, it is treated as Local

Modules and Namespaces

python
import math
from os import path
# import separates namespaces by module name
print(math.pi) # pi in the math namespace
print(path.exists) # exists in os.path
# from import * pollutes the namespace
from math import * # ⚠️ All names are dumped into the current Global
print(pi) # Works, but it is unclear where it comes from

import math adds only the name math to the Global. from math import * dumps dozens of names into the Global, which increases the risk of name collisions.



Decorators and Scope

python
def log_calls(func):
count = 0
def wrapper(*args, **kwargs):
nonlocal count
count += 1
print(f"[{count}th call] {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_calls
def greet(name):
return f"Hello, {name}"
greet("철수") # [1st call] greet
greet("영희") # [2nd call] greet

Decorators are a practical use of closures. count lives in the Local (which is the Enclosing of wrapper) and is incremented with nonlocal with each call.


Namespace conflicts can cause bugs that are difficult to debug. By always being aware of "which scope this variable belongs to," you can avoid these mistakes.

πŸ’¬ Questions & Comments

0 comments

You can post without signing in. Guest comments cannot be edited or deleted by their author.

0/2000

Loading...