BioPlayground

🧬
목록으로

함수 네임스페이스와 스코프

Python에서 변수를 찾는 규칙(LEGB)과 네임스페이스의 동작 원리를 이해합니다.

중급
|
10
|
검증 완료 (2026-07)
namespacescopeLEGB 규칙globalnonlocal변수 탐색
진행률0/18 (0%)

함수 네임스페이스와 스코프

이 토픽을 마치면

Python이 변수 이름을 어떤 순서로 찾는지(LEGB 규칙) 설명할 수 있고, 지역 변수와 전역 변수가 충돌하는 상황을 올바르게 처리할 수 있습니다.


같은 이름, 다른 값

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

함수 안의 x와 바깥의 x이름은 같지만 서로 다른 변수입니다. 이것을 이해하려면 네임스페이스와 스코프를 알아야 합니다.


네임스페이스란

네임스페이스(namespace)는 이름과 객체의 매핑을 담는 공간입니다. Python에서는 딕셔너리처럼 {이름: 객체} 형태로 동작합니다.

python
x = 10
name = "철수"
# 모듈 네임스페이스: {"x": 10, "name": "철수", ...}
python
def greet():
msg = "안녕"
# 함수 네임스페이스: {"msg": "안녕"}

함수를 호출하면 새로운 네임스페이스가 생기고, 함수가 끝나면 사라집니다. 그래서 함수 안에서 만든 변수는 밖에서 쓸 수 없습니다.


스코프와 LEGB 규칙

스코프(scope)는 코드에서 이름이 보이는 범위입니다. Python은 변수를 찾을 때 LEGB 순서로 탐색합니다:

text
L — Local        함수 내부
E — Enclosing    감싸고 있는 함수 (중첩 함수)
G — Global       모듈 최상위
B — Built-in     Python 내장 (print, len, ...)
python
x = "global" # G
def outer():
x = "enclosing" # E
def inner():
x = "local" # L
print(x)
inner()
outer() # "local" — L에서 찾았으므로 더 이상 탐색 안 함

가장 안쪽(L)부터 찾고, 없으면 바깥으로 나갑니다. 찾는 즉시 멈춥니다.


Local — 함수 내부 변수

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

함수 안에서 대입(=)한 변수는 자동으로 Local 변수가 됩니다. 함수 밖에서는 존재하지 않습니다.


Global — 모듈 최상위 변수

python
counter = 0 # Global
def increment():
print(counter) # ✅ 읽기는 가능 (LEGB로 G에서 발견)
def reset():
counter = 0 # ⚠️ 새로운 Local 변수가 생김! Global의 counter와 무관
reset()
print(counter) # 여전히 0 (Global은 변경 안 됨)

함수 안에서 Global 변수를 읽는 것은 자유롭게 가능합니다. 하지만 대입하면 Python은 그것을 새로운 Local 변수로 인식합니다.

global 키워드

Global 변수를 함수 안에서 수정하려면 global 키워드를 씁니다:

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

global counter는 "이 함수 안의 counter는 Local이 아니라 Global의 것"이라는 선언입니다.

하지만 global을 남용하면 어디서 값이 바뀌는지 추적하기 어려워집니다. 가능하면 함수의 매개변수와 반환값으로 데이터를 주고받는 것이 좋습니다.


Enclosing — 중첩 함수와 nonlocal

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

nonlocal은 "이 변수는 Local이 아니라 바로 바깥 함수(Enclosing)의 것"이라는 선언입니다. 클로저를 만들 때 사용합니다.


흔한 실수 — UnboundLocalError

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

에러가 발생합니다. Python은 함수 전체를 미리 분석해서, x = 20이 있으면 x를 Local 변수로 결정합니다. 그런데 print(x) 시점에는 아직 Local x에 값이 할당되지 않았으므로 에러가 납니다.

해결 방법:

python
def fixed_1():
x = 20 # 먼저 할당
print(x)
def fixed_2():
global x # Global임을 명시
print(x)
x = 20

Built-in — Python 내장 이름

python
print(len([1, 2, 3])) # 3 — len은 Built-in
# 실수로 내장 이름을 덮어쓰면
list = [1, 2, 3] # ⚠️ Built-in list를 덮어씀
new_list = list("abc") # TypeError!

list, dict, print, len 같은 이름은 Built-in 네임스페이스에 있습니다. 이 이름을 변수로 쓰면 Built-in이 가려져서 원래 기능을 쓸 수 없게 됩니다.

python
# 복구
del list # Local/Global의 list를 삭제하면 Built-in이 다시 보임

클로저 — 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

multiply 함수가 반환된 뒤에도 factor를 기억합니다. 이것이 클로저(closure) — 함수가 자신이 정의된 스코프의 변수를 "닫아서 가지고 다니는 것"입니다.

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

클래스를 만들지 않고도 상태를 유지하는 함수를 만들 수 있습니다.


네임스페이스 디버깅

python
def debug_scope():
x = 10
y = 20
print(locals()) # {'x': 10, 'y': 20}
debug_scope()
print(globals().keys()) # 전역 네임스페이스의 모든 이름

locals()globals()는 각각 현재 스코프의 네임스페이스를 딕셔너리로 반환합니다. 변수가 어디에 속하는지 확인하고 싶을 때 유용합니다.

python
import builtins
print(dir(builtins)) # Built-in 네임스페이스의 모든 이름

핵심 정리

키워드의미
(없음)대입 → Local, 참조 → LEGB 순서 탐색
global"이 변수는 Global의 것"
nonlocal"이 변수는 Enclosing 함수의 것"
text
탐색 순서: Local → Enclosing → Global → Built-in
대입 규칙: 함수 안에서 = 을 쓰면 Local로 간주

모듈과 네임스페이스

python
import math
from os import path
# import는 모듈 이름으로 네임스페이스를 분리
print(math.pi) # math 네임스페이스의 pi
print(path.exists) # os.path의 exists
# from import *는 네임스페이스를 오염시킴
from math import * # ⚠️ 모든 이름이 현재 Global에 쏟아짐
print(pi) # 작동하지만, 어디서 온 건지 불명확

import mathmath라는 이름 하나만 Global에 추가합니다. from math import *는 수십 개의 이름을 Global에 쏟아부어서 이름 충돌 위험이 커집니다.



데코레이터와 스코프

python
def log_calls(func):
count = 0
def wrapper(*args, **kwargs):
nonlocal count
count += 1
print(f"[{count}번째 호출] {func.__name__}")
return func(*args, **kwargs)
return wrapper
@log_calls
def greet(name):
return f"안녕, {name}"
greet("철수") # [1번째 호출] greet
greet("영희") # [2번째 호출] greet

데코레이터는 클로저의 실전 활용입니다. countlog_calls의 Local(= wrapper의 Enclosing)에 살면서, nonlocal로 매 호출마다 증가합니다.


네임스페이스 충돌로 인한 버그는 디버깅이 까다롭습니다. "이 변수가 어디 소속인지"를 항상 의식하면 이런 실수를 피할 수 있습니다.