[Python] 53. Nested Function

Jeongyun Heo·2021년 1월 15일
0

Python

목록 보기
32/36
post-thumbnail

53. Nested Function

파이썬 코딩 도장 33.2 함수 안에서 함수 만들기
https://dojang.io/mod/page/view.php?id=2365

다른 구문들과 마찬가지로 함수도 중첩 함수로 사용할 수 있다.

즉, 함수 안에 함수를 선언할 수 있다.

def parent_function():
    def child_function():
        print('this is a child function')

    child_function()


parent_function()  # this is a child function

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

중첩함수를 사용하는 이유

  1. 가독성
  2. Closure

가독성

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

중첩함수를 사용하는 이유도 동일하다.

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

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!')


print_all_elements([])   # 빈 리스트를 인수로 줌
---------------------------------------------
There is nothing!
def outer(outer_arg):
    def inner(inner_arg):
        return "내부 함수에 전달된 인수: '%s'" % inner_arg

    return inner(outer_arg)  # 바깥쪽 함수의 매개변수의 인수를 받음


result = outer('안녕!')
print(result)
--------------------------------------------------------
내부 함수에 전달된 인수: '안녕!'

Closure

중첩함수를 사용하는 다른 큰 이유는 closure 이다.

Closure의 사전적 의미는 '폐쇄'이다.

폐쇄란 무언가를 닫아서 가둔다는 의미가 있다.

파이썬에서 사용하는 closure도 어떤 것을 외부로부터 격리하여 사용한다는 느낌이 크다.

중첩 함수가 부모 함수의 변수나 정보를 가두어 사용하는 것을 closure라고 한다.

그리고 부모함수는 중첩 함수를 리턴해준다.

그러면 부모함수의 변수를 외부로부터 직접적인 접근은 격리하면서 중첩 함수를 통해 격리된 부모함수의 변수를 사용한 연산은 가능하게 해주는 것이다.

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

closure는 어떠한 정보를 기반으로 연산을 실행하고 싶지만 기반이 되는 정보는 접근을 제한하여 노출이 되거나 수정이 되지 못하게 하고 싶을 때 사용한다.

주로 factory 패턴을 구현할 때 사용된다.(factory 패턴은 무언가를 생성해 내는 패턴이다. 주로 함수나 오브젝트를 생성할 때 사용된다.)

Factory에서 무언가를 생성해 내기 위해서는 설정값이 필요하다.

그 설정값을 노출하지 않아서 수정이 불가능하게 하면서 해당 설정값을 기반으로 한 연산을 할 수 있는 함수를 만들 때 closure를 사용할 수 있다.

예를 들어 보자.

다음 함수는 x의 y제곱을 구하는 함수이다.

def calculate_power(number, power):
    return number**power


print(calculate_power(2, 7))
-------------------------------------
128

다음 함수는 특정 숫자의 y제곱을 구하는 함수이다.

def calculate_power(power):
    return 2**power


print(calculate_power(7))
------------------------------
128
def calculate_power(power):
    return 2**power


print(calculate_power(10))
----------------------------
1024

위의 함수는 2의 y제곱만 구할 수 있다.

특정 숫자의 y제곱을 구하는 함수가 필요하지만 2가 아니라 설정되는 수의 y제곱을 구하는 함수는 어떻게 구현할 수 있을까?

이때 closure를 사용할 수 있다.

def generate_power(base_number):
    def nth_power(power):
        return base_number**power

    return nth_power


calculate_power_of_two = generate_power(2)  # 클로저

print(calculate_power_of_two(7))
print(calculate_power_of_two(10))

calculate_power_of_seven = generate_power(7)  # 클로저

print(calculate_power_of_seven(3))
print(calculate_power_of_seven(5))
--------------------------------------------
128    # print(calculate_power_of_two(7))
1024   # print(calculate_power_of_two(10))
343    # print(calculate_power_of_seven(3))
16807  # print(calculate_power_of_seven(5))

부모 함수의 변수인 base_number가 중첩 함수에 격리되어 사용되었다.

중첩함수가 부모함수의 리턴값으로 리턴된다.

Decorator가 무엇인가요?

Decorator 라는 단어의 뜻은 장식 혹은 장식하는 사람이라는 뜻이 있다. 즉, 뭔가 장식에 관련된 것이라는 걸 알 수 있다.

다음은 대박 주식 정보를 리턴하는 함수입니다.

def jackpot_stock_information():
    return "계시가 내려졌습니다. 삼성전자를 사세요!"

그러나 이 대박 주식 정보는 유료 회원만 받을 수 있다. 그래서 이 함수가 호출되기 전에 해당 유저가 유료 회원인지 확인해야 한다.

