클로저와 데코레이터

JAMM·2021년 7월 19일
0

Python

목록 보기
1/1
post-thumbnail

클로저


  1. 두 수를 한 번에 입력 파라미터로 받을 수 있다면, 그 수를 곱한 값을 반환하는 함수는 아래처럼 작성할 수 있다.

    def mul(n, m):
        return n * m

  1. 두 수를 한번에 입력 파리미터로 받을 수 없는 상황을 가정해보자.

    한 번에 두 개의 파라미터를 받을 수 없고, 특정 값을 먼저 설정한 후에 나머지 파라미터를 받아서 처리해야 하는 상황이라면 어떻게 해야할까?

    class Mul:
        def __init__(self, m):
                self.m = m
        
        def mul(self, n):
                return self.m * n
    
    if __name__ == "__main__":
        mul3 = Mul(3)
        mul5 = Mul(5)
        
        print(mul3.mul(10)) # 30 출력
        print(mul5.mul(10)) # 50 출력

    위와 같이 클래스를 이용하여, 특정 값을 미리 설정하고 그 다음부터 'mul' 함수를 이용하면 가능하다.


  1. 위에서 정의했던 Mul 클래스로 만들어진 객체에 파라미터를 바로 전달할 수 없을까?

    'call' 메서드를 이용하면 가능하다.

    class Mul:
        def __init__(self, m):
                self.m = m
        
        def __call__(self, n):
                return self.m * n
    
    if __name__ == "__main__":
        mul3 = Mul(3)
        mul5 = Mul(5)
    
        print(mul3(10)) # 30 출력
        print(mul5(10)) # 50 출력

    'call' 메서드를 이용하면, Mul 클래스로 만들어진 객체에 파라미터를 전달하여 호출할 수 있다.


  1. 앞서 작성했던 클래스를 이용한 방법보다 더 간편한 '클로저'를 이용하여 작성해보자.

    def mul_of(m):
        def mul(n):
                return m * n
        return mul
    
    if __name__ == "__main__":
        mul3 = mul_of(3)
        mul5 = mul_of(5)
    
        print(mul3(10)) # 30 출력
        print(mul5(10)) # 50 출력

    이 함수의 구조를 살펴보면 'mul_of' 함수의 안쪽에 'mul' 함수를 정의하고 이를 반환하고 있다.

    (파이썬은 함수도 하나의 객체로 정의되기 때문에, 바깥 함수에서 안쪽 함수를 반환할 수 있다.)

    이 함수는 자세히 살펴보면 클래스가 특정한 값을 미리 설정하여 객체를 만들어 내는 과정과 유사하다.

    'mul_of' 함수에서 받은 인자 m은 'mul' 함수를 반환할 때, 'mul' 함수에 저장되어 리턴된다.

    이는 앞서 'Mul' 클래스를 정의할 때, 생성자 'init' 메서드에서 인자 m을 받는 과정은 클로저 함수인 'mul_of' 함수에서 인자 m을 받는 과정과 유사하며, 'call' 메서드에서 인자 n을 받아서 m n을 반환하는 과정은 클로저 함수의 안쪽 함수인 'mul' 함수에서 인자 n을 받아서 m n 을 반환하는 과정과 유사하다.

    즉, 'mul_of' 함수에서 받은 m이 'mul' 함수에 저장되어 리턴된다는 것이다.


데코레이터


  1. 함수의 수행시간을 반환하는 함수를 작성해보자.

    import time
    
    def myfunc():
        """ 데코레이터 확인 함수 """
        start = time.time()
        print("함수가 실행됩니다.")
        end = time.time()
        print("함수 수행시간: %f 초" % (end - start))
    
    myfunc()

  1. 단 하나의 함수의 수행 시간을 체크한다면 위 함수를 사용하겠지만, 수행 시간을 체크해야되는 함수가 상당히 많다면 그 함수들 모두 이런 동일한 기능을 추가해줘야 한다. 이는 매우 비효율적이므로, '클로저' 함수를 이용하여 작성해보자.

    import time
    
    def decorate(original_func):
            def wrapper():
                    start = time.time()
                    original_func()
                    end = time.time()
                    print("함수 수행시간: %f 초" % (end - start))
            return wrapper
    
    def myfunc():
            """ 데코레이터 확인 함수 """
            print("함수가 실행됩니다.")
    
    decoreated_myfunc = decorate(myfunc)
    decoreated_myfunc()
    

    클로저로 만든 'decorate' 함수는 함수를 입력인수로 받을 수 있다.

    (파이썬은 함수도 객체이기 때문에 함수 자체를 이렇게 파라미터로 전달하는 것이 가능하다.)

    즉, 데코레이터(decorator)는 기존 함수(original_func)의 변경 없이 추가적인 기능을 덧붙일 수 있도록 해주는 함수이다.


  1. 앞서 작성한 데코레이터 함수는 '@'를 이용한 어노테이션을 사용하여 좀 더 간단하게 작성할 수 있다.

    import time
    
    def decorate(original_func):
        def wrapper():
                start = time.time()
                original_func()
                end = time.time()
                print("함수 수행시간: %f 초" % (end - start))
        return wrapper
    
    @decorate
    def myfunc():
        """ 데코레이터 확인 함수 """
        print("함수가 실행됩니다.")
    
    # decoreated_myfunc = decorate(myfunc)
    # decoreated_myfunc()
    myfunc()

    'myfunc' 함수 바로 위에 @decorate라는 어노테이션을 추가하면, 파이썬은 함수명 위에 있는 어노테이션을 데코레이터 함수로 인식한다.

    따라서, 'myfunc' 함수는 decorate 함수를 거쳐서 수행된다.


  1. 'myfunc' 함수가 인자를 받는 경우를 생각해서, 위 코드를 수정해보자.

    import time
    
    def decorate(original_func):
        def wrapper(*args, **kwargs):
                start = time.time()
                original_func(*args, **kwargs)
                end = time.time()
                print("함수 수행시간: %f 초" % (end - start))
        return wrapper
    
    @decorate
    def myfunc():
        """ 데코레이터 확인 함수 """
        print("함수가 실행됩니다.")
    
    myfunc("You need python")

    위 코드를 보면 'wrapper' 함수에 입력 인수로 args, **kwargs를 추가하고 기존 함수('original_func')인 'original_func' 함수 수행 시 args, **kwargs를 입력 파라미터로 호출하도록 변경하였다.

    이 때, 주의할 점은 데코레이터 함수인 'decorate' 함수에는 *args, **kwargs를 입력 파라미터로 추가하지 않는다는 것이다.

    데코레이터 함수는 기존 함수('original_func')에 어떤 입력이 들어오는 지 알 수 없기 때문에, 기존 함수의 입력 인수에 상관없이 동작하도록 만들어야 한다.


functools.wraps

데코레이터 함수를 작성할 때, 함수의 올바른 동작을 보장하고 함수의 여러가지 속성을 보호하기 위해 @functools.wraps를 반드시 사용해야 한다.

import time
import functools

def decorate(original_func):
    @functools.wraps(original_func)
    def wrapper(*args, **kwargs):
            start = time.time()
            original_func(*args, **kwargs)
            end = time.time()
            print("함수 수행시간: %f 초" % (end - start))
    return wrapper

@decorate
def myfunc():
    """ 데코레이터 확인 함수 """
    print("함수가 실행됩니다.")

myfunc("You need python")

Reference

0개의 댓글