파이썬/기초 프로그래밍

파이썬 데코레이터와 클로저 정리

Data Jun 2025. 9. 8. 14:17

1. 클로저 관점에서 보기

def perf_clock(func):          
    def perf_clocked(*args):   # func 캡처 → 클로저
        ...
        result = func(*args)
        ...
    return perf_clocked

 

데코레이터는 사실상 **클로저(Closure)**를 활용합니다.
예제 코드에서 perf_clock 함수는 내부에 perf_clocked라는 함수를 정의하고 반환합니다.
이때 perf_clocked는 외부 함수의 변수 func, 그리고 호출 시 전달되는 *args를 캡처하여 계속 사용할 수 있습니다.

즉, 데코레이터는 클로저 구조 덕분에 원본 함수를 수정하지 않고 기능을 덧붙일 수 있는 것입니다.

 

2. 실행 시간 측정 예제

아래 코드는 데코레이터 perf_clock을 사용해 함수 실행 시간을 측정하는 예제입니다.

import time

def perf_clock(func):
    def perf_clocked(*args):
        # 함수 시작 시간 
        st = time.perf_counter() 
        result = func(*args)
        # 함수 종료 시간 계산
        et = time.perf_counter() - st 
        # 실행 함수명
        name = func.__name__
        # 함수 매개변수 
        arg_str = ', '.join(repr(arg) for arg in args)
        # 결과 출력
        print('[%0.5fs] %s(%s) -> %r' % (et, name, arg_str, result)) 
        return result 
    return perf_clocked

@perf_clock
def time_func(seconds):
    time.sleep(seconds)

@perf_clock
def sum_func(*numbers):
    return sum(numbers)

 

1️⃣  데코레이터 미사용 (직접 감싸기)

none_deco1 = perf_clock(time_func)
none_deco2 = perf_clock(sum_func)

print(none_deco1, none_deco1.__code__.co_freevars)
print(none_deco2, none_deco2.__code__.co_freevars)

print('-' * 40, 'Called None Decorator -> time_func')
none_deco1(1.5)

print('-' * 40, 'Called None Decorator -> sum_func')
none_deco2(100, 150, 250, 300, 350)

실행 결과 예시

<function perf_clock.<locals>.perf_clocked at 0x...> ('func',)
<function perf_clock.<locals>.perf_clocked at 0x...> ('func',)
---------------------------------------- Called None Decorator -> time_func
[1.50012s] time_func(1.5) -> None
---------------------------------------- Called None Decorator -> sum_func
[0.00001s] sum_func(100, 150, 250, 300, 350) -> 1150

 

2️⃣ 데코레이터 사용

print('*' * 40, 'Called Decorator -> time_func')
time_func(1.5)

print('*' * 40, 'Called Decorator -> sum_func')
sum_func(100, 150, 250, 300, 350)

실행 결과 예시

**************************************** Called Decorator -> time_func
[1.50010s] time_func(1.5) -> None
**************************************** Called Decorator -> sum_func
[0.00001s] sum_func(100, 150, 250, 300, 350) -> 1150

@perf_clock을 붙이면 원래 함수 코드를 수정하지 않고도 동일한 기능(실행 시간 측정)을 손쉽게 추가할 수 있습니다.

 

3. 데코레이터의 장점

  • 중복 제거 & 코드 간결화
    여러 함수에 같은 부가기능(예: 실행 시간 측정, 로깅)을 반복해서 작성할 필요가 없음.
  • 공통 기능 분리
    로깅, 권한 체크, 입력값 검증 같은 기능을 쉽게 모듈화 가능.
  • 조합 가능
    여러 데코레이터를 겹쳐서 붙일 수 있어 기능 확장이 용이함.

 

4. 데코레이터의 단점

  • 가독성 저하 가능성
    @데코레이터 문법만 보고는 내부에서 어떤 일이 벌어지는지 한눈에 알기 어렵기 때문에, 처음 접하는 사람에게는 가독성이 떨어질 수 있음.
  • 특정 기능 전용이라면 불필요
    단 한두 개의 함수에서만 쓰는 기능이라면, 굳이 데코레이터로 만들기보다는 그냥 함수 안에서 직접 작성하는 게 직관적임.
  • 디버깅 불편함
    호출되는 함수가 사실상 클로저로 감싸진 다른 함수이기 때문에, 에러 추적 시 원래 함수 이름이나 위치를 바로 파악하기 어려울 수 있음.

 

 

정리하면

  • 데코레이터는 *클로저(func, args 캡처) 덕분에 가능하다.
  • 장점: 중복 제거, 코드 간결, 공통 기능 모듈화, 조합 가능.
  • 단점: 가독성이 떨어질 수 있고, 디버깅이 불편하며, 단발성 기능에는 불필요.

즉, 공통 기능을 여러 함수에 일관되게 적용할 때는 강력한 도구, 하지만 단순하고 제한적인 기능이라면 굳이 사용할 필요는 없습니다.