← Back to Blog

[Python] Decorator

computer science > programming

2026-07-063 min read

#computer-science #programming #python #decorator

Decorator란?

decorator는 함수나 class에 추가 동작을 붙이는 Python 문법이다.

예를 들어 어떤 함수가 실행될 때마다 log를 찍고 싶다고 하자. 함수 내부마다 print()를 직접 넣을 수도 있지만, decorator를 사용하면 공통 동작을 함수 밖에서 감쌀 수 있다.

def log(func):
    def wrapper():
        print("before")
        func()
        print("after")

    return wrapper

이제 함수를 decorator로 감싼다.

@log
def hello():
    print("hello")

호출하면 다음 순서로 실행된다.

hello()
before
hello
after

@ 문법의 의미

Decorator 문법은 사실 함수 재할당을 짧게 쓴 것이다.

다음 코드는

@log
def hello():
    print("hello")

아래 코드와 거의 같다.

def hello():
    print("hello")


hello = log(hello)

@loghello 함수를 log() 함수에 넣고, 그 결과를 다시 hello라는 이름에 넣는 문법이다.

원래 함수 -> decorator 함수 -> wrapper 함수

그래서 decorator는 함수를 인자로 받고, 보통 새로운 함수를 반환한다.


함수도 객체다

Decorator를 이해하려면 Python에서 함수도 객체라는 점을 알아야 한다.

함수를 변수에 담을 수 있다.

def hello():
    print("hello")


func = hello
func()

함수를 다른 함수의 인자로 넘길 수도 있다.

def run(func):
    func()


run(hello)

함수 안에서 함수를 만들고 반환할 수도 있다.

def outer():
    def inner():
        print("inner")

    return inner


func = outer()
func()

Decorator는 이 성질을 이용한다.


인자가 있는 함수 감싸기

실제 함수는 대부분 인자를 가진다. 이때 wrapper도 인자를 받아야 한다.

def log(func):
    def wrapper(name):
        print("before")
        result = func(name)
        print("after")
        return result

    return wrapper

사용 예시는 다음과 같다.

@log
def greet(name: str) -> str:
    return f"hello, {name}"


message = greet("Alice")
print(message)

하지만 이 방식은 인자 개수가 바뀌면 wrapper도 수정해야 한다.

그래서 보통 *args, **kwargs를 사용한다.

def log(func):
    def wrapper(*args, **kwargs):
        print("before")
        result = func(*args, **kwargs)
        print("after")
        return result

    return wrapper

이렇게 하면 다양한 함수에 같은 decorator를 적용할 수 있다.


functools.wraps

Decorator를 사용할 때는 functools.wraps를 함께 쓰는 것이 좋다.

그냥 wrapper를 반환하면 원래 함수의 이름과 docstring 같은 metadata가 사라진다.

def log(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)

    return wrapper
@log
def add(a: int, b: int) -> int:
    """Add two numbers."""
    return a + b


print(add.__name__)

출력은 원래 함수 이름인 add가 아니라 wrapper가 된다.

wrapper

wraps를 사용하면 원래 함수의 metadata를 유지할 수 있다.

from functools import wraps


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

    return wrapper

이제 add.__name__add로 유지된다.

add

Decorator를 직접 만들 때는 특별한 이유가 없다면 @wraps(func)를 붙이는 것이 좋다.


실행 시간 측정 decorator

Decorator는 실행 시간 측정 같은 공통 기능에 잘 맞는다.

import time
from functools import wraps


def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()

        print(f"{func.__name__}: {end - start:.6f}s")
        return result

    return wrapper

사용 예시는 다음과 같다.

@timer
def work():
    total = 0
    for i in range(1_000_000):
        total += i
    return total


work()

함수 내부 로직을 바꾸지 않고 실행 시간 측정 기능을 붙일 수 있다.


인증 확인 decorator

웹 서버에서는 인증이나 권한 확인에도 decorator를 많이 사용한다.

예를 들어 단순한 예시는 다음과 같다.

from functools import wraps


def login_required(func):
    @wraps(func)
    def wrapper(user, *args, **kwargs):
        if user is None:
            raise PermissionError("login required")

        return func(user, *args, **kwargs)

    return wrapper

사용 예시는 다음과 같다.

@login_required
def get_profile(user):
    return {"name": user["name"]}
user = {"name": "Alice"}
profile = get_profile(user)

실제 Flask, FastAPI, Django 같은 framework에서도 비슷한 패턴을 자주 볼 수 있다.


인자를 받는 decorator

Decorator 자체에 인자를 넘기고 싶을 때가 있다.

예를 들어 권한 이름을 decorator에 넘긴다고 하자.

@require_role("admin")
def delete_user(user_id: int):
    ...

이런 decorator는 함수가 한 겹 더 필요하다.

from functools import wraps


