본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] 함수형 프로그래밍의 정수, 클로저(Closure) 정의와 nonlocal 활용 2가지 핵심 해결 방법

by Papa Martino V 2026. 4. 2.
728x90

클로저(Closure) 정의와 nonlocal 활용
클로저(Closure) 정의와 nonlocal 활용

 

 

파이썬(Python)에서 함수는 '일급 객체(First-class Object)'로 취급됩니다. 이는 함수를 인자로 전달하거나 리턴값으로 사용할 수 있음을 의미합니다. 이러한 특성 덕분에 파이썬 개발자는 클로저(Closure)라는 강력한 개념을 활용할 수 있습니다. 클로저는 자신을 둘러싼 외부 함수가 종료된 후에도 그 환경(데이터)을 기억하고 사용할 수 있는 특수한 함수를 말합니다.

하지만 클로저 내부에서 외부 함수의 변수를 수정하려고 할 때, 파이썬의 스코프(Scope) 규칙 때문에 의도치 않은 오류가 발생하곤 합니다. 이때 구원 투수로 등장하는 것이 바로 nonlocal 키워드입니다. 본 포스팅에서는 클로저의 구조적 특징과 nonlocal을 이용한 상태 관리 해결 전략을 7가지 실무 예제와 함께 심도 있게 살펴봅니다.


1. 클로저(Closure)와 변수 스코프의 메커니즘

클로저가 성립되기 위해서는 세 가지 조건이 충족되어야 합니다.

  • 해당 함수는 중첩 함수(Nested Function)여야 합니다.
  • 내부 함수는 외부 함수(Enclosing Function)의 영역에 있는 변수를 참조해야 합니다.
  • 외부 함수는 내부 함수를 반환(Return)해야 합니다.

파이썬 인터프리터는 내부 함수가 참조하는 외부 변수를 __closure__ 속성에 저장하여, 외부 함수가 호출 스택에서 사라진 뒤에도 데이터를 유지할 수 있게 합니다.


2. 클로저와 일반 함수의 결정적 차이 및 nonlocal 역할 비교

변수의 생명 주기와 수정 권한을 기준으로 두 개념의 차이를 표로 분석했습니다.

비교 항목 일반 중첩 함수 클로저 (Closure)
변수 생명 주기 외부 함수 종료 시 소멸 함수 객체와 함께 메모리 유지
상태 유지 능력 불가능 (매번 초기화) 가능 (객체처럼 사용)
nonlocal 키워드 단순 참조 시 불필요 외부 변수 '수정' 시 필수적
메모리 할당 스택(Stack) 영역 위주 셀(Cell) 객체를 통한 힙(Heap) 유지
사용 사례 코드 분할, 단순 캡슐화 데코레이터, 데이터 은닉, 팩토리 패턴

3. 실무 최적화 Sample Examples (7가지 적용 시나리오)

단순한 카운터를 넘어 실제 프로젝트에서 클로저와 nonlocal이 어떻게 문제 해결에 기여하는지 확인해 보세요.

Example 1: 데이터 은닉을 구현한 카운터 (Basic Closure)

전역 변수 없이 함수 내부 상태를 안전하게 격리하여 증가시킵니다.

def make_counter():
    count = 0
    def counter():
        nonlocal count  # 외부 함수의 변수를 수정하기 위해 사용
        count += 1
        return count
    return counter

c = make_counter()
print(c()) # 1
print(c()) # 2

Example 2: 이동 평균 계산기 (Moving Average)

실시간으로 들어오는 데이터의 평균값을 메모리 효율적으로 계산합니다.

def make_averager():
    series = []
    def averager(new_value):
        series.append(new_value)  # 가변 객체(List)는 nonlocal 없이 수정 가능
        return sum(series) / len(series)
    return averager

avg = make_averager()
print(avg(10)) # 10.0
print(avg(20)) # 15.0

Example 3: nonlocal을 이용한 런타임 설정 변경

클로저가 기억하는 환경 설정을 외부에서 동적으로 변경하는 패턴입니다.

def set_threshold(initial_limit):
    limit = initial_limit
    def checker(value):
        nonlocal limit
        if value > limit:
            return f"Over Limit (Current: {limit})"
        return "Safe"
    
    def update_limit(new_limit):
        nonlocal limit
        limit = new_limit
    
    checker.update = update_limit
    return checker

check = set_threshold(100)
print(check(120)) # Over Limit
check.update(200)
print(check(120)) # Safe

Example 4: 데코레이터(Decorator)와 함수의 상태 기억

함수가 몇 번 호출되었는지 기록하는 성능 분석 데코레이터입니다.

def count_calls(func):
    calls = 0
    def wrapper(*args, **kwargs):
        nonlocal calls
        calls += 1
        print(f"Call {calls} to {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@count_calls
def say_hello():
    pass

say_hello()
say_hello()

Example 5: 특정 이벤트에 대한 '한 번만 실행(Once)' 보장

플래그 변수를 클로저에 숨겨 함수가 중복 실행되는 것을 방지합니다.

def run_once(func):
    has_run = False
    def wrapper(*args, **kwargs):
        nonlocal has_run
        if not has_run:
            has_run = True
            return func(*args, **kwargs)
        print("Function already executed.")
    return wrapper

@run_once
def initialize_db():
    print("Database Initialized!")

initialize_db()
initialize_db() # Function already executed.

Example 6: HTML 태그 생성기 (Factory Pattern)

특정 설정을 고정시킨 여러 종류의 함수를 찍어냅니다.

def tag_maker(tag):
    def wrap_content(content):
        return f"<{tag}>{content}</{tag}>"
    return wrap_content

h1 = tag_maker('h1')
p = tag_maker('p')
print(h1('Hello')) # <h1>Hello</h1>

Example 7: 비동기 처리에서의 상태 보존 (Callback Style)

콜백 함수가 실행될 때 당시의 컨텍스트를 유지해야 하는 경우입니다.

def process_data(data_id):
    status = "pending"
    def on_complete():
        nonlocal status
        status = "completed"
        print(f"Data {data_id} is now {status}")
    return on_complete

callback = process_data(42)
# 시간이 흐른 뒤 실행되어도 data_id 42와 status 변수에 접근 가능
callback()

4. 해결 전략: global vs nonlocal의 차이점

변수를 수정할 때 global은 전역 범위의 변수를 찾고, nonlocal은 현재 함수의 바로 바깥쪽 스코프(Enclosing scope)부터 찾기 시작합니다. nonlocal은 모듈 전체에 영향을 주지 않으면서 특정 함수군 사이의 상태를 공유하게 해주므로, 객체지향의 '캡슐화'를 함수형 언어 방식으로 구현할 때 매우 중요한 도구입니다.


5. 참고 문헌 및 자료 출처

  • Python Software Foundation. "Python Language Reference: Scoping Rules & non-local".
  • Luciano Ramalho. "Fluent Python". O'Reilly Media (Chapter 7: Closures and Decorators).
  • Brett Slatkin. "Effective Python: 91 Specific Ways to Write Better Python".
  • Real Python Tutorials - "Python Closures: A Step-by-Step Guide".
728x90