프로그래밍/PYTHON

python - Decorator

aiemag 2021. 1. 22. 00:43
반응형

 

지난 posting인 closure 에 이어서 이번에는 decorator 에 관해 정리합니다.

 

간단히 decorator 는 말 그대로 function을 감싸서 장식하는 역할을 합니다.

 

 


Decorator basic

 

우선 예제부터 보겠습니다.

 

3 line에 decorator(func)가 정의되어 있습니다. 

 

func을 인자로 받아 netsted function인 wrapper() 함수를 이용하여 closure 를 만들어 return 하도록 구현되어 있습니다.

 

여기서 func() 은 단팥빵에서 앙꼬라면 wrapper() 는 빵이 되겠네요. 여기서는 func() 앞 뒤로 "before" / "after" 메세지를 출력하게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/usr/bin/python3
 
def decorator(func):
 
    def wrapper():
        print("before")
        ret = func()
        print("after")
        return ret
 
    return wrapper
 
 
@decorator
def test1():
    print("test1 function")
 
 
def test2():
    print("test2 function")
 
 
def main():
    test1() # using notation
    print()
    decorator(test2)() #not using notation 
 
if __name__ == "__main__":
    main()
    
cs

 

우선 26 line에서 test2() 함수를 호출 해보겠습니다.

 

decorator의 인자에 test2 함수를 넣어서 호출하게 되면 closure 의 특징상 wrapper가 return 되면서 wrapper 내의 내용들이 실행되게 됩니다. 

따라서 "before" 출력 -> test2() 실행 -> "after" 출력이 되게 됩니다.

단팥빵이 잘 완성이 되었습니다.

 

다음, 24 line에서 test1() 함수를 호출 해보겠습니다.

 

이번에는 test1()을 바로 호출해도 실행결과는 test2() 를 호출할 때와 같습니다. 이유는 14 라인에서 test1()에 decorator 를 사용하겠다는 annotation을 사용하였기 때문입니다. python 언어 자체에서 지원하는 decorator 기능입니다.

 

실행 결과를 보시기 전에 충분히 코드에 대해 예상 결과를 생각해 보시면 좋습니다.

 

실행 결과

 

 

 


Multi decorator

 

다음은 decorator를 여러개 사용하는 예제 코드입니다. 말 그대로 알맹이를 여러겹 포함합니다.

 

48 line의 test() 함수를 decorator 3개로 포장을 하였는데, 제일 아래 decorator3 부터 decorator2, decorator1의 순서로 실행됩니다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
  
#!/usr/bin/python3
 
import time
from functools import wraps
 
def decorator1(func):
 
    def wrapper(*args, **kwargs):
        ret = func(*args, **kwargs)
        print("decorator1() func name : %s" % func.__name__)
        
        return ret
 
    return wrapper
 
 
def decorator2(func):
 
    def wrapper(*args, **kwargs):
        start = time.time()
        ret = func(*args, **kwargs)
        end = time.time()
        print("%s" % str(end - start))
        print("decorator2() func name : %s" % func.__name__)
 
        return ret
 
    return wrapper
 
 
def decorator3(func):
 
    @wraps(func)
    def wrapper(*args, **kwargs):
        ret = func(*args, **kwargs)
        print("decorator3 is called.")
        print("decorator3() func name : %s" % func.__name__)
 
        return ret
 
    return wrapper
 
 
@decorator1
@decorator2
@decorator3
def test(value):
    time.sleep(value)
 
 
def main():
    test(3)
 
 
if __name__ == "__main__":
    main()
cs

 

 

다음은 실행 결과 입니다. 예상하던 결과인가요? 

 

각 decorator() 함수 단계에서 func name을 표시하고 있는데,

 

decorator1() 단계에서는 func name이 우리가 의도적으로 호출한 test가 아닌, wrapper로 표시되고 있습니다.

그 이유는 decorator2() 에서 decorator1()으로 넘길 때 wrapper 함수명으로 넘기고 있기 때문입니다.

 

하지만, decorator2() 에서는 func name이 test로 잘 표시되고 있습니다. 

그 이유는 decorator3() 에서 decorator2()로 넘길 때 test 함수명이 잘 전달 되도록 func을 wrapping하는 @wraps annotation을 사용했기 때문입니다.

 

여러 단계의 decorator 중첩 사용 시, 최초 호출한 함수를 그대로 전달하기 위해서 @wraps annotation을 꼭 사용하도록 합시다.

 

 


Class decorator

클래스를 decorator로 사용한 예입니다.

20 line의 test() 함수에 DecoratorClass annotation을 mapping 하였습니다.

 

5 line 부터 시작하는 DecoratorClass는 __init__ 함수에서 함수를 전달받고 __call__ 함수에서 매개변수를 전달받아 실행하므로 closure 구현은 필요 없습니다.

 

9 line의 update_wrapper는 @wraps 역할과 같습니다. __init__ 에서는 함수명만 전달 받을 수 있어 @wraps 를 사용할 수 없으므로 update_wrapper를 사용합니다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/python3
 
from functools import update_wrapper
 
class DecoratorClass:
 
    def __init__(self, f):
        self.func = f
        update_wrapper(selfself.func)
 
    def __call__(self*args, **kwargs):
        print("before")
        ret = self.func(*args, **kwargs)
        print("after")
 
        return ret
        
 
@DecoratorClass
def test1(value):
    print("test1 :",value)
 
 
def test2(value):
    print("test2 :",value)
 
 
def main():
    test1(7)
    DecoratorClass(test2)(5)
 
 
if __name__ == "__main__":
    main()
cs

 

29 line에서 DecoratorClass annotation이 mapping된 test1 함수에 매개변수로 7을 넣어서 실행했습니다.

30 line에서 DecoratorClass의 객체를 직접 생성하고, 생성할 때 test2 함수를 생성자로 전달하였습니다.

실행은 매개변수로 5를 넣어서 실행했습니다.

 

실행 결과입니다.

 

 

 

반응형

'프로그래밍 > PYTHON' 카테고리의 다른 글

[환경설정] python - Linux anaconda  (0) 2021.02.10
python - Iterable, Iterator  (0) 2021.02.03
python - Closure  (0) 2020.11.03
python - map(), filter(), reduce() usage  (0) 2020.10.27
python - global, nonlocal keyword  (0) 2020.10.25