def require_role(role: str):
    def decorator(func):
        @wraps(func)
        def wrapper(user, *args, **kwargs):
            if user.get("role") != role:
                raise PermissionError(f"{role} role required")

            return func(user, *args, **kwargs)

        return wrapper

    return decorator

호출 구조는 다음과 같다.

require_role("admin") -> decorator 반환
decorator(delete_user) -> wrapper 반환
delete_user = wrapper

즉 인자를 받는 decorator는 다음 형태를 가진다.

def decorator_factory(option):
    def decorator(func):
        def wrapper(*args, **kwargs):
            return func(*args, **kwargs)

        return wrapper

    return decorator

여러 decorator를 함께 사용하기

하나의 함수에 여러 decorator를 붙일 수도 있다.

@decorator_a
@decorator_b
def func():
    pass

이 코드는 다음과 같다.

def func():
    pass


func = decorator_a(decorator_b(func))

가까운 decorator인 decorator_b가 먼저 적용되고, 그 결과에 decorator_a가 적용된다.

실행 순서를 예로 보면 다음과 같다.

from functools import wraps


def a(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("a before")
        result = func(*args, **kwargs)
        print("a after")
        return result

    return wrapper


def b(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("b before")
        result = func(*args, **kwargs)
        print("b after")
        return result

    return wrapper
@a
@b
def hello():
    print("hello")


hello()

출력은 다음과 같다.

a before
b before
hello
b after
a after

class method 관련 decorator

Python class에서 자주 보는 decorator도 있다.

decorator의미
@staticmethodinstance 없이 호출 가능한 method
@classmethodclass 자체를 첫 번째 인자로 받는 method
@propertymethod를 attribute처럼 접근

@staticmethod

@staticmethodselfcls를 받지 않는다.

class Math:
    @staticmethod
    def add(a: int, b: int) -> int:
        return a + b
print(Math.add(1, 2))

class 안에 넣었지만, 실제로는 독립 함수에 가깝다.

@classmethod

@classmethod는 첫 번째 인자로 class를 받는다. 보통 대체 생성자에 사용한다.

class User:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

    @classmethod
    def from_dict(cls, data: dict):
        return cls(name=data["name"], age=data["age"])
user = User.from_dict({"name": "Alice", "age": 20})

여기서 clsUser class를 의미한다.

@property

@property는 method를 attribute처럼 접근하게 만든다.

class Rectangle:
    def __init__(self, width: int, height: int):
        self.width = width
        self.height = height

    @property
    def area(self) -> int:
        return self.width * self.height
rect = Rectangle(3, 4)
print(rect.area)

rect.area()가 아니라 rect.area로 접근한다.


class decorator

Decorator는 함수뿐 아니라 class에도 사용할 수 있다.

def add_repr(cls):
    def __repr__(self):
        return f"{cls.__name__}({self.__dict__})"

    cls.__repr__ = __repr__
    return cls
@add_repr
class User:
    def __init__(self, name: str):
        self.name = name
user = User("Alice")
print(user)

class decorator는 class를 인자로 받고, 수정된 class나 새로운 class를 반환한다. @dataclass도 class decorator의 대표적인 예시이다.

from dataclasses import dataclass


@dataclass
class Point:
    x: int
    y: int

decorator를 쓸 때 주의할 점

Decorator는 편리하지만 남용하면 코드 흐름을 숨긴다.

주의할 점은 다음과 같다.

주의점설명
metadata 손실functools.wraps를 사용한다
실행 흐름 숨김decorator 안에서 너무 많은 일을 하지 않는다
debugging 어려움wrapper가 여러 겹이면 stack trace가 복잡해진다
인자 처리 실수*args, **kwargs를 적절히 사용한다
순서 의존성여러 decorator를 쓸 때 적용 순서를 확인한다

Decorator는 logging, timing, caching, permission check처럼 공통 관심사를 분리할 때 효과적이다. 하지만 핵심 비즈니스 로직을 decorator 안에 숨기면 코드를 읽기 어려워진다.


정리

Python decorator는 함수나 class에 추가 동작을 붙이는 문법이다.

핵심은 다음과 같다.

  1. @decoratorfunc = decorator(func)와 같은 의미이다.
  2. decorator는 함수를 인자로 받고 함수를 반환한다.
  3. 일반적으로 wrapper 함수 안에서 원래 함수를 호출한다.
  4. 인자를 받는 함수에는 *args, **kwargs를 사용한다.
  5. 직접 decorator를 만들 때는 functools.wraps를 사용하는 것이 좋다.
  6. decorator 자체에 인자가 필요하면 함수를 한 겹 더 감싼다.
  7. @staticmethod, @classmethod, @property, @dataclass도 decorator이다.

Decorator는 반복되는 공통 동작을 함수 밖으로 분리할 때 유용하다. 다만 코드 흐름을 숨길 수 있으므로, 사용 목적이 명확할 때 적용하는 것이 좋다.