[python] 중첩 함수와 데코레이터

Suhyeon Lee·2025년 1월 15일
0

자기주도학습

목록 보기
81/83
post-thumbnail

출처 1
출처 2

1. 중첩 함수(Nested Function)

  • 함수 안에 정의된 또 다른 함수
  • 내부 함수라고도 함
    • 중첩 함수를 감싸고 있는 함수를 외부 함수라고도 함
  • 중첩 함수는 해당 함수가 정의된 함수 내에서만 호출할 수 있음
# 외부 함수 선언
def outer_function():

	# 중첩 함수( = 내부 함수) 선언
    def inner_function():
        print("this is a inner function")

    inner_function()

outer_function()


# 결과
>>> this is a inner function

중첩 함수를 사용하는 이유

  1. 가독성을 높이기 위해
    • 반복되는 코드 블럭을 함수로 정의해서 효과적으로 코드를 관리하고 가독성을 높일 수 있음
  2. 클로저(Closure)를 사용하기 위해

클로저(Closure)?

  • 함수와 해당 함수가 가지고 있는 데이터(지역 변수, 코드 등)를 함께 복사, 저장하고 있다가 함수를 호출할 때 다시 꺼내서 사용하는 함수
  • 예시:
def generate_power(base_number):

	# 중첩 함수에서 외부 함수의 변수에 접근 가능
    def nth_power(power):
        return base_number ** power

    # 중첩 함수 이름을 리턴
    return nth_power

# generate_power(2)함수를 calculate_power_of_two라는 변수에 저장
calculate_power_of_two = generate_power(2)  

# 저장해둔 함수를 호출. 즉, Closure호출. Closure호출시 내부 함수가 실행됨
print(calculate_power_of_two(7))            

# 결과 (2**7의 결과)
>>> 128

## 참고 ##
print(calculate_power_of_two)
# 결과
<function generate_power.<locals>.nth_power at 0x7f8fd01ca4c0>
  • calculate_power_of_two에 저장된 함수가 바로 closure
    • calculate_power_of_two = generate_power(2) 에서 generate_power 함수는 호출이 종료되었고 calculate_power_of_two()은 결국 nth_power 함수를 호출한 것
      • 즉, closure를 이용하면 부모 함수(=외부 함수)가 리턴한 중첩 함수를 통해서 외부 함수의 변수에 접근할 수 있음
      • 따라서 부모 함수의 변수를 외부로부터 직접적인 접근은 막으면서도 중첩 함수를 통해서 격리된 부모 함수의 변수를 이용한 연산은 가능하게 해주는 것
  • 클로저(closure)는 언제 사용할까?
    • 어떠한 정보를 기반으로 연산을 실행하고 싶지만 기반이 되는 정보는 접근을 제한하여 노출이 되거나 수정이 되지 못하게 하고 싶을 때
    • 일반적으로 제공해야 할 기능(method)이 적은 경우, closure를 사용하기도 하고 제공해야 할 기능(method)가 많은 경우는 class를 사용하여 구현하기도 함

데코레이터(Decorator)

  • 함수 앞뒤에 기능을 추가해서 손쉽게 함수를 활용할 수 있는 기법
    • 함수(메서드)를 장식한다고 해서 이런 이름(decorate = 장식하다)
  • 데코레이터는 Closuer 함수를 활용한 기법
    • 즉, 중첩 함수(nested function)을 리턴하는 함수만 decorator 함수로 사용될 수 있음
    • 여러 개의 함수가 연속적으로 호출이 자동으로 되게 해줘야 하기 때문

사용법

유료 회원에 한해서 대박 주식 정보를 알려주는 함수인 jackpot_stock_info가 있다고 하자.
jackpot_stock_info 함수 실행전에 해당 유저가 유료 유저인지 꼭 확인해야 한다.
이를 데코레이터로 구현해보자.

# 데코레이터 함수 작성
def is_paid_user(func): # func는 이 함수 안에 넣을 함수 이름을 말함
    user_paid = True    # 테스트를 위해 true로만 설정
	
    # 실제 실행하려고 한 함수를 중첩 함수를 통해 실행시킨다.
    def wrapper():      # 호출할 함수를 감싸는 함수 
        if user_paid:
            return func()
        else:
            return
            
    return wrapper      # closure 함수로 만듦

# 데코레이터 함수 적용
@is_paid_user
def jackpot_stock_info():
    return "buy now!"

# 데코레이터가 적용된 함수 실행
print(jackpot_stock_info())

