Python TIL(9) - Decorator

random·2021년 4월 1일
0

Python - TIL

목록 보기
9/19

Decorator 학습

Python 에서 decorator는 어떤 역할을 하는지 탐구해보자.

데코레이터(decorator)란 무엇일까?

  • 배경: 파이썬에서 객체는 변수에 저장된 주소로 호출된다. 그렇기 때문에 함수객체를 담은 변수(주소)를 다른 함수에 인자로 넘겨서 호출하는것이 가능해진다.
  • 정의: 위 원리를 따라서 함수를 인자값으로 받아 명령을 추가한 뒤 이를 다시 함수의 형태로 반환하는 함수이다. 함수의 내부 수정 없이 기능에 변화를 가미할 때 사용하며 일반적으로 함수의 앞뒤 수식추가가 필요할 때 사용한다. 아울러, 데코레이터를 이용해, 반복을 줄이고 메소드나 함수의 책임을 확장한다.
  • 아래 다양한 데코레이터 용법을 보면서 어떻게 기능하는지 확인해보자.

<함수의 객체 성질을 보여주는 코드>

def func():
    return 1

print(func())
print(func)
  • 위 두 print출력문의 차이점: 소괄호의 유무 차이로 인해서; 위는 함수를 실행한 것이고, 아래는 함수 위치를 알려 주는 것으로 실행됨.
  • 따라서 print(func()) 는 1이라는 반환값을 출력하고, print(func)는 <function func at 0x7fc83001df70>를 출력함.
  • 앞서 설명한 함수가 객체 성질을 보유했다는 사실을 보여줌.

<함수안에 함수 구조의 코드>


def hello(name='Ray Kim'):
    print('The hello() function has been executed')

    def greet():
        return '\t This is the greet() func inside hello'
    def welcome():
        return '\t This is welcome() inside hello'

    if name == 'Ray Kim':
        return greet
    else:
        return welcome
        
 my_new_func = hello('Ray Kim')
 
  • hello() 함수 내부에 존재한 greet()과 welcome() 함수는 hello() 내에서만 호출되는 범위가 제한된 함수이다. 함수 범위가 정해져있기 때문에 특별한 조치 없이 일반 함수 처럼 hello() 함수를 호출하려하면 에러가 난다.
  • 위 에러에 대한 해결책으로 함수를 함수의 인자값으로 넣어주는 방법이 있다. 위 코드 예시처럼, "my_new_func = hello('Ray Kim')"에서 hello 함수에 대한 인자값이 'Ray Kim' 임으로 위 코드의 if절을 받기 때문에; 1차로는 hello() 함수를, 2차로는 greet() 함수를 받게된다. hello 함수 밖에서 greet 함수를 호출 할 수 있는 방법 중 하나; 함수 내부에 존재한 함수를 호출할 때 쓰는 방법.

<상위 함수를 그냥 지나치는 함수 코드>

def cool():

    def super_cool():
        return ' I am very cool!'

    return super_cool

some_func = cool()
some_func #cool()
  • some_func 변수를 Cool() 함수로 포인팅을 했지만 cool은 그저 Passing 하는 상위 함수 일뿐, 특별한 역할이 부여된 것이 없으므로 그 내부의 super_cool 함수를 반환하게 된다.

<함수A가 동일 선상에서 정의된 다른 함수B를 인자값으로 쓰는 코드>

 
def hey():  
    return 'Hey Ray!'

def other(some_def_func):
    print('Other code runs here!')
    print(some_def_func())

print(id(hey))
print(id(other))
hey() 
other(hey) 
  • 위 코드 출력 결과물:
    "Other code runs here!"
    "Hey Ray!"

=> 위 코드는 함수A가 다른 함수B의 인자값으로 들어가 함수B의 명령을 행하며 본래 자기 자신 내에 코드를 실행하는 법을 보여준다.
=> 위 출력 과정을 자세히 살펴보면... aaa
(1) 'other(hey)' 실행시, 먼저 정의된 hey()함수가 other()함수의 인자값으로 들어가고,
(2) other()함수의 첫번째 내부 명령인 'other code runs here!'을 출력한 후,
(3) 두번째 내부 명령인 print(some_def_func())함수에 인자값으로 들어가게 되어 print(hey()) 로 변환된 함수를 수행하며 "hey ray!"를 출력하게됨.


<전후를 감싸는(wrap)기능 함수 + @ decorator 기능이 사용된 코드>


def new_decorator(original_func):

    def wrap_func():


        print('Some extra code, before the original function')

        original_func()

        print('some extra code, after the original function!')

    return wrap_func

def func_needs_decorator1():
    print("I want to be decorated!")

func_needs_decorator1()

decorated_func = new_decorator(func_needs_decorator1)
decorated_func()


@new_decorator 
def func_needs_decorator2():
    print("I want to be decorated!")

func_needs_decorator2()
  • a. Wrap 함수 개념 설명: 위 코드 예제는 "wrap_func()" 함수를 활용해 원하는 출력물의 전후를 감싸는 모양의 다음과 같은 출력물이 나온다:
    ["Some extra code, before the original function!"
    "I want to be decorated!"
    "some extra code, after the original function!"]

    ==> new_decorator() 함수의 인자값으로 포장 함수의 내부 내용물이 될 실행할 함수 func_needs_decorator1()를 넣은 후, wrap_func() 포장 함수 출력문('Some extra code...이하 생략')이 앞뒤로 출력된다.

  • b. @ decorator 개념 설명:
    - 기존에 만든 함수에 새로운 기능을 추가 하고 싶을 때, 1) 기존 함수에 코드를 추가로 작성하는 방법, 2) 기존 함수와 동일한 코드를 지닌 새로운 함수를 만들고 그 곳에 새 코드를 작성하기 이 두가지로 나눌 수 있다. 아마 후자로 작업하기에는 메모리 사용측면이나, 적힌 코드 분량 관리에 있어서 크고 작은 어려움을 겪을 것이다.
    - 그렇다면 전자의 방법으로 작업을 어떻게 용이하게 할 수 있을까? 한 가지 고려사항으로, 기존함수에 새 코드를 작성하게 되면, 나중에 해당 추가 기능을 다시 없애고 싶을 때 도전과제로 다가올 것이다. 이를 일일이 손으로 다 지우면 많이 불편할 것이다. 따라서 스위치를 켰다 컸다 하듯이 새 함수기능을 추가했다가 지웠다 하는 역할을 하는 방법이 고안된 것인데, 바로 지금 소개된 @ 데코레이터이다.
    - 위 코드의 예시처럼 @some_decorator 를 def 시작라인 윗줄에 써서 코드 라인을 한 줄 썼다 지웠다 하면서 원하는 기능을 넣었다 뺐다 할 수 있음. 예) @decorator가 있다가 없으면 위 wrap_func 기능이 실행되었다가 다시 없어진다.
    - 특히나 이 '@' 기능은 인터넷에 기작성된 코드를 따와서 응용하는데 꼭 필요한 까닭에 장고나 플라스크 웹개발 시 굉장히 많이 쓰인다. 따라서 여러 코드를 살필때 저 @오퍼레이터가 어떤 기능을 하는지 자세히 이해하고 잘 활용할 줄 알아야 한다!

0개의 댓글