해당 유저가 유료 회원인지 확인해주는 함수는 다음과 같다.

def is_paid_user():
    return True

다음과 같이 jackpot_stock_information 함수를 호출하기 전에 항상 is_paid_user 함수가 먼저 호출 되어야 한다.

if is_paid_user():
    jackpot_stock_information()

지금은 아무 문제가 없어 보이지만 jackpot_stock_information 함수가 여러 곳에서 자주 사용될 때 문제가 발생한다.

jackpot_stock_information 함수가 호출될 때 is_paid_user 함수가 무조건 먼저 호출 되어야 하는데, 이 연결고리를 잊어 먹을 확률이 있다는 것이다.

즉, jackpot_stock_information 함수가 is_paid_user 함수가 먼저 호출되지 않은 채로 호출 될 수 있다는 것이다.

그럼 jackpot_stock_information 함수를 호출할때 알아서 자동으로 is_paid_user 함수가 강제적으로 먼저 호출되게 할 수 있는 방법은 없을까?

바로 여기서 decorator가 사용된다. Decorator를 적용하면 다음과 같다.

@is_paid_user
def jackpot_stock_information():
    return "계시가 내려졌습니다. 삼성전자를 사세요!"

jackpot_stock_information 함수 정의 위에 is_paid_user 함수가 골뱅이 마크(@)와 함께 달려 있는 것을 볼 수 있다.

그래서 장식, 즉 decorator 라고 하는 것이다.

함수를 만들 때 함수 위에 다른 함수를 골뱅이 마크를 사용해서 장식처럼 달아 놓는 것이다.

저렇게 decorator로 달아놓으면 해당 함수가 호출 되기 전에 장식으로 달린 함수가 먼저 호출이 되고, 그 후에 본 함수가 호출이 된다.

Decorator 함수 구현 방법

그럼 아무 함수나 다 decorator로 장식 할 수 있는 걸까?

아니다.

Decorator는 중첩 함수를 리턴하는 함수에만 사용할 수 있다.

그 이유는 간단하다.

Decorator의 기능을 다르게 설명 하면 chain of functions, 즉 자동으로 여러 개의 함수를 연속적으로 호출해 주는 것이다.

그러려면 마지막 함수를 제외한 함수들이 그 다음 함수를 리턴해주어야 파이썬이 함수들을 차례대로 호출해 줄 수 있다.

만약 다음 함수를 리턴하지 않고 다른 값을 리턴해버리면 그 다음 함수로 넘어가지 못하고 그냥 함수 실행이 종료되어 버리기 때문이다.

그렇기 때문에 decorator 함수는 그 다음 함수를 리턴해주어야 한다.

그럼 is_paid_user 함수를 decorator로 만들어 보자.

다음과 같이 is_paid_user 함수를 만든다.

def is_paid_user(func):  # 호출할 함수를 매개변수로 받음
    user_paid = True     # 간단화 하기 위해 무조건 True

    def wrapper(*args, **kwargs):  # 호출할 함수를 감싸는 함수
        if user_paid:    # user_paid = True이므로 무조건 참
            func()       # 매개변수로 받은 함수를 호출
        else:
            return

    return wrapper       # wrapper 함수 반환

그리고 is_paid_user 함수를 decorator로 장식해보자.

@is_paid_user
def jackpot_stock_information():
    return "계시가 내려졌습니다. 삼성전자를 사세요!"

Assignment

Decorator를 구현해보세요.

Decorator는 앞서 배운 closure를 한단계 더 나아가 사용하는 고급 기능입니다.

Decorator는 closure처럼 중첩함수를 리턴하는 함수 이며 다른 함수에 적용해서, 적용된 함수가 실행되기 전에 무조건 실행됩니다. 즉 특정 함수를 실행하기 전에 강제적으로 다른 함수가 먼저 실행된후 실행되도록 하는 강제성을 제공하는 기능입니다.

더 자세한 내용은 아래 링크의 노션 자료를 참조하세요:

https://www.notion.so/wecode/Decorator-8e6eb41d0f95474c94ed0136bcbfc2b1

왼쪽 상단의 greeting 함수에 적용될 decorator 함수를 구현하여 greeting 함수에 적용해주세요.

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

예를 들어, 다음 처럼 정의 하여 welcome_decorator decorator를 적용하여 greeting을 호출하면 결과값은 다음과 같아야 합니다.

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


greeting()
-----------------------------
"Hello, welcome to WECODE!"

👇 정답

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

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

greeting()

decorator 함수는 중첩 함수를 리턴하는 함수

decorator 함수는 다음 함수를 리턴해주어야 한다

greeting 함수의 리턴값 + "welcome to WECODE!"

welcome_decorator(greeting)

0개의 댓글