Python

TIL7 - Nested Function & decorator

Django King, Lee 2022. 2. 20. 15:20
728x90

 

Intro

Django 개발자이기전에 Python을 이용하는 개발자로서, 배울 때 가장 헷갈렸던 중첩함수(Nested Function)과  decorator에 대한 포스팅을 해보겠습니다.

 

중첩함수

함수도 함수안에 중첩하여 사용할 수 있는데, 이를 중첩함수라고 합니다.

중첩함수(내부함수)는 상위부모 함수안에서만 호출 가능합니다.

 

예를 들어 

def parent_function():
    def child_function():
        return "this is gg"
    return child_function()

print(parent_function())

 

이렇게 쓴다면, child_function에서 작성한 print문이 출력되는 것입니다.

 

그러면 굳이 이렇게 쓰는 이유가 무엇일까요?

 

Why Nested Function?

이유는 가독성, Closure 두 가지 있습니다.

1. 가독성

우리가 함수를 사용하는 이유 중 하나는 반복되는 코드블럭을 함수로 정의해서 효과적으로 코드를 관리하고 가독성을 높이기 위함입니다.

중첩함수 역시 동일합니다. 함수안의 코드 중 반복되는 코드가 있다면 중첩함수로 선언해서 부모함수의 코드를 효과적으로 관리하고 가독성을 높일 수 있습니다.

 

def print_all_elements(list_of_things):
    def print_each_element(things):
        print("things : ", things, "types : ", type(things))
        for thing in things:
            print("thing : ",thing)

    if len(list_of_things) > 0:
        print_each_element(list_of_things)
    
    else:
        print("empty things")
        
print_all_elements(list_of_things=[])
print_all_elements(list_of_things=[1,2,4])
print_all_elements(list_of_things=(1,2,3,))
print_all_elements(list_of_things={"a":1, "b":2})

 

print_all_elements를 부모함수로 선언하고 list_of_things라는 변수로 파라미터를 받도록 설정했습니다.

그리고 4가지 경우에 대해 테스트를 했습니다.

 

1. 빈 리스트

2. 값이 있는 리스트

3. 튜플

4. 딕셔너리

 

 

결과를 보면 things는 부모함수의 list_of_things를 받는다는 걸 알 수 있습니다.

 

중첩함수를 쓰지 않고 나눠쓰게 된다면

def print_each_element2(things):
    for thing in things:
        print(thing)

def print_all_elements2(list_of_things):
    if len(list_of_things) > 0:
        print_each_element2(list_of_things)
    
    else:
        print("empty things")

이렇게 되고, 함수를 타고 들어가야 하는 불편함이 있는데 중첨함수로 묶어버리면 따로 선언할 필요가 없게 되는 것입니다.

 

2. Closure

중첩함수를 사용하는 다른 큰 이유는 Closure인데, 부모의 변수나 정보를 가두어 사용하는 걸 뜻합니다.

부모함수는 중첨함수를 리턴하여 부모함수의 변수를 외부로부터 직접적인 접근을 격리하면서도, 연산은 가능하게 해주는 것입니다.

주로, 함수나 객체를 생성해내는 팩토리패턴에 사용됩니다.

그리고 여기서 사용 될 개념이 데코레이터입니다.

Decorator

데코레이터는 그대로 직독직해하자면 장식이라는 뜻으로 많이 알고 계실겁니다.

그러면 파이썬과 장식과 어떤 연관이 있을까요?

 

간단한 함수를 통해 알아보겠습니다.

 

예를 들어 저는 투자전문가이며 삼성전자의 주가가 곧 떡상할거라는 정보를 알고 있는데, 이 정보를 제 고객들에게만 알려주고 싶습니다.

그러면 여기서 나올 수 있는 함수는 2개가 있습니다.

 

첫 번째로 이사람은 내 고객인가? 에 대한 함수입니다.

 

def is_my_customer(email):
    if not User.objects.filter(email=email).exist():
        return False
    return True

is_my_customer라는 함수를 만들고, 파라미터로 이메일을 받습니다.

