AMAD's Tech blog

Python - 데코레이터

by AMAD

 

데코레이터란??

 

파이썬에는 데코레이터(decorator)라는 문법이 있다.
데코레이터는 장식하다, 꾸미다라는 뜻의 decorate에 er(or)을 붙인 말인데 장식하는 도구 정도로 설명할 수 있다.

 

 

언제 사용하는가??

 

함수를 수정하지 않은 상태에서 추가 기능을 구현할 때 사용한다.
예를 들어서 함수의 시작과 끝을 출력하고 싶다면 다음과 같이 함수 시작, 끝 부분에 print를 넣어주어야 한다.

def hello():
    print('hello 함수 시작')
    print('hello')
    print('hello 함수 끝')
 
def world():
    print('world 함수 시작')
    print('world')
    print('world 함수 끝')
 
hello()
world()
# 실행결과
hello 함수 시작
hello
hello 함수 끝
world 함수 시작
world
world 함수 끝

 

만약 다른 함수도 시작과 끝을 출력하고 싶다면 함수를 만들 때마다 print를 넣어야 한다.
이런 함수가 몇개 안된다면 크게 문제될게 없지만 만약 1억개의 함수에 이런 print을 넣어야 한다면??
(항상 극단적으로 생각해야 한다.)

 

이럴 경우에 데코레이터를 활용하면 아주 편리하다.

#decorator

def trace(func):                         # 호출 할 함수를 매개변수로 받음
	def wrapper():					     # 호출 할 함수를 감싸는 함수
    	print(func.__name__, '함수 시작')  # __name__으로 함수 이름 출력
        func()							 # 매개변수로 받은 함수를 호출
        print(func.__name__, '함수 끝')   
   	return wrapper						 # wrapper 함수 반환 (함수를 반환 할때는 () 없이 함수 이름만으로 반환한다.
    
def hello():
	print('hello')
    
def world():
	print('world')

trace_hello = trace(hello)  # 데코레이터에 호출 할 함수를 넣음
trace_hello()               # 반환된 함수를 호출
trace_world = trace(world)  # 데코레이터에 호출 할 함수를 넣음
trace_world()

 

# 실행결과
hello 함수 시작
hello
hello 함수 끝
world 함수 시작
world
world 함수 끝

 

hello와 world 함수의 시작과 끝이 출력된것을 볼 수 있다.

데코레이터 trace는 호출 할 함수를 매개변수로 받는데 trace는 추적하다의 뜻으로 프로그래밍에서 함수의 실행 상황을 추적할 때 trace라는 말을 사용한다.

def trace(func): 		# 호출 할 함수를 매개변수로 받음

trace 함수 안에서는 호출할 함수를 감싸는 함수 wrapper를 정의한다. (wrapper는 물건을 싸는 포장지라는 뜻인데 여기서는 함수를 감싼다고 해서 이런 이름을 붙였는데, 다른 이름을 사용해도 상관없다.)

def wrapper():			# 호출 할 함수를 감싸는 함수

 

이제 wrapper 함수에서는 함수의 시작을 알리는 문자열을 출력하고, trace에서 매개변수로 받은 func를 호출한다.

그다음에 함수의 끝을 알리는 문자열을 출력. 여기서 매개변수로 받은 함수의 원래 이름을 출력할 때는 name 속성을 활용한다.

마지막으로 wrapper 함수를 다 만들었으면 return을 사용하여 wrapper 함수 자체를 반환한다.

(함수를 반환할 시 함수 이름만 사용, ()없이!!)

def trace(func):                             # 호출할 함수를 매개변수로 받음
    def wrapper():                           # 호출할 함수를 감싸는 함수
        print(func.__name__, '함수 시작')      # __name__으로 함수 이름 출력
        func()                               # 매개변수로 받은 함수를 호출
        print(func.__name__, '함수 끝')
    return wrapper                           # wrapper 함수 반환

 

즉, 함수 안에서 함수를 만들고 반환하는 클로저이다.

데코레이터를 사용할 때는 trace에 호출할 함수 hello 또는 world를 넣는다. 그다음에 데코레이터에서 반환된 함수를 호출한다. 이렇게 하면 데코레이터에 인수로 넣은 함수를 호출하고 시작과 끝을 출력한다.

trace_hello = trace(hello)    # 데코레이터에 호출할 함수를 넣음
trace_hello()                 # 반환된 함수를 호출
trace_world = trace(world)    # 데코레이터에 호출할 함수를 넣음
trace_world()                 # 반환된 함수를 호출

 

