[Python] 스코프와 LEGB, global, nonlocal
python scope, LEGB, python global, python nonlocal, namespace, 변수 스코프
LEGB 규칙
Python은 이름 해결 시 L → E → G → B 순서로 스코프를 탐색한다.
| 단계 | 의미 | 예 |
|---|---|---|
| Local | 현재 함수 안 | def f(): x = 1 의 x |
| Enclosing | 둘러싼 함수 (closure) | 중첩 함수에서 외부 함수의 변수 |
| Global | 모듈 최상위 | 모듈 파일의 최상위 변수 |
| Builtin | 내장 | print, len, range 등 |
python
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # L
inner()
print(x) # E (outer의 x)
outer()
print(x) # G 결과
local
enclosing
global이름이 어디서도 발견되지 않으면 NameError.
global 선언
함수 내에서 모듈 전역 변수에 재할당하려면 global 선언이 필요하다.
python
counter = 0
def increment_wrong():
counter += 1 # UnboundLocalError: 로컬로 해석되는데 미할당
def increment_ok():
global counter
counter += 1
try:
increment_wrong()
except UnboundLocalError as e:
print("Error:", e)
increment_ok()
increment_ok()
print(counter) 결과
Error: cannot access local variable 'counter' where it is not associated with a value
2규칙: 함수 본문 어디서든 변수에 할당이 있으면, 그 변수는 함수 내내 로컬로 취급된다. 읽기 전에 할당이 와도 마찬가지. global로 명시해야 전역 바인딩을 갱신.
읽기만 한다면 global 불필요.
counter = 0
def read_only():
return counter * 2 # global 선언 없이 읽기 가능
nonlocal 선언
중첩 함수에서 enclosing 스코프의 변수에 재할당하려면 nonlocal.
python
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
c = make_counter()
print(c(), c(), c()) 결과
1 2 3nonlocal 없으면 count += 1이 새 로컬 변수를 만들려다 UnboundLocalError를 던진다.
global vs nonlocal
| global | nonlocal | |
|---|---|---|
| 대상 | 모듈 최상위 변수 | 가장 가까운 enclosing 함수의 변수 |
| 변수 없을 시 | 새로 생성 가능 | SyntaxError (반드시 존재해야) |
| 사용 위치 | 함수 안 | 중첩 함수 안 |
def outer():
x = 1
def middle():
def inner():
nonlocal x # outer의 x를 가리킴
x += 1
inner()
middle()
return x
print(outer()) # 2
nonlocal은 LEGB의 E를 따라 가장 가까운 enclosing 스코프부터 탐색해 첫 번째로 발견된 동일 이름에 바인딩.
클래스 스코프의 특이점
클래스 본문은 새 스코프를 만들지만 메서드 안에서 보이지 않는다. 메서드는 self.x나 ClassName.x로 접근해야.
class Cls:
x = 1
def method(self):
return x # NameError! (클래스 스코프 못 봄)
return self.x # OK
return Cls.x # OK
또한 클래스 본문 안의 컴프리헨션도 클래스 스코프를 못 본다.
class C:
n = 5
nums = [x * n for x in range(3)] # NameError: n
컴프리헨션 스코프 (3.x)
컴프리헨션과 generator expression은 자체 스코프를 가진다.
x = 99
nums = [x for x in range(5)]
print(x) # 99 (덮어쓰지 않음)
Python 2에선 x가 누출됐지만 3에서 수정됨.
globals() / locals()
현재 스코프의 이름 사전을 반환.
x = 1
print(globals()["x"]) # 1
def f():
y = 2
print(locals()) # {'y': 2}
f()
locals()는 함수 안에서 호출 시 로컬 변수 사본을 돌려준다. 변경해도 실제 로컬에 반영되지 않을 수 있다.
namespace 종류
- 빌트인 namespace: 인터프리터 시작 시 생성, 종료까지 존재
- 모듈 namespace: 각 모듈의
__dict__. import 시 생성 - 함수 namespace: 호출 시 생성, 반환 시 폐기 (closure로 잡힌 변수는 살아남음)
- 클래스 namespace: 클래스 본문 실행 시 생성
import math
math.__dict__["pi"] # 3.141592...
def f(): pass
f.__globals__ # 함수가 속한 모듈의 globals
함정 모음
1. for 변수 누출
for i in range(3):
pass
print(i) # 2 (for 변수는 누출됨!)
루프 변수는 함수 스코프에 남는다(블록 스코프 없음).
2. 함수 안에서 이름 그림자
def f():
list = [1, 2] # 빌트인 list 가림
list(...) # TypeError: 'list' object is not callable
# 빌트인 이름 사용 금지: list, dict, set, str, type, id, file, format ...
3. mutable global
items = []
def add(x):
items.append(x) # global 불필요 (재할당이 아니라 변경)
# 재할당하려면 필요
def reset():
global items
items = []
💬 댓글