Scope: Local, Enclosing, Global and Built-in (LEGB)
- Explain what Scope is and why a variable created in a function does not exist outside it
- Know the four Scopes — Local, Enclosing, Global and Built-in — and the LEGB lookup order
- Understand what happens when you change a global variable inside a function, and what
globaldoes - Write functions that receive data through parameters and return a result, instead of relying on globals
What will we learn?
Scope = the area of the code where a name (a variable or a function) is known. We will learn:
- Local — variables inside a function.
- Enclosing — variables of an outer function, when there is a function inside a function.
- Global — variables at the top level of the file.
- Built-in — Python’s built-in names (
print,len…). - LEGB — the order in which Python looks for a name.
Why do we need this?
If every variable were accessible from everywhere, a variable named total in one function would overwrite a variable total in another function, and in a program of 10,000 lines that is chaos. Scope isolates every function: it can use its own names without fearing collisions.
On the other hand, errors like NameError or UnboundLocalError are very confusing if you do not understand Scope.
Explanation from the basics
Local
A variable created inside a function (including its parameters) is local:
- It is created on every call to the function, and disappears when it ends.
- It is not accessible from outside.
- Two different functions can have local variables with the same name, with no connection between them.
Global
A variable created at the top level of the file (not inside a function) is global:
- It is accessible for reading from every function in the file.
- To change it (assign it a new value) from inside a function, you need an explicit declaration:
global name.
Enclosing
You can define a function inside a function. The inner one sees the variables of the outer one. The Scope of the outer one is called Enclosing from the inner one’s point of view.
Built-in
The names Python always knows, without import: print, len, sum, max, int, str, list, range…
The lookup order: LEGB
When the code uses a name, Python looks for it in this order, and stops at the first place where it found it:
L Local → inside the current function
E Enclosing → inside outer functions (if nested)
G Global → top level of the file
B Built-in → Python's built-in names
→ not found anywhere: NameErrorYou look for your keys: first in your pocket (Local), then in the bag you are carrying (Enclosing), then at home (Global), and finally in the building’s lobby, where things are left for everyone (Built-in). The moment you find them, you stop looking. And a key in a friend’s pocket you cannot see at all: that is the Local of another function.
Syntax
counter = 0 # global
def outer():
message = "hi" # local to outer = enclosing for inner
def inner():
result = message + "!" # local to inner; message found in Enclosing
return result
return inner()
def increase():
global counter # declare: assignments go to the GLOBAL counter
counter = counter + 1First code example
week-2/scope_demo.py:
course = "Kodkod 6"
def welcome(name):
message = "Welcome " + name + " to " + course
return message
print(welcome("Dana"))
print(course)
print(message)Output:
Welcome Dana to Kodkod 6
Kodkod 6
Traceback (most recent call last):
File "scope_demo.py", line 11, in <module>
print(message)
NameError: name 'message' is not definedBreaking down the code line by line
| Name | Scope | Explanation |
|---|---|---|
course | Global | Created at the top level. welcome reads it: L (not found) → E (none) → G (found!). |
name | Local of welcome | A parameter = a local variable. Exists only during the call. |
message | Local of welcome | Created inside the function and disappears when it returns. |
print, len… | Built-in | Found at the end of the lookup. |
print(message) outside | — | At the top level there is no message. It was Local, and it is already gone, so NameError. |
The right way to get a value out of a function is return, and that is exactly what welcome does. The caller got the value (and can store it in a variable of its own).
Another example
The same name in several Scopes, and global. week-2/scope_levels.py:
total = 100
def local_total():
total = 5
print("inside local_total:", total)
def read_global():
print("inside read_global:", total)
def change_global():
global total
total = total + 50
print("inside change_global:", total)
def outer():
label = "outer"
def inner():
print("inner sees enclosing label:", label)
inner()
local_total()
print("after local_total:", total)
read_global()
change_global()
print("after change_global:", total)
outer()Output:
inside local_total: 5
after local_total: 100
inside read_global: 100
inside change_global: 150
after change_global: 150
inner sees enclosing label: outerBreakdown:
local_total— the assignmenttotal = 5creates a new local variable namedtotal, which “hides” the global one. The global one did not change (100).read_global— only readstotal. It has no local with this name, so according to LEGB we reach the global one.change_global—global totaltells Python: “in this function,totalis the global one”. Now the assignment changes the global one (150).innersees thelabelofouter. That is the E in LEGB.
Common mistake
Mistake 1: UnboundLocalError — reading and then changing a global without global.
count = 0
def add_one():
count = count + 1
add_one()UnboundLocalError: cannot access local variable 'count' where it is not associated with a valueWhy? Because the function has an assignment to count, Python decides (in advance, before running) that count is local in the whole function. And then count + 1 tries to read a local that has not received a value yet.
Mistake 2 (the big one): using global as a permanent solution. global “works”, but:
- Every function can change the variable, and when there is a bug, there is no way to know who changed it.
- The function depends on something “from outside”, so it is hard to test it and reuse it.
The clean way: a parameter goes in, return comes out.
def add_one(count):
return count + 1
count = 0
count = add_one(count)
print(count) # 1Now it is completely clear what goes in and what comes out, and the function touches nothing it did not receive. (In the OOP topic we will learn another way to manage “state” that changes: objects.)
Mistake 3: overwriting a Built-in name.
list = [1, 2, 3]
numbers = list(range(5))TypeError: 'list' object is not callableOur global list comes before the Built-in in LEGB, so it “hid” the Built-in function. The same with sum = 0, max = 10, input = ..., str = .... Never name variables with the names of Built-ins.
Practice
The exercises use only what was taught in this lesson and the lessons before it. Try on your own first, and only then open the solution.
Exercise 1: The variable that disappeared
def welcome(name):
message = "Welcome " + name
return message
print(welcome("Dana"))
print(message)- What will happen on the last line, and why?
- Fix it so that the message is printed twice.
Show solution
NameError: name 'message' is not defined.messageis a Local variable ofwelcome— it is created on every call and disappears when the function ends.- Save the returned value in a variable of our own:
def welcome(name):
message = "Welcome " + name
return message
result = welcome("Dana")
print(result)
print(result)Welcome Dana
Welcome DanaExercise 2: Predict the output
x = "global"
def first():
x = "local in first"
print("1:", x)
def second():
print("2:", x)
first()
second()
print("3:", x)Show solution
x = "global"
def first():
x = "local in first"
print("1:", x)
def second():
print("2:", x)
first()
second()
print("3:", x)1: local in first
2: global
3: globalfirstcreates a localxof its own — the global one was not touched.seconddoes not definex, so according to LEGB we reach the Global one.
Exercise 3: UnboundLocalError
count = 0
def add_one():
count = count + 1
add_one()- Which error will you get, and why?
- Fix it without
global.
Show solution
UnboundLocalError. Because of the assignmentcount = ..., Python decides thatcountis a local variable in the whole function — and thencount + 1tries to read a local that has no value yet.- A parameter goes in,
returncomes out:
def add_one(count):
return count + 1
count = 0
count = add_one(count)
count = add_one(count)
print(count)2Exercise 4: Overwriting a built-in name
list = [1, 2, 3]
numbers = list(range(5))- Which error will you get?
- Explain using LEGB why it happens, and fix it.
Show solution
TypeError: 'list' object is not callable.- According to LEGB, Python looks for
listin Global before Built-in. Our variable namedlist“hid” the built-in function. The fix — do not give variables the names of Built-ins:
items = [1, 2, 3]
numbers = list(range(5))
print(items, numbers)[1, 2, 3] [0, 1, 2, 3, 4]Exercise 5: Without global
cart = []
total = 0
def add(price):
global total
cart.append(price)
total = total + price
add(100)
add(50)
print(cart, total)Refactor it so that no function uses global or changes outside variables: every function receives what it needs through parameters and returns a result.
Expected output:
[100, 50] 150Show solution
def add(cart, price):
return cart + [price]
def cart_total(cart):
return sum(cart)
cart = []
cart = add(cart, 100)
cart = add(cart, 50)
print(cart, cart_total(cart))[100, 50] 150totalis no longer “state” that has to be maintained — it is calculated from the cart.addreturns a new list (cart + [price]) and does not change the existing one.
Summary
- Scope = where a name is known. A variable created in a function (including parameters) is Local, and disappears at the end of the call.
- LEGB: Local → Enclosing → Global → Built-in. Stop at the first place where it is found. If it is not found,
NameError. - An assignment inside a function creates a local variable, even if there is a global one with the same name.
global xlets you change a global from inside a function, but it is better to avoid it. A parameter goes in andreturncomes out.- Do not give variables the names of Built-ins (
list,sum,max,str…).