본문 바로가기
프로그래밍/Python

Python 함수 위 @ - 데코레이터 란

by 허구의 2025. 7. 15.
728x90

Python 코드를 보다 보면 종종 함수나 메서드 위에 @ 기호로 시작하는 이상한(?) 줄을 보게 됩니다. 예를 들면 다음과 같은 형태입니다:

@my_decorator
def my_function():
    ...

 

이것은 데코레이터(decorator)라는 문법으로, 기존 함수나 클래스의 동작을 수정하거나 확장할 수 있게 해주는 Python의 강력한 기능입니다. 본 글에서는 데코레이터의 개념, 기본 사용법, 자주 쓰는 내장 데코레이터들, 그리고 직접 만드는 방법을 다루려고 합니다.

 

데코레이터란?

데코레이터는 다른 함수를 인자로 받아 새로운 기능을 추가한 뒤 반환하는 함수입니다. 흔히 함수에 어떤 "포장"을 입혀주는 역할을 합니다.

 

예를 들어, 아래와 같은 데코레이터 함수가 있다고 합시다:

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

 

이제 이 데코레이터를 @my_decorator로 사용하면:

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

 

위 코드는 아래와 동일합니다:

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

say_hello = my_decorator(say_hello)

 

즉, say_hello()를 실행하면 이렇게 출력됩니다:

함수 실행 전
안녕하세요!
함수 실행 후

함수에 인자가 있을 경우

함수에 인자가 있는 경우, *args**kwargs를 이용하면 데코레이터를 더 일반화할 수 있습니다.

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("함수 실행 전")
        result = func(*args, **kwargs)
        print("함수 실행 후")
        return result
    return wrapper

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

print(add(3, 4))

 

출력 결과:

함수 실행 전
함수 실행 후
7

자주 사용하는 내장 데코레이터

  • @staticmethod: 인스턴스 없이 호출 가능한 정적 메서드
class MathUtil:
    @staticmethod
    def add(a, b):
        return a + b

# 인스턴스를 만들지 않아도 호출 가능
result = MathUtil.add(3, 5)
print(result)  # 출력: 8

 

  • @classmethod: 클래스 자신(cls)을 첫 번째 인자로 받는 메서드
class Person:
    count = 0

    def __init__(self, name):
        self.name = name
        Person.count += 1

    @classmethod
    def how_many(cls):
        return f"현재 인원 수: {cls.count}"

p1 = Person("Alice")
p2 = Person("Bob")

print(Person.how_many())  # 출력: 현재 인원 수: 2

 

  • @property: 메서드를 속성처럼 접근 가능하게 만듦
class MyClass:
    @property
    def name(self):
        return "홍길동"

obj = MyClass()
print(obj.name)  # name()이 아닌 name으로 호출

 

  • @functools.lru_cache: 함수 결과를 캐싱하여 성능 향상
import functools

@functools.lru_cache(maxsize=None)
def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(30))  # 매우 빠르게 계산됨 (결과: 832040)

데코레이터에 인자 전달하기 (중첩 함수)

데코레이터 자체에 인자를 넘기고 싶을 때는 한 번 더 함수를 감싸면 됩니다.

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

@repeat(3)
def greet():
    print("안녕하세요!")

 

결과:

안녕하세요!
안녕하세요!
안녕하세요!

functools.wraps를 꼭 써야 할까?

Python의 functools 모듈에서 @wraps 데코레이터를 사용하면, 원래 함수의 이름, docstring 등을 유지할 수 있습니다.

from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

 

이렇게 하지 않으면 데코레이터가 적용된 함수의 이름이나 문서가 모두 wrapper로 바뀌기 때문에 디버깅이나 문서화에서 문제가 생길 수 있습니다.

 

감사합니다!

728x90