너도 할 수 있는, 너도밤나무 코딩

[Python] 데코레이터(Decorator) 완전 정복 본문

프로그래밍 언어/파이썬

[Python] 데코레이터(Decorator) 완전 정복

곡마일장 2025. 9. 11. 06:50
반응형

파이썬에서 데코레이터는 함수나 클래스의 동작을 수정하거나 기능을 추가할 수 있는 매우 강력한 기능입니다. 블로그에서 독자에게 소개할 때는 단순 코드 설명을 넘어, 개념과 활용 사례, 설계 팁을 함께 제공하면 이해도가 높아집니다.

이번 글에서는 다음을 다룹니다:

  • 데코레이터 기본 개념과 필요성
  • 함수형 데코레이터와 클래스형 데코레이터
  • 파라미터 전달과 고급 활용
  • 실전 예제: 실행 시간 측정, 로깅
  • 설계 팁과 베스트 프랙티스

1. 데코레이터란 무엇인가?

데코레이터는 함수를 입력으로 받아 새로운 함수를 반환하는 함수입니다. 이를 통해 기존 함수를 감싸서 추가 기능을 적용할 수 있습니다.

예를 들어, 어떤 함수가 실행될 때마다 로그를 남기거나 실행 시간을 측정하고 싶다면, 함수를 직접 수정하지 않고 데코레이터를 적용할 수 있습니다.

# 기본 데코레이터 예제
def simple_decorator(func):
    def wrapper():
        print("함수 실행 전")
        func()
        print("함수 실행 후")
    return wrapper

def say_hello():
    print("안녕하세요!")

decorated = simple_decorator(say_hello)
decorated()
  • 여기서 say_hello를 감싸는 wrapper 함수가 실행 전후 로직을 담당합니다.
  • 결과적으로 코드 중복 없이 공통 기능을 적용할 수 있습니다.

2. @ 문법으로 간단히 적용하기

데코레이터를 적용할 때 @decorator_name 문법을 사용하면 코드가 훨씬 간결해집니다.

@simple_decorator
def greet():
    print("반갑습니다!")

greet()
  • 위 코드는 greet = simple_decorator(greet)와 동일합니다.
  • 블로그에서는 @ 문법의 편리함과 가독성 향상을 강조하면 좋습니다.

3. 인자를 받는 함수 데코레이터

데코레이터가 적용되는 함수가 인자를 받는 경우, wrapper 함수에서도 동일하게 인자를 받아야 합니다.

def decorator_with_args(func):
    def wrapper(name):
        print("함수 실행 전")
        func(name)
        print("함수 실행 후")
    return wrapper

@decorator_with_args
def hello_name(name):
    print(f"{name}님, 안녕하세요!")

hello_name("철수")
  • 독자에게 *args, **kwargs를 활용하면 모든 함수 인자를 지원할 수 있다고 안내하면 실용적입니다.

4. 파라미터를 받는 데코레이터

데코레이터 자체에 인자를 전달하고 싶을 때는 중첩 함수를 사용합니다.

def repeat(num_times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(num_times):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(num_times=3)
def say_hi():
    print("안녕!")

say_hi()
  • 블로그에서는 반복 실행, 로깅 레벨 설정 등 실무 활용 사례를 함께 소개하면 좋습니다.

5. 클래스형 데코레이터

데코레이터를 클래스 형태로 구현하면 상태 유지가 용이합니다.

class CountCalls:
    def __init__(self, func):
        self.func = func
        self.num_calls = 0

    def __call__(self, *args, **kwargs):
        self.num_calls += 1
        print(f"{self.func.__name__} 호출 횟수: {self.num_calls}")
        return self.func(*args, **kwargs)

@CountCalls
def greet(name):
    print(f"{name}님, 안녕하세요!")

greet("철수")
greet("영희")
  • 블로그에서는 클래스형 데코레이터의 장점, 즉 상태 추적, 파라미터 저장 등을 강조하면 좋습니다.

6. 실전 예제

1) 실행 시간 측정

import time

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

@timer
def long_task():
    time.sleep(2)
    print("작업 완료!")

long_task()

2) 로깅 데코레이터

def logger(func):
    def wrapper(*args, **kwargs):
        print(f"[LOG] {func.__name__} 호출됨")
        return func(*args, **kwargs)
    return wrapper

@logger
def add(a, b):
    return a + b

print(add(3, 5))
  • 블로그에서는 실무에서 흔히 쓰이는 기능을 보여주는 것이 독자 친화적입니다.

7. 설계 팁

  1. functools.wraps 사용
    • 함수 메타데이터(__name__, __doc__) 유지
from functools import wraps

def decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper
  1. 유연한 인자 처리: *args, **kwargs 필수
  2. 데코레이터 순서: 여러 데코레이터 적용 시 순서 중요
  3. 상태가 필요한 경우 클래스형 데코레이터 사용

8. 결론

  • 데코레이터는 코드 중복 제거, 기능 확장, 전처리/후처리 적용에 유용합니다.
  • 함수형과 클래스형 모두 장점이 있으며, 상황에 맞게 선택하면 됩니다.
  • 블로그에서는 개념 + 코드 + 활용 사례 + 설계 팁을 함께 제공하면 독자 이해도를 크게 높일 수 있습니다.
반응형