[Python] 데코레이터(decorator)란?

1. 데코레이터란?

 

데코레이터(decorator)는 함수나 메서드에 적용되어, 해당 함수나 메서드의 기능을 확장하거나 변경하는 역할을 합니다. 데코레이터는 일반적을@기호와 함께 사용되며, 함수 또는 메서드 위에 위치합니다. 데코레이터는 기본적으로 함수를 인자로 받고, 또 다른 함수를 반환하는 고차 함수(higher-order function)입니다.  

 


1.1 데코레이터 작동 원리

 

my_decorator 함수는 데코레이터 함수로 hello 함수를 인자로 받아 wrapper 함수를 반환합니다. decorated_hello를 호출하면 데코레이터가 추가한 내용과 함께 hello 함수의 내용이 출력됩니다.

 

def my_decorator(func):
    def wrapper():
        print("데코레이터가 추가한 내용")
        func()
        print("데코레이터가 추가한 내용")
    return wrapper

def hello():
    print("안녕하세요")

decorated_hello = my_decorator(hello)
decorated_hello()



@기호를 이용하면 간단하고 직관적이게 코드를 작성할 수 있으며 동일한 효과를 볼 수 있습니다.

 

def my_decorator(func):
    def wrapper():
        print("데코레이터가 추가한 내용")
        func()
        print("데코레이터가 추가한 내용")
    return wrapper

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

hello()

 


2. 데코레이터 예제

2.1 타이머 데코레이터

 

타이머 데코레이터는 함수의 실행 시간을 측정하는데 사용됩니다. 아래 코드는 실행 시간을 측정하는 데코레이터를 정의하고, @ 기호를 사용하여 함수에 적용한 코드입니다. timer_decorator 함수는 함수를 인자로 받아 해당 함수를 실행하는데 걸리는 시간을 측정하여 출력하는 함수입니다. wrapper 함수에서는 func 함수를 실행하기 전과 후에 시간을 측정하고, 실행 시간을 출력합니다. 그리고 func 함수를 실행한 결과를 반환합니다.

 

import time

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

@timer_decorator
def example_function():
    time.sleep(1)

example_function() #example_function 실행 시간: 1.00293초

 


2.2 로깅 데코레이터

 

로깅 데코레이터는 함수 호출시 로그를 기록하는데 사용됩니다. logging_decorator 함수는 함수를 인자로 받아 해당 함수가 호출되고 종료될 때 로그 정보를 출력하는 함수입니다. wrapper 함수에서는 func 함수가 호출되기 전과 후에 로그 정보를 출력하고, func 함수를 실행한 결과를 반환합니다.

 

import logging

def logging_decorator(func):
    def wrapper(*args, **kwargs):
        logging.info(f"{func.__name__} 호출됨")
        result = func(*args, **kwargs)
        logging.info(f"{func.__name__} 종료됨")
        return result
    return wrapper

@logging_decorator
def example_function():
    print("예제 함수 실행")

example_function()

 


3. 인자가 있는 데코레이터 예제

 

인자가 있는 데코레이터를 사용하면 더 유연하게 데코레이터를 사용할 수 있습니다.

 


3.1 함수 호출 횟수 제한 데코레이터

 

limit_calls_decorator 함수는 함수 호출 횟수를 제한하는 데코레이터를 반환하는 함수입니다. decorator 함수에서는 calls 변수를 사용하여 함수 호출 횟수를 세고, max_calls보다 작은 경우에만 func 함수를 실행하고 calls 변수를 증가시킵니다. max_calls보다 크거나 같은 경우에는 예외를 발생시킵니다.



example_function 함수에 @limit_calls_decorator(3) 데코레이터를 적용하여 실행하면, 최대 3번까지만 함수를 실행할 수 있습니다. for 루프에서 example_function 함수를 5번 호출하면, 처음 3번은 함수가 정상적으로 실행되고, 그 이후 2번은 raise Exception("함수 호출 횟수 초과") 구문에서 예외가 발생하므로, 예외 메시지가 출력됩니다. 

 

def limit_calls_decorator(max_calls):
    def decorator(func):
        calls = 0

        def wrapper(*args, **kwargs):
            nonlocal calls
            if calls < max_calls:
                calls += 1
                return func(*args, **kwargs)
            else:
                raise Exception("함수 호출 횟수 초과")

        return wrapper

    return decorator

@limit_calls_decorator(3)
def example_function():
    print("예제 함수 실행")

for i in range(5):
    try:
        example_function()
    except Exception as e:
        print(e)

 


3.2 권한 확인 데코레이터

 

permission_required_decorator 함수는 권한이 있는 사용자만 함수를 실행할 수 있도록 하는 데코레이터를 반환하는 함수입니다. decorator 함수에서는 permission 인자를 사용하여 사용자의 권한을 확인하고, func 함수를 실행하거나 예외를 발생시킵니다.



read_function 함수에 @permission_required_decorator('read') 데코레이터를 적용하여 실행하면, user_permissions 리스트에 'read'가 포함되어 있으므로 함수가 정상적으로 실행됩니다. 반면에, delete_function 함수에 @permission_required_decorator('delete') 데코레이터를 적용하여 실행하면, user_permissions 리스트에 'delete'가 포함되어 있지 않으므로 raise Exception("권한이 없습니다.") 구문에서 예외가 발생하고, 예외 메시지가 출력됩니다.

 

def permission_required_decorator(permission):
    def decorator(func):
        def wrapper(*args, **kwargs):
            user_permissions = ['read', 'write']
            if permission in user_permissions:
                return func(*args, **kwargs)
            else:
                raise Exception("권한이 없습니다.")
        return wrapper
    return decorator

@permission_required_decorator('read')
def read_function():
    print("읽기 함수 실행")

@permission_required_decorator('delete')
def delete_function():
    print("삭제 함수 실행")

try:
    read_function()
    delete_function()
except Exception as e:
    print(e)

 



4. 데코레이터를 사용하는 이유

4.1 코드 재사용성 향상

 

데코레이터를 사용하면 코드의 일부분을 여러 함수에서 공유할 수 있습니다. 이로 인해 코드의 재사용성이 향상되며, 유지 보수도 용이해집니다.

 


4.2 코드 가독성 향상

 

데코레이터를 사용하면 기능을 확장하거나 변경하는 코드를 함수와 분리할 수 있습니다. 이로 인해 코드의 가독성이 향상되고, 코드를 이해하기 쉬워집니다.

 


4.3 관심사의 분리

 

데코레이터를 사용하면 관심사를 분리할 수 있습니다. 예를 들어, 로깅이나 타이머와 같은 기능은 데코레이터를 사용하여 함수와 분리할 수 있습니다. 이로 인해 함수는 핵심 기능에만 집중할 수 있습니다.

 


5. 데코레이터의 주의 사항

 

데코레이터를 사용할 때 함수의 메타데이터가 변경될 수 있습니다. 이를 방지하기 위해 `functools.wraps`를 사용하여 원래 함수의 메타데이터를 보존할 수 있습니다. 또한 데코레이터의 실행 순서에 주의해야 합니다. 여러 데코레이터를 사용할 때 데코레이터가 적용되는 순서가 중요할 수 있으므로, 데코레이터를 올바른 순서로 적용해야 합니다.