TIL | Python - 중첩함수(Nested Function)

송치헌·2021년 8월 8일
0
post-thumbnail

무엇을 배웠는가?

중첩함수란?

중첩함수(Nested Function) : 상위 부모 함수 안에서만 호출 가능한 함수. 부모 함수 밖에서는 호출할 수 없다.

중첩함수를 사용하는 이유

  1. 가독성
  • 함수 안에 반복되는 코드가 있으면 중첩 함수를 선언해 부모 함수의 코드를 효과적으로 관리하고, 가독성을 높일 수 있다.
def print_all_elements(list_of_things):
    ## 중첩함수 선언
    def print_each_element(things): # 리스트 요소를 하나씩 꺼내주는 함수
        for thing in things:
            print(thing)

    if len(list_of_things) > 0:
        print_each_element(list_of_things)
    else:
        print("There is nothing!")
  1. Closure
  • 중첩함수가 부모 함수의 변수나 정보를 가두어 사용하는 것을 Closure라고 한다.

  • 중첩 함수가 부모 함수의 변수나 정보를 중첩 함수 내에서 사용한다.

  • 부모 함수는 리턴값으로 중첩 함수를 리턴한다.

  • 부모 함수에서 리턴 했으므로 부모 함수의 변수는 직접적인 접근이 불가능 하지만 부모 함수가 리턴한 중첩 함수를 통해서 사용될 수 있다.

  • 특정 정보를 기반으로 연산을 실행하고 싶지만, 그 정보에 접근을 제한하여 노출 혹은 수정을 못하게 하고 싶을때 사용한다.

def generate_power(base_number):
    def nth_power(power):
        return base_number ** power 
    	# 부모 함수의 변수인 base_number가 중첩 함수에 사용됨 (closure)
    return nth_power # 중첩 함수가 부모 함수의 결과 값으로 리턴
 
calculate_power_of_two = generate_power(2)
calculate_power_of_two(7) # base_number가 2로 이미 셋팅된 함수
> 128

calculate_power_of_seven = generate_power(7)
calculate_power_of_seven(3)
> 343

처음에 굉장히 비효율적이라고 생각했다. 왜냐하면

def generate_power(base_num, power):
    return base_num ** power

generate_power(2,7)
>128
generate_power(7,3)
>343

이게 더 명료해 보이기 때문이다.

그러나 간과한 것이

특정 정보를 기반으로 연산을 실행하고 싶지만, 그 정보에 접근을 제한하여 노출 혹은 수정을 못하게 하고 싶을때 사용한다.

라는 것이다.

아래에 있는 코드에서 generate_power(2,7) 왼쪽 positional argument가 밑이고 오른쪽 positional argument가 지수인데 함수를 호출할 때마다 밑을 계속해서 바꿔줄 수 있다. 그러나 위에 있는 코드처럼 중첩함수를 이용하여 closure하게 작성하면 밑이 2인 상태로 원하는 지수를 맘껏 사용할 수 있지만 밑이 2가 아닌 다른 숫자로 변경하는 것이 힘들게 된다.
밑이 5인 숫자를 사용하고 싶으면 다시 객체를 선언해야 한다. 좀 더 private한 코드를 만들 수 있다는 장점이 있다.

Decorator

데코레이터는 말 그대로 장식해주는 도구라고 생각하면 된다. 무슨 말이냐면, 데코레이터는 decorate하고자 하는 함수를 전이나 후에 감싸주는 역할을 한다. 감싼다는게 어떤 얘기인지 감이 안잡히므로 햄버거를 만들어 먹어보자.

def bread(func):
    def wrapper():
        print("</''''''\>")
        func()
        print("<\______/>")
    return wrapper

def ingredients(func):
    def wrapper():
        print("#tomatoes#")
        func()
        print("~salad~")
    return wrapper
    
@bread
@ingredients
def sandwich(food="--ham--"):
    print(food)

sandwich()

#outputs:
#</''''''\>
# #tomatoes#
# --ham--
# ~salad~
#<\______/>

ham을 토마토와 샐러드로 감싸기 위해서는 햄을 만드는 함수 위에 데코레이터로 토마토와 샐러드 재료를 만드는 함수를 호출하는데 @를 적어서 호출하면 된다.