@를 사용하여 더 간편하게 데코레이터를 사용할 수 있다.
호출 할 함수 위에 @데코레이터 형식으로 지정한다.

@데코레이터
def 함수이름():
	코드

 

# @사용하여 데코레이터 사용하기

def trace(func):                             # 호출할 함수를 매개변수로 받음
    def wrapper():
        print(func.__name__, '함수 시작')      # __name__으로 함수 이름 출력
        func()                               # 매개변수로 받은 함수를 호출
        print(func.__name__, '함수 끝')
    return wrapper                           # wrapper 함수 반환
 
@trace    # @데코레이터
def hello():
    print('hello')
 
@trace    # @데코레이터
def world():
    print('world')
 
hello()    # 함수를 그대로 호출
world()    # 함수를 그대로 호출

# 실행결과
hello 함수 시작
hello
hello 함수 끝
world 함수 시작
world
world 함수 끝

# hello와 world함수 위에 @trace를 붙인뒤에 hello(), world()를 그대로 호출하면 끝!

 

이걸 도식화 하면 다음의 그림처럼 나타낼 수 있다.

 

그런데 여기서 이상한 점???

 

@trace 하면 정의해 두었던 데코레이터가 실행되는 것 까지는 알겠다.

그런데,

hello()함수 자체를 호출하는데 @trace 밑에 def hello()가 실행이 안된다.

#내가 생각한 실행결과

@trace 했으니
hello 함수 시작
hello
hello 함수 끝   # 여기 까지 실행되는 것은 이해가 됐는데, 그 밑으로 hello 함수가 호출되었으니 hello함수가 실행되어야 하는거 아닌가? 하는 의문점이 들었다. 
              # 그래서 나는 hello 함수 끝 및에 hello가 한번 더 찍힐것으로 생각했다.

ex)
hello 함수 시작
hello
hello 함수 끝
hello

이렇게 나올것이라고 생각했다.

하지만 결과는 아니었다.

 

책에 나온 설명만으론 이해가 안 되서 vs_code로 실험을 해보았다.

 

위 사진처럼 코드를 쳤다.

wrapper함수 에서 매개변수로 받은 func()를 주석처리 해보았다.

 

 

결과는 다음과 같았다. wrapper함수 에서 func()를 호출하지 않으면

@trace   # 밑에있는
def hello():
	print('hello 실행 안 되는건가??')
    
#hello 함수는 실행되지 않는다.

 

다시 이해해 보면 기존 함수 위에 데코레이터를 붙이면 기존 함수는 함수자체가 아닌 데코레이터된 함수로 인식한다.

따라서 wrapper함수 에서 func를 따로 호출해주는 코드가 작성되어있지 않으면 맨 마지막에 hello()로 호출해주어도 hello()에서 정의한 print('hello 실행 안 되는건가??')는 실행되지 않는다.

따라서 기존의 함수를 사용하고는 싶은데 데코레이터로 처리해야 하는 경우에는
기존의 함수를 감싸는 함수 즉, wrapper 함수 안에서 한번 더 호출 해 주어야 한다.

 

한가지 더 알고 넘어가면 좋은점이있어 정리한다.

이렇게 코드를 짜면 어떻게 될까??
데코레이터인 trace 함수에서 print문을 넣으면??

 

결과는 위의 사진과 같다.
@trace로 trace 함수를 호출한 것과 같은 효과이다.
그래서 print문이 실행된것이고 '이건 무조건 실행' 이라는 문자열이 두 번 찍힌 후에
데코레이터된 hello와 world함수가 실행되어 함수 시작 함수 끝 이 찍힌다.

 

여기서 더욱 신기한것은 (당연한 걸수도있다.)

hello()와 world()를 주석처리 하여도,

 

@trace는 trace()와 같은 효과를 갖고있어 trace에서 정의한 코드는 실행이 되는것을 볼 수 있다.

 

 

요약: 데코레이터는 기존의 함수에 덧붙여서, 함수 자체를 수정하지 않아도 수정한 효과를 볼수 있게끔 해주는 장식자 역할을 하는 함수이다.

 

'Python' 카테고리의 다른 글

Python 매개변수  (0) 2022.12.06
itertools 모듈 combination 함수 사용하기  (2) 2022.11.27

블로그의 정보

성장 하고 싶은 개발자

AMAD

활동하기