# 결과
>>> buy now!
  • 위 예제를 데코레이터를 사용하지 않고 코드를 짠다면:
def is_paid_user(func):
    user_paid = True  

    def wrapper():
        if user_paid:
            return func()
        else:
            return
    return wrapper


def jackpot_stock_info():
    return "buy now!"

# 먼저 데코레이터 함수를 실행시키는 구문(=이 함수를 실행시킬 세팅이 되었다고 보면됨)이 어떤 변수에 할당되고, 
# 데코레이터 함수를 호출하는 구문에서는 인자값으로 실제 호출하고자 하는 jackpot_stock_info 함수가 전달됨
check_user_first = is_paid_user(jackpot_stock_info)

# 데코레이터 함수(->클로저 개념 이용)가 실행시키는 변수를 함수처럼 실행하면, 
# 데코레이터 함수의 내부 함수가 호출되는데 내부 함수는 jackpot_stock_info 함수를 리턴하므로 이때 jackpot_stock_info 함수가 실행된다.
print(check_user_first())

왜 사용할까?

  • 여러 함수에 동일한 기능을 @decorator_function 하나로 간편하게 추가할 수 있음
    • 예를 들면, 파라미터가 있는 함수에 파라미터 유효성 검사가 필요할 경우, 데코레이터를 사용하면 매우 편리
  • 데코레이터를 사용하지 않는다면 파라미터가 있는 함수가 있을 때마다 유효성 검사 코드를 넣어야 하고, 만약 그 유효성 검사 코드에 수정이 필요하다면 관련 함수를 모두 수정해야 하는 불편함이 있기 때문

Closure function

  • 함수와 해당 함수가 가지고 있는 데이터를 함께 복사, 저장해서 별도 함수로 활용하는 기법
    • First-class 함수와 동일
  • 외부 함수가 소멸되더라도, 외부 함수 안에 있는 로컬 변수 값과 중첩함수(내부함수)를 사용할 수 있는 기법
  • 함수형 프로그래밍에서부터 고안된 기법

In [1]:

def outer_func(num):
    # 중첩 함수에서 외부 함수의 변수에 접근 가능
    def inner_func():
        print(num)
        return '안녕'
    
    return inner_func                 # 중첩 함수 이름을 리턴합니다.

In [2]:

closure_func = outer_func(10)    # <--- First-class function
closure_func()            # <--- Closure 호출 

Out [2]:

>>> 10
>>> '안녕'
  • 위의 예제에서 closure_func이 바로 closure
    • closure_func = outer_func(10) 에서 outer_func 함수는 호출 종료
    • closure_func() 은 결국 inner_func 함수를 호출
    • outer_func(10) 호출 종료 시 num 값은 없어졌으나, closure_func()에서 inner_func이 호출되면서 이전의 num값(10)을 사용함
  • 심지어 outer_func 함수를 아예 삭제해버려도 fn(), 즉 inner_func() 와 num값(10)은 살아있음
    In [3]:
del outer_func

In [4]:

closure_func()

Out [2]:

>>> 10
>>> '안녕'

언제 closure를 사용할까?

  • closure는 객체와 유사
  • 일반적으로 제공해야 할 기능(method)이 적은 경우, closure를 사용하기도 함
    • 제공해야 할 기능(method)가 많은 경우 등은 class를 사용하여 구현

예제

# before
def calc_square(digit):
    return digit * digit

def calc_power_3(digit):
    return digit * digit * digit

def calc_quad(digit):
    return digit * digit * digit * digit
    
print (calc_square(2))
print (calc_power_3(2))
print (calc_quad(2))

# 결과
>>> 4
>>> 8
>>> 16

# after
def calc_power(n):
    def power(digit):
        return digit ** n
    return power
 
power2 = calc_power(2)
power3 = calc_power(3)
power4 = calc_power(4)

print (power2(2))
print (power3(2))
print (power4(2))

# 결과
>>> 4
>>> 8
>>> 16

1에서 5까지 1승부터 5승까지 출력하기 (위 calc_power() 함수를 사용해서 list_data 리스트 변수에 1승부터 5승까지 계산 클로져 함수를 넣어서 사용)

list_data = list()
for num in range(1, 6):
    list_data.append(calc_power(num))

for func in list_data:
    print(func(2))

# 결과
>>> 2
>>> 4
>>> 8
>>> 16
>>> 32
profile
2 B R 0 2 B

0개의 댓글

관련 채용 정보