제일 아래 sandwich()를 호출하는 부분에서 샌드위치를 만들건데 위에 데코레이터가 존재한다. 데코레이터가 존재한다는 것은 데코레이터를 먼저 실행한다는 얘기이다. 그런데 그 데코레이터 위에 또 다른 데코레이터가 존재하므로 그 위에 있는 @bread를 먼저 실행한다.

  • bread함수를 실행했는데 파라미터로 func라는 것이 들어왔다. 참고로 파이썬에서는 함수도 파라미터로 사용할 수 있다.

    • 그러면 어떤 함수가 파라미터로 들어올까?
    • 바로 데코레이터를 호출한 함수가 들어온다.
    • bread함수를 누가 불렀을까...? sandwich? 아니다.
    • ingredients함수가 호출했다. 그 이유는 아래에 설명하도록 하고 일단 ingredients가 호출했으므로 얘가 파라미터로 들어온다.
    • 그럼 이런 식으로 진행이 될 것이다.

      </''''''\>
      ingredients()
      <\______/>

    • 빵 사이에 ingredients함수가 호출되어 있으니 ingredients함수로 가보자.
    • ingredients함수에도 파라미터로 func라는 것이 들어왔다.
    • ingredients는 sandwich함수가 호출했다. 그러므로 파라미터로 들어오는 함수는 sandwich가 된다.
    • ingredients함수에서 볼 수 있듯이 위에 토마토가 있고 아래 샐러드가 있다. 그 사이에 파라미터로 들어온 func인 sandwich가 있다.
    • 그럼 이제 이런 식으로 진행이 될 것이다.

      </''''''\>
      #tomatoes#"
      sandwich()
      ~salad~")
      <\______/>

    • 햄버거의 모양이 거의 완성되었다. 이제 가운데에 sandwich함수가 호출되었기 때문에 마지막으로 sandwich함수를 보자.
    • sandwich의 파라미터로 default value가 ham이 있다.
    • 파라미터로 들어온 재료를 출력하는게 sandwich함수의 끝이다.
    • 가장 아래에 sandwich를 호출했는데 argument로 넘겨주는 값이 없으므로 defual값인 ham이 출력된다.
    • 따라서 햄버거가 다음과 같이 완성된다.

      </''''''\>
      #tomatoes#
      --ham--
      ~salad~
      <\______/>

    • 햄버거 완성
  • 위 과정을 보면 알 수 있듯이 bread() -> ingredients() -> sandwich() 순서로 호출이 된다. 이걸 데코레이터 관점에서 보면 다음과 같다. 주석으로 처리된 숫자 순으로 진행이 된다고 보면 된다.

def bread(func): #---> 4. bread를 호출한게 ingredients이므로 파라미터로 ingredients함수가 들어온다.
    def wrapper():
        print("</''''''\>")
        func() #---> 5. ingredients함수를 호출했으므로 ingredients함수가 실행된다.
        print("<\______/>")
    return wrapper

def ingredients(func): #---> 6. ingredients를 호출한게 sandwich이므로 파라미터로 sandwich함수가 들어온다.
    def wrapper():
        print("#tomatoes#")
        func() #---> 7. sandwich함수를 호출했으므로 sandwich함수가 실행된다.
        print("~salad~")
    return wrapper
    
@bread #---> 3. 그러나 ingredients의 위에 또 데코레이터가 있으므로 얘를 처음으로 실행하게 된다.
@ingredients #---> 2. sandwich함수의 데코레이터로 얘를 sandwich보다 먼저 실행하게 된다.
def sandwich(food="--ham--"):
    print(food)

sandwich() #---> 1. sandwich함수를 호출했다. 그럼 sandwich함수로 가서 그 함수를 실행한다.

Assignment

greeting 함수에 적용될 decorator 함수를 구현하여 greeting 함수에 적용해주세요.

greeting 함수가 호출 되었을때 decorator 함수에 parametor 값이 greeting 함수 리턴값의 다음에 붙혀져서 리턴되어야 합니다.

Decorator 함수의 이름은 welcome_decorator 여야 합니다.

예를 들어, 다음 처럼 정의 하여 welcome_decorator decorator를 적용하여 greeting을 호출하면:

@welcome_decorator
def greeting():
    return "Hello, "
 
greeting()
# result
#"Hello, welcome to WECODE!"

풀이

def welcome_decorator(func):
  def wrapper():
   return (func() + "welcome to WECODE!")
  return wrapper

@welcome_decorator
def greeting():
  return "Hello, "

greeting()

어디에 적용했는가?

파이썬 Replit문제를 푸는데 적용했다. 또한 중첩함수에서 내부함수를 부모 함수 밖에서 호출해서 어떤 에러가 나오는지 확인해 보았다.

데코레이터는 Flask를 사용할 때 나왔던 문법이었다. 그래서 어떻게 동작하는지 이해하는데 쉽게 이해가 되었다.

어려웠던 점은 무엇인가?

일단 중첩함수, 데코레이터가 들어는 봤지만 사용해 본적은 없기 때문에 처음에 조금 헤맸다. 그러나 여러 글을 보면서 사용법을 익혔다. 배운 것보다 더 어렵게 사용하는 경우가 분명 있을 것이다. 더 어려운 상황도 헤쳐나가기 위해 기초부터 튼튼히 공부할 것이다.

참고자료
https://velog.io/@matisse/Python-04.-nested-function

profile
https://oraange.tistory.com/ 여기에도 많이 놀러와 주세요

0개의 댓글