본문 바로가기
Python/Basic

Python Decorator 에 관하여

by 하람 Haram 2023. 4. 4.
728x90

Decorate

처음 논문을 구현한 코드 등을 보거나 깃허브를 둘러보면 이 Decorate 때문에 당황한 경험이 있을 것이다

쉽게 말 그대로 코드를 꾸밀 수 있는 기능으로 생각하면 편하다 (@를 사용한다)

아래 예시로 쉽게 파악이 가능하다

def Deco(func):
    print("#" * 14)
    print("## 함수를 수행합니다.")
    func()
    print("#" * 14)


@Deco
def sum_one2five():
    sum = 0 
    for i in range (1,6):
        print("## ",i,"번째 처리중")
        sum += i
    print("##  결과값 : ", sum)


print("\n")

@Deco
def mul_one2five():
    mul = 1
    for i in range (1,6):
        print("## ",i,"번째 처리중")
        mul *= i
    print("##  결과값 : ", mul)


sum_one2five()

다음을 실행하면 아래와 같은 결과를 얻을 수 있다

##############
## 함수를 수행합니다.
##  1 번째 처리중
##  2 번째 처리중
##  3 번째 처리중
##  4 번째 처리중
##  5 번째 처리중
##  결과값 :  15
##############


##############
## 함수를 수행합니다.
##  1 번째 처리중
##  2 번째 처리중
##  3 번째 처리중
##  4 번째 처리중
##  5 번째 처리중
##  결과값 :  120
##############

TypeError: 'NoneType' object is not callable

대충 어떤 역할을 하는지는 알겠는데

왜 일반적으로 함수를 호출하게되면

TypeError: 'NoneType' object is not callable 라는 오류가 나오는지

그러면 어떻게 사용하는지에 대한 감이 안잡히게 된다

 

Decorator에 대해서 알아보자

 

다음 단계로 설명이 가능하다

  • First class objects (1급 객체)
  • inner function
  • Decorator

 

First class objects

파이썬에서 말하는 First class objects  즉, 일급객체란

쉽게 변수나 데이터 구조에 할당이 가능한 객체를 뜻하는 말로

파라미터나 리턴값으로 사용이 가능한 객체를 말한다

 

추가로 파이썬의 모든 함수는 일급함수이다 (함수 이름을 변수처럼 사용이 가능)

우리가 자주 사용하는 map 함수

def func_twice(x):
    return x **2  # x 의 제곱을 반환
    
    
result = list(map(func_twice, [1, 2, 3, 4, 5]))
result

>>> [1, 4, 9, 16, 25]

다음과 같이 func_twice 라는 함수를 변수처럼 parameter로 주고 받는 것이 가능한 것을 볼 수 있다

 

이러한 성격을 일급객체/ 일급함수라고 한다

 

 

Inner function

python에서 말하는 inner function 즉, 내제 함수란

함수 내에 또 다른 함수가 존재하는 구조이다

 

아래와 같은 예시를 보자

def print_msg(msg):
    def printer():
        print(msg)
    printer()

print_msg("Hello world")

다음 코드를 설명해 보면

Hello world 가 msg 변수에 담겨서 print_msg에 들어가게 되고

이를 통해 printer라는 함수가 선언되며

print_msg 는 printer를 실행시킨다

 

 

주로 사용하는 이유가 자바스크립트에서

Closures 이라는 것으로 활용이 된다

(Closures : inner function을 return 값으로 반환)

 

def student(name, num, year):
    name = name
    num = num

    def inner_func():
        student_number = year*10000 + num
        return 'This student name : %s and student_number : %d' %(name, student_number)
    return inner_func

s1 = student('홍길동', 13, 2017)
s2 = student('새내기', 76, 2023)


>>> This student name : 홍길동 and student_number : 20170013
>>> This student name : 새내기 and student_number : 20230076

즉, 여기서 s1 은 다음과 같다

def s1():
    return 'This student name : 홍길동 and student_number : 20170013'

 

그러면 이 Closer랑 Decorator랑 무슨 관련이 있을까???

 

Decorator

Decorator는 복잡한 Closure 함수를 간단하게 만들어 준다고 생각하면 된다

(물론 Closure 함수 이니깐 inner function을 return 시켜준다)

 

위에 예시를 다시 살펴보자

def Deco(func):
    print("#" * 14)
    print("## 함수를 수행합니다.")
    func()
    print("#" * 14)


@Deco
def sum_one2five():
    sum = 0 
    for i in range (1,6):
        print("## ",i,"번째 처리중")
        sum += i
    print("##  결과값 : ", sum)

sum_one2five()

Deco 라는 Decorator는 일종의 Closure 함수라고 생각할 수 있는데
inner function을 return 을 해준다

근데 여기는 inner function이 따로 정의가 되어 있지 않으므로

당연히 NoneType error 가 나오게 되는 것이다

 

또한 따로 정의하는 것이 아닌

함수 바로 안쪽에 print 실행 문이 있으므로

비록 sum_one2five() 만 실행했음에도 불구하고

mul_one2five()의 결과도 함께 출력이 되어 버렸다

 

 

위를 제대로 동작하게 하고 싶으면

 

def Deco(func):
    def inner():
        print("#" * 14)
        print("## 함수를 수행합니다.")
        func()
        print("#" * 14)
    return inner


@Deco
def sum_one2five():
    sum = 0 
    for i in range (1,6):
        print("## ",i,"번째 처리중")
        sum += i
    print("##  결과값 : ", sum)


print("\n")

@Deco
def mul_one2five():
    mul = 1
    for i in range (1,6):
        print("## ",i,"번째 처리중")
        mul *= i
    print("##  결과값 : ", mul)


sum_one2five()

다음과 같이 inner function을 통해 감싸주면 된다

 

즉. Decorator는 inner function을 안에 넣어주고 return 시켜줘야 한다

만약 메세지를 다음과 같은 형식으로 출력해주는 코드를 만들고 싶다면

def Deco(func):
    def inner(*arg, **kwargs):
        print("#" * 14)
        func(*arg, **kwargs)
        print("#" * 14)
    return inner


@Deco
def print_msg(msg):
    print(msg)


print_msg("Hello this is python Decorator")

>>>  ##############
>>>  Hello this is python Decorator
>>>  ##############

이런 식으로 활용가능하고 여러개도 다음과 같이 가능하다

def star(func):
    def inner(*arg, **kwargs):
        print("*" * 14)
        func(*arg, **kwargs)
        print("*" * 14)
    return inner


def blank(func):
    def inner(*arg, **kwargs):
        print(" " * 14)
        func(*arg, **kwargs)
        print(" " * 14)
    return inner



@star
@blank
def print_msg(msg):
    print(msg)


print_msg("Hello this is python Decorator")


>>> **************
>>>               
>>> Hello this is python Decorator
>>>               
>>> **************

 

근데 위에 같이 복잡하게 매번 선언하는 것이 아닌

Decorator에 값을 넣어주고 싶으면

함수로 한번 더 감싸주면 된다 (주로 wrapper라고 쓴다)

 

def custom(shape):
    def wrapper(func):
        def inner(*arg, **kwargs):
            print(shape * 14)
            func(*arg, **kwargs)
            print(shape * 14)
        return inner
    return wrapper


@custom("*")
@custom("#")
def print_msg(msg):
    print(msg)


print_msg("Hello this is python Decorator")

 

728x90