그래서 파라미터로 받은 이메일이 내 DB에 있다면, 이 사람은 제 고객이어서 True를 리턴하고 아니라면 False를 리턴하게 됩니다.

 

그 다음은 내 고객인걸 확인되었다는 걸 알려주는 함수입니다.

def get_stock_information():
    return "확인되었습니다. 제 고객이시군요."

간단하게 이렇게 함수를 만들어보겠습니다.

 

쉽게 하나의 함수로 묶을 수 있지만 데코레이터 사용을 위해 분리했습니다.

 

이 정보는 아까 말했던 것처럼 내 고객에게만 말해야 하는데, 이 연결고리를 잊어먹지 않기 위해 강제적으로 유저를 식별해줄 수 있는 방법이 필요합니다.

바로 이때 사용하는 것이 데코레이터 입니다.

@is_my_customer
def get_stock_information():
    return "확인되었습니다. 제 고객이시군요."

 

이렇게 선언해주면 되는데, 데코레이터의 가장 큰 특징이 하나 더 있습니다.

바로 중첩함수를 리턴해야 데코레이터를 사용할 수 있다는 것입니다.

왜냐하면 데코레이터의 기능을 다르게 설명하면 chains of functions, 즉 여러개의 함수가 연속적으로 자동호출되도록 해주는 것이기 때문입니다.

그래서  is_my_customer에 수정 작업이 필요합니다.

 

def is_my_customer(func):
    def wrapper(*args, **kwargs):
        if not User.objects.filter(email=kwargs["email"]):
            return "제 고객이 아닙니다"
        return func(*args, **kwargs)
    return wrapper

is_my_customer를 중첩함수로 만들어야 했기에 내부에 wrapper라는 이름의 함수를 만들었습니다.

그리고 이메일 정보를 받아야 하는데, 우리가 값을 보낼 때 email="lee@gmail.com" 형태로 보내다보니 Keyword-Argument 형태로 보내게 되어 kwargs["email"] 이렇게 받을 수 있게 됩니다.

 

파라미터를 넣을 때 def get_stock_information("lee@gmail.com") 이렇게 넣을 수 있는데, 이럴 경우 wrapper에서 이메일을 받으려면  email = args 이렇게 설정해야 합니다.

보다 명확하게 하기 위해 kwargs를 이용하는 걸 권장드리며, 현업에서도 어떤 변수에 어떤 값을 보냈는 지 알기 위해 kwargs 형태를 많이 이용합니다.

 

그래서 User로 접근했을 때 계정이 없으면 False를, 있으면 func를 리턴시킵니다.

이 때 func는 제 고객이시군요.. 라는 메시지가 리턴되는 그 함수입니다.

 

이렇게 is_my_customer를 작성한 후 데코레이터를 다시 덮으면

@is_my_customer
def get_stock_information(*args ,**kwargs):
    return "확인되었습니다. 제 고객이시군요."

이렇게 됩니다.

 

wrapper라는 중첩함수에서 나의 고객이 맞으면 func인 get_stock_information을 리턴하도록 되어 있었고, get_stock_information을 까보니 "확인되었습니다. 제 고객이시군요"가 리턴된다고 나와 있습니다.

 

제 DB상에는 "1@gmail.com"이라는 메일을 가진 유저가 존재하고, "1@gmail.com1"이라는 이메일은 가진 유저는 존재하지 않습니다.

없는 계정으로 테스트해보니, 고객이 아니라는 말이 뜨는 걸 확인할 수 있습니다.

반대로 있는 계정이면 제 고객이라고 뜨게 되는 걸 확인할 수 있습니다.

 

 

마치며..

이렇게 데코레이터에 대해 알아봤습니다.

데코레이터는 현업에서도 굉장히 많이 쓰이는 함수이니, Python을 하며 꼭 알아야 합니다.

저도 처음에 이해하는 데 많이 헷갈렸고, 직접 이렇게 코드들을 입력해보며 이해할 수 있었습니다.