본문 바로가기
CS

[Python] 데코레이터와 클로저

by 왕밤빵도라에몽 2025. 4. 5.
728x90

함수의 데코레이터는 소스 코드의 함수에 표시해서 함수의 작동을 개선하게 해준다.
다만 이를 자유자재로 사용하기 위해 클로저의 이해가 필요하다.
클로저는 자신의 본체 바깥에서 정의된 변수를 함수가 포착해서 가져오는 기능이다.

데코레이터

다른 함수를 인수로 받는 콜러블

데코레이터 실행 시점

데코레이트된 함수가 정의된 직후에 실행
일반적으로 파이썬이 모듈을 로딩하는 시점, 즉 임포트 시에 실행

함수 데코레이터는 모듈이 임포트되지마자 실행되지만,
데코레이트된 함수는 명시적으로 호출될 때만 실행됨

등록 데코레이터

  • 일반적으로 데코레이터를 정의하는 모듈과 데코레이터를 적용하는 부분을 별도의 모듈에 둔다
  • 대부분의 데코레이터는 내부함수를 정의해 반환한다

    대부분의 데코레이터는 데코레이트된 함수를 변경한다.
    일반적으로 내부 함수를 정의해 반환함으로써 데코레이트된 함수를 대체한다.

클로저

함수 밖의 변수(외부 스코프)를 함수가 기억해서 사용할 수 있게 하는 함수

함수가 정의될 때 존재하던 자유 변수에 대한 바인딩을 유지하는 함수
함수를 정의하는 범위가 사라진 후 함수를 호출해도 자유 변수에 접근할 수 있음

함수가 비전역 외부 변수를 다루는 경우는
오로지 함수가 다른 함수 안에 정의되고 변수가 바깥쪽 함수의 지역 범위 안에 있을 때만
-> 그래서 많은 사람들이 클로저를 익명 함수와 혼동하기도 함

nonlocal 선언

"지역(local)은 아니지만 전역(global)도 아닌 변수", 즉 "바로 바깥 스코프에 있는 변수" 를 참조하고 수정할 때 사용

nonlocal의 배경

파이썬의 변수 스코프

  • local : 함수 내부
  • enclosing (nonlocal) : 중첩 함수의 외부 함수
  • global : 전역
  • built-in : 파이썬 기본 내장
def outer():
    x = 10
    def inner():
        x = 20  # 새로운 지역 변수 x 를 만든 것
        print(x)
    inner()
    print(x)

outer()
# 출력:
# 20
# 10

inner() 안에서 x = 20 을 하면 outer 의 x 가 아니라, inner 함수의 새로운 x 가 생성된다
따라서 outer 의 x 값은 그대로 10

함수 안에서 값을 할당하면 파이썬은 기본적으로 지역 변수로 간주함

이러한 배경에서 nonlocal이 등장함

def outer():
    x = 10
    def inner():
        nonlocal x
        x = 20
        print(x)
    inner()
    print(x)

outer()
# 출력:
# 20
# 20

이제 inner 함수 안에서 nonlocal x 를 선언하니까, 바깥 함수의 x 를 수정할 수 있게 됨

변수 조회 논리

  • global x 선언이 있으면 모듈 전역 변수 x를 가져오고 여기에 할당한다
  • nonlocal x 선언이 있으면 가장 가까운 상위 함수에 정의된 지역 변수 x를 가져오고 여기에 할당한다
  • x가 매개변수이거나 함수 본체 안에서 값이 할당되면 x는 지역 변수다

데코레이터 구현하기

코레이터를 사용해서 함수 실행 시간을 측정하는 예시 코드

import time
from typing import Callable, Any

def timer(func: Callable) -> Callable:
    def wrapper(*args: Any, **kwargs: Any) -> Any:
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"⏱️ 함수 '{func.__name__}' 실행 시간: {end_time - start_time:.4f}초")
        return result
    return wrapper

# 사용 예시
@timer
def slow_function():
    time.sleep(2)
    print("완료!")

slow_function()
완료!
⏱️ 함수 'slow_function' 실행 시간: 2.0001초
  • slow_function 함수가 timer 함수의 인자로 들어가고,
  • timer가 리턴하는 wrapper 함수가 원래 slow_function 을 대체

표준 라이브러리에서 제공하는 데코레이터

  • @staticmethod: 클래스 내부 독립 함수
  • @classmethod: 클래스 메서드 (cls 인자 사용)
  • @property: 메서드를 속성처럼 사용
  • @functools.lru_cache: 함수 결과 캐싱
  • @functools.wraps: 원본 함수 메타데이터 유지
  • @functools.cached_property: 속성 값 캐싱 (1회 계산 후 저장)
  • @functools.singledispatch: 타입마다 다른 로직을 적용 (오버로딩)

매개변수화된 데코레이터

함수 뿐만이 아니라 추가 인자를 받을 수 있음
데코레이터를 반환하는 함수를 만들어서 매개변수를 받을 수 있게 만듦

  • 일반 데코레이터 = 그냥 바로 데코레이터 꺼내 쓰기
  • 매개변수화된 데코레이터 = 내가 필요한 옵션을 고르고 데코레이터를 만드는 것

클래스 기반 데코레이터

클래스에 __call__ 메서드를 정의하면 객체가 함수처럼 호출 가능,
이걸 이용해서 데코레이터를 클래스 형태로 만들 수 있음
\

class Timer:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        import time
        start = time.time()
        result = self.func(*args, **kwargs)
        end = time.time()
        print(f"{self.func.__name__} 실행 시간: {end - start:.4f}초")
        return result

@Timer
def slow_function():
    import time
    time.sleep(2)
    print("완료!")

slow_function()

References

  • 『 전문가를 위한 파이썬(2판) 』, 루시아누 하말류, 한빛미디어, Part 1 - Chapter 9

어려웠다. 하지만 클래스 기반의 데코레이터는 다음에 활용해볼 기회가 있으면 좋겠다,

728x90