Decorator에 대해 알기 전에 먼저, Nested Function Closure 개념을 살펴보겠습니다.


1. Closure

Closure란 아주 간단히 설명하면
inner 함수가 outer 함수의 인자를 기억하고 있는 것이라고 할 수 있습니다.

예제를 통해 알아보겠습니다.

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

mul3 = mul_of(3)
mul5 = mul_of(5)

print(mul3(10))  # 30 출력
print(mul5(10))  # 50 출력

여기서 눈여겨 봐야할 것은 mul_of함수에서 mul함수를 return해 줄때
mul_of에서 받은 변수 값 m이 mul함수에 저장되어 return된다는 것 입니다.
(즉, inner 함수가 outer 함수의 인자를 기억하고 있다는 것입니다.)

이렇게 되면 mul_of로 인해서 만들어지는 mul 함수는 각기 다른 변수값(m)을 지닌 채 return 되는 것입니다.

이처럼 inner 함수가 outer 함수의 변수나 정보를 가두어 사용하는 것을 closure라고 합니다. 왜 closure라고 할까요?
outer 함수는 inner 함수를 return해줍니다. 그리하면 outer 함수의 변수를 외부로부터 직접적인 접근은 격리하면서도 inner 함수를 통해서 격리된 outer 함수의 변수를 사용한 연산은 가능하게 해주는 것입니다.

정리하자면:

  1. inner 함수가 outer 함수의 변수나 정보를 inner 함수 내에서 사용한다.
  2. outer 함수는 reutrn 값으로 inner 함수를 return한다.
  3. outer 함수에서 return 했으므로 outer 함수의 변수는 직접적인 접근이 불가능 하지만 outer 함수가 return한 inner 함수를 통해서 사용될 수 있다.

2. Decorator

코드를 작성하다보면 각 함수들에게 동일한 코드를 삽입해줘야하는 경우가 있습니다.
함수 하나하나 수정하는 것은 매우 번거롭습니다.
그리고 수정해줘야할 함수의 양이 매우 많다면 시간도 오래 걸릴 것입니다.
Python에서는 이를 해결하기 위해 Decorator를 이용해 매우 쉽게 해결할 수 있습니다.

예를 들어, 다음과 같은 코드가 있다고 하겠습니다.

def origin_func():
    print("origin_func가 실행되었습니다.")

그런데, 만약 코드가 실행되기 전과 후에 다른 코드를 삽입하고 싶은 경우가 생겼다면 코드를 이렇게 고칠 것입니다.

def origin_func():
    print("origin_func 의 print 전")
    print("origin_func가 실행되었습니다.")
    print("origin_func 의 print 후")

그런데 고쳐야할 코드가 만약 1억개가 넘는다면 어떻게 해야할까요?
이때, 사용해야할 개념이 Decorator입니다.

def decorator_func(func): #3
    def inner_func(): #4
        print("origin_func print 전") 
        func() 
        print("origin_func print 후")

    return inner_func #5

def origin_func():
    print("origin_func가 실행되었습니다.")

origin_func = decorator_func(orgin_func) #2
origin_func() #1

이제 #을 따라 가면서 하나하나 살펴보겠습니다.
#1origin_func가 실행됩니다.
#2origin_func는 decorator_func(origin_func)이므로
#3decorator_func(origin_func)이 실행됩니다.
#4inner_func()이 정의됩니다.
#5return값이 inner_func입니다.
여기서 closure 개념이 필요합니다.
closure는 inner함수가 outer함수의 인자를 기억하고 있는 것이라 했습니다.
여기서 outer함수는 decorator_func입니다.
그리고 decorator_func의 인자는 origin_func입니다.

저는 저만의 표현으로 이를 이렇게 표현하고 싶습니다.
'origin_func'() => 'decorator_func(orgin_func)'()
=>'inner_func'()<origin_func를 기억하고 있음>

즉, 다음과 같이 코드가 실행됩니다.

print("origin_func print 전") 
origin_func()
print("origin_func print 후")

위의 예제에서 origin_func = decorator_func(orgin_func)와 같은 과정을 @ (at symbol)을 사용해서 간략하게 나타낼 수 있습니다.

기본 문법은 다음과 같습니다.

@Decorator
def function(args):
    #code

위의 예제를 이 문법으로 표현하면 다음과 같습니다.

@decorator_func
def origin_func():
    print("origin_func가 실행되었습니다.")

이제 실용적인 예제를 살펴보겠습니다.

만약, 유저가 이름을 입력하면 "Hello, "라는 string뒤에 유저가 입력해준 이름이 뒤에 오고 return해주는 함수를 만들어 보겠습니다.

name = input('이름을 입력하세요: ')

def name_decorator(input_name):
  def decorator(func):
    def deco ():
      return (func()+input_name)
    return deco
  return decorator

@ name_decorator(name)
def greeting():
  return "Hello, "

print(greeting())

여기서 유심히 살펴봐야 할 부분은

  1. Nested Function이 3개의 function으로 이루어져있다는 점
  2. @ name_decorator(name)처럼 변수가 들어가 있는 상태로 greeting 함수를 꾸미고 있다는 점입니다.

이를 저만의 표현으로 쉽게 표현하면 이와 같습니다.
@ name_decorator(name) === @ decorator <name변수가 저장되어있는 상태>

그러므로 다음 코드와 같다고 보면 훨씬 쉽습니다.

name = input('이름을 입력하세요: ')

 def decorator(func):
   def deco ():
     return (func()+input_name)
   return deco

@ decorator <name의 정보를 알고 있는 상태>
def greeting():
  return "Hello, "

print(greeting())