본문 바로가기
[파이썬]/함수

[파이썬][함수] 데코레이터 함수

by sung min_Kim 2023. 11. 15.

set_decorator_function 

데코레이터 함수

함수를 '장식'하는 기능


· 데코레이터를 사용하는 이유는 ?


 기존 함수의 동작을 바꾸지 않으면서 추가적인 기능을 부여할 때 사용한다.
 또한 특정 기능을 여러 함수에서 공통적으로 사용해야 할 때, 그 기능을 한 곳에 모아서 관리한다.
 이는 코드의 중복을 줄여주고, 코드의 재사용성과 가독성, 유지보수성을 향상한다.

 이를테면, 데코레이터를 사용하여 함수의 실행 시간을 측정하거나, 로그를 출력하거나, 인자의 유효성 검사하는 등의 작업을 수행할 수 있다.

 


· 기본 형태


 데코레이터의 기본 형태는 크게 두 부분으로 나뉜다. 바로 데코레이터 함수와 래퍼 함수로 말이다.

  1. 데코레이터 함수 : 데코레이터 함수는 외부 함수이며, 함수를 인자로 받아 사용한다. 해당 데코레이터 함수 내부에 래퍼 함수를 정의하고, 이를 반환한다.
    ('func'는 데코레이터를 적용한 원래의 함수를 받는 역할을 수행하는 매개 변수이다.)


  2. 래퍼 함수(wrapper) : 래퍼 함수는 데코레이터 함수 내부에 정의되며, 데코레이터가 적용된 원래의 함수를 '감싸는' 역할을 수행한다. 즉, 원래 함수의 실행 전후에 추가적인 기능을 부여한다.


  3. 데코레이터 : @ 기호는 파이썬에서 데코레이터를 사용하겠다는 표시이다. 해당 기호 뒤에 데코레이터에 적용될 함수를 입력함으로써, 그 아래에 정의된 원래의 함수를 '장식' 하겠다는 의미를 가진다. 해당 함수는 인자로서 데코레이터 함수에게 전달된다. 이렇게 하면 데코레이터 함수는 원래의 함수에 추가적인 기능을 부여하거나 변경할 수 있게 된다.


· 동작 원리


 데코레이터는 함수를 '장식'하는 기능을 수행한다.
 이를 위해 데코레이터 함수는 데코레이터가 적용될 원래의 함수를 인자로 받아 참조한다.

 보통 'wrapper'라는 내부 함수를 정의하고, 이 함수를 리턴함으로써 원래의 함수를 참조할 수 있는 상태로 만들어 준다.
 이 'wrapper' 함수는 원래의 함수가 호출될 때 같이 실행되어 실행 함수 전후에 추가적인 동작을 수행한다.

 아래의 예시를 살펴보자.

def decorator_function(func):
    def wrapper():
        print("함수 실행 전 출력기능 추가")
        func()
        print("함수 실행 후 출력기능 추가")
    return wrapper


 데코레이션 함수인 'decorator_function'는 매개 변수로 'func'를 가지고 있으며, 'wrapper' 함수를 리턴하는 동작을 수행한다.

 리턴된 'wrapper' 함수는 상위 함수의 'func' 매개 변수를 참조할 수 있는 상태가 되며, 코드 블록 내의 동작을 수행한다.
 또한 원래 함수인 'origin_decorator_function' 함수를 감싸는 역할을 수행한다.

 위의 예시에서는 실행 함수 전후에 print 동작을 수행함으로써 추가적인 기능을 부여하고 있다.


@decorator_function
def origin_decorator_function():
	print("wrapper")


 데코레이터는 기본적으로 함수를 입력받아서 새로운 함수를 반환하는 구조이다.

 데코레이터가 적용된 '@decorator_function' 함수 아래의 'origin_decorator_function' 함수는 원래의 함수로서, 데코레이션 함수의 인자로 전달된다.

 그리하여 데코레이터 함수의 매개 변수 'func'는 원래의 함수인 'origin_decorator_functi
on'을 참조할 수 있는 상태가 된다.

 데코레이터 함수는 'wrapper' 함수를 리턴하고, 리턴된 'wrapper' 함수는 'origin_decora
tor_function' 함수의 메모리 주소를 참조할 수 있게 된다.

origin_decorator_function # <function decorator_function.<locals>.wrapper at 0x7d9547669bd0> 출력


 'origin_decorator_function' 함수를 호출하면 데코레이션 함수 내부의 'wrapper' 함수가 실행되어, 실행 함수 전후에 추가적인 동작을 수행하게 된다.

origin_decorator_function() # 함수 실행 전 출력기능 추가
                            # wrapper
                            # 함수 실행 후 출력기능 추가 출력

 

데코레이터 함수인 'decorator_function'는 데코레이터가 적용된 아래의 원래 함수를 매개 변수의 인자로 받아, 해당 원래 함수의 메모리 주소를 참조하는 상태가 된다.

 데코레이터 함수를 호출하면, 내부의 'wrapper' 함수를 리턴하고, 리턴된 해당 함수는 원래의 함수를 참조할 수 있는 상태가 된다.
 (즉, 'origin_decorator_function' 함수를 데코레이터 함수 내부의 'wrapper' 함수가 참조한다.)

 이 'wrapper' 함수는 내부의 코드 블록을 동작하며, 실행 함수 전 후에 추가적인 동작을 수행한다.
 ('func()' 코드는 'wrapper' 함수가 참조하고 있는 원래 함수를 호출하는 역할을 수행한다.)

 이렇게 데코레이터는 원래 함수의 동작을 그대로 유지하면서 추가적인 기능을 수행할 수 있게 하여 준다.
 이는 코드의 재사용성을 높이고 가독성을 향상하는데 도움이 된다.

 


