
파이썬 프로그래밍을 하다 보면 함수 내부에 또 다른 함수를 정의하는 중첩 함수(Nested Function) 구조를 자주 접하게 됩니다. 이때 초보 개발자부터 숙련된 개발자까지 한 번쯤 당혹감을 느끼는 지점이 바로 '바깥쪽 함수의 변수를 안쪽 함수에서 수정하고 싶을 때'입니다. 단순히 값을 읽어오는 것은 문제가 없지만, 값을 변경하려고 하면 UnboundLocalError가 발생하거나 의도치 않게 지역 변수가 새로 생성되어 버리곤 합니다. 이러한 스코프(Scope)의 한계를 극복하고 데이터의 캡슐화와 상태 유지를 가능하게 만드는 열쇠가 바로 nonlocal 키워드입니다. 본 가이드에서는 nonlocal의 정의부터 실전 활용 사례, 그리고 global과의 결정적 차이점까지 심도 있게 다룹니다.
1. nonlocal 키워드란 무엇인가?
파이썬의 변수 탐색은 LEGB 룰(Local, Enclosed, Global, Built-in)을 따릅니다. nonlocal은 이 중 Enclosed Scope(중첩 함수를 둘러싼 부모 함수의 범위)에 있는 변수를 수정하겠다고 명시적으로 선언할 때 사용합니다. 기본적으로 파이썬의 중첩 함수 내에서 상위 함수의 변수에 접근할 때는 '읽기 전용' 권한만 가집니다. 만약 상위 변수에 새로운 값을 할당(Assignment)하려고 하면, 파이썬 인터프리터는 이를 해당 중첩 함수만의 새로운 '지역 변수'로 간주해 버립니다. nonlocal은 "이 변수는 내 지역 변수가 아니라, 나를 감싸고 있는 바로 위 함수의 변수야!"라고 알려주는 역할을 합니다.
2. 왜 nonlocal이 필요한가? (상태 유지와 클로저)
가장 큰 이유는 클로저(Closure)의 구현입니다. 전역 변수를 사용하지 않고도 특정 상태를 안전하게 유지하고 싶을 때 nonlocal이 빛을 발합니다. 이는 객체 지향 프로그래밍의 클래스(Class)보다 가벼운 구조로 특정 기능을 캡슐화할 때 매우 유용합니다.
| 대상 범위 | 모듈 수준의 전역 변수 | 중첩 함수 구조의 상위 함수 변수 |
| 주요 목적 | 프로그램 전체 공유 데이터 수정 | 부모 함수 내부의 상태(State) 수정 |
| 제약 사항 | 어디서든 선언 가능 | 반드시 중첩 함수 내부에서만 사용 가능 |
| 데이터 안전성 | 낮음 (오염 가능성 큼) | 높음 (특정 함수 내로 제한됨) |
3. 실전 예제: Counter 함수 만들기
가장 전형적인 nonlocal 활용 사례인 카운터 생성기를 통해 코드가 어떻게 달라지는지 확인해 보겠습니다.
Step 1: 오류가 발생하는 경우
def make_counter():
count = 0
def counter():
# 여기서 count += 1을 시도하면 UnboundLocalError 발생
count += 1
return count
return counter
Step 2: nonlocal을 적용한 올바른 예시
def make_counter():
count = 0
def counter():
nonlocal count # 부모 함수의 count 변수를 사용하겠다고 선언
count += 1
return count
return counter
my_count = make_counter()
print(my_count()) # 결과: 1
print(my_count()) # 결과: 2
print(my_count()) # 결과: 3
위 코드에서 count 변수는 make_counter 함수가 실행을 마친 뒤에도 메모리에 유지되며, counter 함수가 호출될 때마다 nonlocal 덕분에 안전하게 업데이트됩니다.
4. 고급 활용: 데코레이터와 상태 관리
전문적인 개발 환경에서 nonlocal은 주로 데코레이터(Decorator) 내에서 호출 횟수를 제한하거나 캐싱(Caching) 로직을 구현할 때 사용됩니다.
Sample Example: API 호출 제한 데코레이터
다음은 특정 함수가 일정 횟수 이상 호출되지 않도록 방어하는 로직입니다.
def limit_calls(max_calls):
remaining = max_calls
def decorator(func):
def wrapper(*args, **kwargs):
nonlocal remaining
if remaining > 0:
remaining -= 1
return func(*args, **kwargs)
else:
print(f"Error: '{func.__name__}' 호출 한도를 초과했습니다.")
return wrapper
return decorator
@limit_calls(3)
def fetch_data():
print("데이터를 성공적으로 가져왔습니다.")
fetch_data() # 1회
fetch_data() # 2회
fetch_data() # 3회
fetch_data() # "호출 한도를 초과했습니다." 출력
5. 주의사항 및 모범 사례
- 계층 구조 준수:
nonlocal은 바로 위의 부모 함수에 변수가 없으면 더 상위 단계로 거슬러 올라가며 변수를 찾습니다. 하지만 전역(Global) 범위까지 가지는 않습니다. - 가독성 유지: 중첩 구조가 너무 깊어지면
nonlocal이 가리키는 대상이 모호해질 수 있습니다. 3단계 이상의 중첩은 가급적 지양하고 클래스 구조를 검토하세요. - 단일 변수 원칙: 가능하면 변경이 필요한 최소한의 변수에만 사용하세요.
6. 결론
nonlocal 키워드는 파이썬의 스코프 구조를 이해하고 활용하는 데 있어 중추적인 역할을 합니다. 특히 함수형 프로그래밍 패러다임을 파이썬에 적용하거나, 가벼운 상태 유지가 필요한 클로저를 설계할 때 대체 불가능한 도구입니다. 전역 변수의 남용을 막고 코드의 응집도를 높이고 싶다면, 오늘부터 nonlocal을 적재적소에 배치해 보시기 바랍니다.
내용 출처
- Python Software Foundation - The
nonlocalstatement (Official Documentation) - Real Python - Python Scope & LEGB Rule
- Fluent Python by Luciano Ramalho - Closures and Decorators Chapter
'Artificial Intelligence > 60. Python' 카테고리의 다른 글
| [PYTHON] 상속(Inheritance)을 사용하는 이유는? 객체 지향의 정수를 맛보다 (0) | 2026.02.18 |
|---|---|
| [PYTHON] 인스턴스 변수와 클래스 변수의 완벽 이해 : 객체 지향 프로그래밍의 핵심 설계 전략 (0) | 2026.02.18 |
| [PYTHON] 파이썬 함수의 다중 반환값 마스터하기 : 튜플 패킹과 언패킹의 미학 (0) | 2026.02.17 |
| [PYTHON] 파이썬 유연함의 극치 : Asterisk(*)와 Double Asterisk(**) 언패킹 완벽 가이드 (0) | 2026.02.17 |
| [PYTHON] 객체 지향의 시작, __init__ 메서드의 본질과 설계 철학 완벽 분석 (0) | 2026.02.17 |