중첩함수(Nested Function) : 상위 부모 함수 안에서만 호출 가능한 함수. 부모 함수 밖에서는 호출할 수 없다.
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!")
중첩함수가 부모 함수의 변수나 정보를 가두어 사용하는 것을 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한 코드를 만들 수 있다는 장점이 있다.
데코레이터는 말 그대로 장식해주는 도구라고 생각하면 된다. 무슨 말이냐면, 데코레이터는 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
라는 것이 들어왔다. 참고로 파이썬에서는 함수도 파라미터로 사용할 수 있다.
</''''''\>
ingredients()
<\______/>
</''''''\>
#tomatoes#"
sandwich()
~salad~")
<\______/>
</''''''\>
#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함수로 가서 그 함수를 실행한다.
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를 사용할 때 나왔던 문법이었다. 그래서 어떻게 동작하는지 이해하는데 쉽게 이해가 되었다.
일단 중첩함수, 데코레이터가 들어는 봤지만 사용해 본적은 없기 때문에 처음에 조금 헤맸다. 그러나 여러 글을 보면서 사용법을 익혔다. 배운 것보다 더 어렵게 사용하는 경우가 분명 있을 것이다. 더 어려운 상황도 헤쳐나가기 위해 기초부터 튼튼히 공부할 것이다.