몇 가지 예시를 살펴보도록 하자.

 

· 함수 실행 시간 확인하는 데코레이터 구현
import time
def timer_decorator(func):
    def wrapper(*args, **kwargs):
    	start_time = time.time()
        rs = func(*args, **kwargs)
        end_time = time.time()
        return rs
    return wrapper
    
@timer_decorator
def exe_function():
    print("실제 처리할 함수")
    time.sleep(2)

 
 데코레이터 함수인 'timer_decorator'는 데코레이터가 적용된 아래의 원래 함수 'exe_function'를 매개변수 'func'의 인자로 받아서 새로운 'wrapper' 함수를 반환하고 있다.

 리턴된 'wrapper' 함수는 원래 함수의 메모리를 참조하여, 실제 'exe_function' 함수를 호출하면 'wrapper' 함수가 실행되어 코드 블록 내부의 동작을 수행하게 된다.

exe_function() # 실체 처리할 함수 출력


 'start_time', 'end_time' 두 변수에 대한 출력 로직을 추가하여 동작하면 아래와 같이 출력 결과가 잘 나오게 된다.

    def wrapper(*args, **kwargs):
        start_time = time.time()
        print(f"start_time = {start_time}")
        
        rs = func(*args, **kwargs)
        
        end_time = time.time()
        print(f"end_time = {end_time}")
        
        return rs
    return wrapper
exe_function() # start_time = 1699969151.647465
               # 실제 처리할 함수
               # end_time = 1699969153.6493669  출력



 · 로그 출력 데코레이터 구현
def log_decorator(func):
    def wrapper(*args, **kwargs):
        print(f"Log : {func.__name__} 함수 호출됨")
        rs = func(*args, **kwargs)
        print(f"Log : {func.__name__} 함수 호출됨")
        return rs
    return wrapper
    
@log_decorator
def another_function():
    print("로그 출력")

 데코레이터 함수인 'log_decorator'는 데코레이터가 적용된 함수 '@log_decorator' 아래의 'another_function' 함수를 매개변수 'func'의 인자로 받는다.
 이 때, 데코레이션의 매개 변수는 원래의 함수를 참조하는 상태가 된다.

 리턴된 'wrapper' 함수는 코드 블록 내의 동작을 수행하며, 원래 함수를 감싸 전후로 추가적인 동작을 실행한다.

 print문 내의 표현식을 통해 특정 함수가 호출되었다는 로그를 반환하게 할 수 있다. 

another_function() # Log : another_function 함수 호출됨
                   # 로그 출력
                   # Log : another_function 함수 호출됨  출력



 · ID를 통한 접근 권한 확인 데코레이터 구현
def check_permission(param_id):
    def decorator(func):
        def wrapper(*args, **kwargs):
            check_id = "admin"
            if check_id == param_id:
                args = ["인증성공"]
                return func(*args, **kwargs)
            else:
                raise PermissionError("접근 불가!!")
        return wrapper
    return decorator

@check_permission(param_id = "admin")
def admin_function(rs_msg):
    print(f"인증 결과 : {rs_msg}")


 '@check_permission( param_id = 'admin' )'과 같이 데코레이터를 적용하면서 인자를 전달하면, 해당 인자는 데코레이션 함수의 매개변수 'param_id'로 전달된다.

 여기서 주의 깊게 볼 부분은, 보통의 경우 데코레이션 함수는 데코레이션이 적용된 아래의 함수를 인자로 받는데, (param_id = 'admin')이라는 인자만을 받아 사용하고 있다는 것이다.
 이러한 경우 데코레이터 팩토리를 사용한다.
 
 '데코레이터 팩토리'란 실제 데코레이터를 생성하는 함수이다.
 사용하는 이유는 실제 데코레이터에게 인자를 전달해야 하는 경우이기 때문이다.

 그리하면 실제 데코레이터 함수 'decorator'는 'param_id' 에 대한 메모리 주소, 'admin_function' 함수에 대한 메모리 주소, 총 2개의 메모리 주소를 참조하게 된다.
 따라서 리턴된 'wrapper' 함수가 동작하면, 'wrapper' 함수는 위의 두 메모리를 참조할 수 있는 상태가 된다.

 'param_id' 는 "admin"으로 디폴트 된 값으로, if문의 조건에 할당되어 "admin"인 경우에만 'admin_function' 함수가 실행된다.
 이때 원래 함수의 인자 'rs_msg'는 'wrapper' 함수의 매개 변수 '*args'에 전달되어, 'wrapper' 함수 내에서 참조할 수 있는 상태가 된다.

 이후에 'wrapper' 함수는 해당 인자를 활용하여 필요한 처리를 수행한다.

admin_function() # 인증 결과 : 인증성공 출력


 이러한 처리는 데코레이터가 새로운 함수를 추가하는 기능이며, 원래의 함수는 데코레이터를 통해 원래의 기능 전후에 추가적인 기능을 부여하여 이를 수행하게 된다. 




 

데코레이터 함수에 대해 요약하자면 아래와 같다.

데코레이터는 함수를 '장식'하는 기능을 수행한다.

이를 위해 데코레이터 함수는 원래의 함수를 인자로 받아, 리턴한 내부 함수를 정의한다.

내부 함수는 원래 함수의 메모리를 참조할 수 있는 상태가 된다.

이 내부 함수는 원래 함수를 호출할 때, 같이 동작하여 원래 함수 전후에 추가적인 동작을 부여하고 이를 반환한다.

'[파이썬] > 함수' 카테고리의 다른 글

[파이썬][함수] 클로저 함수  (2) 2023.11.15
[파이썬] 람다 함수  (0) 2023.11.09
[파이썬] 함수  (0) 2023.11.08