Область видимости: Local, Enclosing, Global и Built-in (LEGB)
- Объяснить, что такое Scope и почему переменная, созданная в функции, не существует вне её
- Знать четыре области видимости — Local, Enclosing, Global и Built-in — и порядок поиска LEGB
- Понимать, что происходит при изменении глобальной переменной внутри функции и что делает
global - Писать функции, которые получают данные через параметры и возвращают результат, а не полагаются на глобальные переменные
Что мы изучим?
Scope (область видимости) = область кода, где известно имя (переменной или функции). Мы изучим:
- Local — переменные внутри функции.
- Enclosing — переменные внешней функции, когда есть функция внутри функции.
- Global — переменные на верхнем уровне файла.
- Built-in — встроенные имена Python (
print,len…). - LEGB — порядок, в котором Python ищет имя.
Зачем это нужно?
Если бы каждая переменная была доступна отовсюду, переменная total в одной функции перезаписывала бы переменную total в другой, и в программе на 10 000 строк был бы хаос. Область видимости изолирует каждую функцию: она может использовать свои имена, не боясь столкновений.
С другой стороны, ошибки вроде NameError или UnboundLocalError очень запутывают, если не понимать области видимости.
Объяснение с основ
Local
Переменная, созданная внутри функции (включая её параметры), — локальная:
- Она создаётся при каждом вызове функции и исчезает, когда та завершается.
- Она недоступна снаружи.
- У двух разных функций могут быть локальные переменные с одинаковым именем, никак не связанные.
Global
Переменная, созданная на верхнем уровне файла (не внутри функции), — глобальная:
- Она доступна для чтения из любой функции файла.
- Чтобы изменить её (присвоить новое значение) изнутри функции, нужно явное объявление:
global name.
Enclosing
Можно определить функцию внутри функции. Внутренняя видит переменные внешней. Область видимости внешней с точки зрения внутренней называется Enclosing (объемлющая).
Built-in
Имена, которые Python знает всегда, без import: print, len, sum, max, int, str, list, range…
Порядок поиска: LEGB
Когда код использует имя, Python ищет его в таком порядке и останавливается на первом месте, где нашёл:
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: NameErrorВы ищете ключи: сначала в кармане (Local), потом в сумке, которую несёте (Enclosing), потом дома (Global) и наконец в вестибюле дома, где вещи оставляют для всех (Built-in). Как только нашли — перестаёте искать. А ключ в кармане друга вы вообще не видите: это Local другой функции.
Синтаксис
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 + 1Первый пример кода
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)Вывод:
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 definedРазбор кода построчно
| Имя | Область видимости | Объяснение |
|---|---|---|
course | Global | Создано на верхнем уровне. welcome читает его: L (не найдено) → E (нет) → G (найдено!). |
name | Local функции welcome | Параметр = локальная переменная. Существует только во время вызова. |
message | Local функции welcome | Создаётся внутри функции и исчезает, когда она возвращает значение. |
print, len… | Built-in | Находятся в конце поиска. |
print(message) снаружи | — | На верхнем уровне нет message. Она была Local и уже исчезла, поэтому NameError. |
Правильный способ получить значение из функции — return, и именно это делает welcome. Вызывающий код получил значение (и может сохранить его в своей переменной).
Ещё один пример
Одно и то же имя в нескольких областях видимости и 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()Вывод:
inside local_total: 5
after local_total: 100
inside read_global: 100
inside change_global: 150
after change_global: 150
inner sees enclosing label: outerРазбор:
local_total— присваиваниеtotal = 5создаёт новую локальную переменную с именемtotal, которая «заслоняет» глобальную. Глобальная не изменилась (100).read_global— только читаетtotal. Локальной с таким именем у неё нет, поэтому по LEGB доходим до глобальной.change_global—global totalговорит Python: «в этой функцииtotal— глобальная». Теперь присваивание меняет глобальную (150).innerвидитlabelфункцииouter. Это E в LEGB.
Типичная ошибка
Ошибка 1: UnboundLocalError — прочитать, а затем изменить глобальную без global.
count = 0
def add_one():
count = count + 1
add_one()UnboundLocalError: cannot access local variable 'count' where it is not associated with a valueПочему? Поскольку в функции есть присваивание count, Python решает (заранее, до выполнения), что count — локальная во всей функции. А затем count + 1 пытается прочитать локальную, которая ещё не получила значения.
Ошибка 2 (главная): использовать global как постоянное решение. global «работает», но:
- Любая функция может изменить переменную, и когда возникает баг, невозможно понять, кто её изменил.
- Функция зависит от чего-то «снаружи», поэтому её трудно тестировать и переиспользовать.
Чистый способ: параметр входит, return выходит.
def add_one(count):
return count + 1
count = 0
count = add_one(count)
print(count) # 1Теперь совершенно ясно, что входит и что выходит, и функция не трогает ничего, чего не получила. (В теме ООП мы изучим ещё один способ управлять меняющимся «состоянием»: объекты.)
Ошибка 3: перезаписать встроенное имя.
list = [1, 2, 3]
numbers = list(range(5))TypeError: 'list' object is not callableНаша глобальная list идёт в LEGB раньше Built-in, поэтому она «заслонила» встроенную функцию. То же самое с sum = 0, max = 10, input = ..., str = .... Никогда не называйте переменные именами встроенных функций.
Практика
Упражнения опираются только на то, что пройдено в этом уроке и в предыдущих. Сначала попробуйте сами, и только потом открывайте решение.
Упражнение 1: Переменная, которая исчезла
def welcome(name):
message = "Welcome " + name
return message
print(welcome("Dana"))
print(message)- Что произойдёт на последней строке и почему?
- Исправьте так, чтобы сообщение напечаталось дважды.
Показать решение
NameError: name 'message' is not defined.message— локальная (Local) переменная функцииwelcome: она создаётся при каждом вызове и исчезает, когда функция завершается.- Сохраните возвращённое значение в собственной переменной:
def welcome(name):
message = "Welcome " + name
return message
result = welcome("Dana")
print(result)
print(result)Welcome Dana
Welcome DanaУпражнение 2: Предскажите вывод
x = "global"
def first():
x = "local in first"
print("1:", x)
def second():
print("2:", x)
first()
second()
print("3:", x)Показать решение
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: globalfirstсоздаёт собственную локальнуюx— глобальная не тронута.secondне определяетx, поэтому по LEGB доходим до глобальной (Global).
Упражнение 3: UnboundLocalError
count = 0
def add_one():
count = count + 1
add_one()- Какую ошибку вы получите и почему?
- Исправьте без
global.
Показать решение
UnboundLocalError. Из-за присваиванияcount = ...Python решает, чтоcount— локальная переменная во всей функции, — и затемcount + 1пытается прочитать локальную, у которой ещё нет значения.- Параметр входит,
returnвыходит:
def add_one(count):
return count + 1
count = 0
count = add_one(count)
count = add_one(count)
print(count)2Упражнение 4: Перезапись встроенного имени
list = [1, 2, 3]
numbers = list(range(5))- Какую ошибку вы получите?
- Объясните с помощью LEGB, почему так происходит, и исправьте.
Показать решение
TypeError: 'list' object is not callable.- По LEGB Python ищет
listв Global раньше, чем в Built-in. Наша переменная с именемlist«заслонила» встроенную функцию. Исправление — не давать переменным имена встроенных функций:
items = [1, 2, 3]
numbers = list(range(5))
print(items, numbers)[1, 2, 3] [0, 1, 2, 3, 4]Упражнение 5: Без global
cart = []
total = 0
def add(price):
global total
cart.append(price)
total = total + price
add(100)
add(50)
print(cart, total)Выполните рефакторинг так, чтобы ни одна функция не использовала global и не меняла внешние переменные: каждая функция получает всё нужное через параметры и возвращает результат.
Ожидаемый вывод:
[100, 50] 150Показать решение
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] 150totalбольше не «состояние», которое нужно поддерживать, — оно вычисляется из корзины.addвозвращает новый список (cart + [price]) и не меняет существующий.
Итоги
- Scope = где известно имя. Переменная, созданная в функции (включая параметры), — Local и исчезает в конце вызова.
- LEGB: Local → Enclosing → Global → Built-in. Остановка на первом месте, где найдено. Если не найдено —
NameError. - Присваивание внутри функции создаёт локальную переменную, даже если есть глобальная с тем же именем.
global xпозволяет менять глобальную изнутри функции, но этого лучше избегать. Параметр входит,returnвыходит.- Не давайте переменным имена встроенных функций (
list,sum,max,str…).