Python 클로저, 데코레이터

배추·2025년 12월 23일

🐍 Python

목록 보기
21/23

Python의 클로저, 데코레이터에 대해 알아봅니다.


▶︎클로저

  • 클로저: 외부 함수의 변수를 기억하는 내부 함수를 말한다.
  • 예) 함수 A 안에 함수 B가 있고, 함수 B가 함수 A의 변수를 사용한다고 하자. 보통은 함수 A가 끝나면 함수 A의 변수도 사라진다. 하지만 클로저에서는 함수 A가 끝나도 함수 B가 함수 A의 변수를 계속 사용할 수 있다. 마치 함수 A의 변수가 함수 B 안에 저장되어 있는 것처럼 말이다.

예제) 클래스 사용

  • 입력으로 받은 수 n에 항상 3을 곱하여 반환하는 함수. 5를 곱하여 반환하는 함수.
def mul3(n):
    return n * 3

def mul5(n):
    return n * 5
  • 클래스 사용
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 출력
  • __call__ 메서드를 이용하여 개선
    • mul() 함수의 이름을 __call__로 바꾸었다.
    • __call__ 함수는 Mul 클래스로 만든 객체에 인수를 전달하여 바로 호출할 수 있도록 하는 메서드.
      • mul3 객체를 mul3(10)처럼 호출할 수 있다.
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 출력

예제) 클로저: 외부 함수 안에 내부 함수를 구현

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

if __name__ == "__main__":
    mul3 = mul(3)
    mul5 = mul(5)

    print(mul3(10))  # 30 출력
    print(mul5(10))  # 50 출력
  • 함수가 함수를 반환하는 것은 파이썬에서 가능하다.
    • 외부 함수 mul 안에 내부 함수 wrapper를 구현.
    • 외부 함수는 내부 함수 wrapper를 반환.
  • mul(3)을 호출하면 외부 함수 mul은 종료되지만, 반환된 wrapper 함수는 여전히 m=3 값을 기억하고 있다. -> 클로저의 핵심!
  • 이후 mul3(10)을 호출하면 wrapper 함수가 기억하고 있던 m=3 값을 사용해서 3 * 10 = 30을 계산한다.
  • 즉, 클로저는 함수가 생성될 때의 환경(변수 값)을 기억하는 특별한 함수라고 할 수 있다.
    • 여기서 반환된 wrapper 함수가 바로 클로저이고,
    • mul과 같이 클로저를 만들어내는 함수를 클로저 팩토리(closure factory) 함수라고 한다.

▶︎데코레이터

def myfunc():
    print("함수가 실행됩니다.")
  • 이 함수의 실행 시간을 측정 하고 싶다.
    • 함수가 시작하는 순간의 시간과 함수가 종료되는 순간의 시간 차이를 구한다.
import time

def myfunc():
    start = time.time()
    print("함수가 실행됩니다.")
    end = time.time()
    print("함수 수행시간: %f 초" % (end-start))

myfunc()
  • 실행 시간을 측정해야 하는 함수가 myfunc 말고도 많다면? 이런 코드를 모든 함수에 마찬가지로 적용하는 것은 너무 비효율적이다. -> 클로저 이용.
    • elapsed 함수로 클로저를 만든다. 이 함수는 함수를 인수로 받는다. (파이썬은 함수도 객체이므로 함수 자체를 인수로 전달할 수 있다.)
    • decorated_myfunc = elapsed(myfunc)로 생성한 decorated_myfuncdecorated_myfunc()로 실행하면 실제로는 elapsed 함수 내부의 wrapper 함수가 실행되고, 이 함수는 전달받은 myfunc 함수를 실행하면서 실행 시간을 함께 출력한다.
import time

def elapsed(original_func):   # 기존 함수를 인수로 받는다.
    def wrapper():
        start = time.time()
        result = original_func()    # 기존 함수를 수행한다.
        end = time.time()
        print("함수 수행시간: %f 초" % (end - start))  # 기존 함수의 수행시간을 출력한다.
        return result  # 기존 함수의 수행 결과를 반환한다.
    return wrapper

def myfunc():
    print("함수가 실행됩니다.")

decorated_myfunc = elapsed(myfunc)
decorated_myfunc()
  • 클로저를 이용하면 기존 함수에 기능을 덧붙이기가 매우 편리하다. 이렇게 기존 함수를 바꾸지 않고 기능을 추가할 수 있게 만드는 클로저를 데코레이터(decorator)라고 한다.
  • 파이썬은 함수 위에 @+함수명이 있으면 데코레이터 함수로 인식한다.
    • myfunc 함수 바로 위에 @elapsed(@+함수명)라는 데코레이터를 추가.
    • 따라서 이제 myfunc 함수는 ‘elapsed 데코레이터’를 통해 수행될 것이다.
import time

def elapsed(original_func):   # 기존 함수를 인수로 받는다.
    def wrapper():
        start = time.time()
        result = original_func()    # 기존 함수를 수행한다.
        end = time.time()
        print("함수 수행시간: %f 초" % (end - start))  # 기존 함수의 수행시간을 출력한다.
        return result  # 기존 함수의 수행 결과를 반환한다.
    return wrapper

@elapsed
def myfunc():
    print("함수가 실행됩니다.")

# decorated_myfunc = elapsed(myfunc)  # @elapsed 데코레이터로 인해 더이상 필요하지 않다.
# decorated_myfunc()

myfunc()
  • 기존 함수에 인수를 넣고 싶을 때.
    • myfunc 함수는 입력 인수가 필요하지만, elapsed 함수 안의 wrapper 함수는 전달받은 myfunc 함수를 입력 인수 없이 호출해 오류가 발생.
    • 그러므로 데코레이터 함수는 기존 함수의 입력 인수에 상관없이 동작하도록 만들어야 한다. 데코레이터는 기존 함수가 어떤 입력 인수를 취할지 알 수 없기 때문이다.
    • 이렇게 전달받아야 하는 기존 함수의 입력 인수를 알 수 없는 경우에는 *args **kwargs 매개변수를 이용하면 된다.
import time

def elapsed(original_func):   # 기존 함수를 인수로 받는다.
    def wrapper(*args, **kwargs):   # *args, **kwargs 매개변수 추가
        start = time.time()
        result = original_func(*args, **kwargs)  # 전달받은 *args, **kwargs를 입력파라미터로 기존함수 수행
        end = time.time()
        print("함수 수행시간: %f 초" % (end - start))  # 수행시간을 출력한다.
        return result  # 함수의 결과를 반환한다.
    return wrapper

@elapsed
def myfunc(msg):
    """ 데코레이터 확인 함수 """
    print("'%s'을 출력합니다." % msg)

myfunc("You need python")

+) *args**kwargs

  • *args: 모든 입력 인수를 튜플로 변환하는 매개변수.

  • **kwargs: 모든 ‘키=값’ 형태의 입력 인수를 딕셔너리로 변환하는 매개변수.

  • func(1, 2, 3, name='foo', age=3) 함수가 입력 인수의 개수와 형태에 상관없이 모든 입력을 처리하려면.

    def func(*args, **kwargs):
        print(args)
        print(kwargs)
    
    func(1, 2, 3, name='foo', age=3)
    # (1, 2, 3)
    # {'age': 3, 'name': 'foo'}
    • func 함수에 *args, **kwargs 매개변수를 지정하면 다양한 입력 인수를 모두 처리할 수 있다.
    • 1, 2, 3 같은 일반 입력은 args 튜플, name = 'foo'와 같은 ‘키=값’ 형태의 입력은 kwargs 딕셔너리로 저장한다.
profile
난 🥬

0개의 댓글