def func(*args, **kwargs):
    ''' do something '''
    return

일반적으로 함수를 선언하면 작성하는 형태이다.

def로 함수를 선언하고, 그 안에 받아들인 매개변수를 작성해준다.

그리고 함수의 내부 코드를 작성한 다음, 경우에 따라 return을 진행한다.

 

def Calculator(A, op, B):
    if op == "+": return A + B
    if op == "-": return A - B
    if op == "*": return A * B
    if op == "/": return A / B

만약 계산기 함수를 구현하고 싶어, 위처럼 Calculator를 작성했다고 가정해보자.

'사칙연산을 수행'하는데 있어서 아무런 문제가 없는 코드이다.

만들고 싶은 것은 계산기 코드지, 사칙연산을 수행하는 코드가 아니다.

제곱 연산을 하려면? 정수와 실수를 구분하려면? 계산하려는 값이 2개가 아니라면?

이런 생각을 거쳐, 나아가 이 코드는 SRP를 지키지 않은 코드이다.

 

SRP(Single Responsibility Principle; 단일 책임 원칙)는 하나의 모듈이 하나의 책임만을 져야 한다는 의미다.

SRP는 다섯 가지 SOLID 애자일 원칙 중 하나이다. https://en.wikipedia.org/wiki/SOLID

쉽게 말하면 위의 계산기 코드는 사칙연산의 4가지 책임을 지는 코드라는 이야기이다. 

 

def outer_func():
    def inner_func():
        print("Hello, World!")
        
    inner_func()
    
outer_func()	# Hello, World!

이럴 때 Nested function를 사용할 수 있다.

 └ 단순히 SRP만을 위해 존재하는 개념도, SRP가 핵심 이유도 아니다.

 └ 흐름상 이렇게 설명하는 것이 자연스러워 빌드업을 한 것이니 참고만 하자.

 

Nested는 중첩했다는 의미로, 여러 개의 함수를 중첩하여 사용한다.

Nested function 대신 Inner function라고 표현하기도 한다.

outer_func()은 parent function이라 하고, inner_func()은 child function이라고도 한다.

Nested function은 다양한 이점이 있어 사용한다. 그 이유들을 살펴보자.

 

def func():
    dp = [1] * 10
    for i in range(2, 10):
        dp[i] = dp[i-1] + dp[i-2]
        
    ''' do something one '''
    
    dp = [1] * 10
    for i in range(2, 10):
        dp[i] = dp[i-1] + dp[i-2]
        
    ''' do something two '''
    
    dp = [1] * 10
    for i in range(2, 10):
        dp[i] = dp[i-1] + dp[i-2]
        
    ''' do something three '''

어떤 함수를 위처럼 작성했다고 가정해보자.

얼핏봤을 때는 문제가 없지만 똑같은 코드가 반복해서 나타나고 있다.

우리가 함수를 처음 사용한 이유를 생각해보자. 반복을 줄이기 위함이다.

 

def func():
    def maybeFibonacci():
        dp = [1] * 10
        for i in range(2, 10):
            dp[i] = dp[i-1] + dp[i-2]
      
      
    maybeFibonacci()
    ''' do something one '''
    
    maybeFibonacci()        
    ''' do something two '''
    
    maybeFibonacci()
    ''' do something three '''

1. 가독성이 뛰어나다.

반복적으로 나타나는 부분을 inner_function으로 만들 수 있다.

반복하는 부분을 줄여 코드 길이도 짧아져 가독성이 높아지고, 유지보수도 쉬워진다.

 

def Calculator():
    def add(): ''' code '''
    def sub(): ''' code '''
    def mul(): ''' code '''
    def div(): ''' code '''
    def pow(): ''' code '''

2. SRP를 만족한다.

첫 번째 이유와 비슷한 맥락이다. 함수를 작성하는 또 다른 이유는 기능 분할(모듈화)이다.

Calculator 함수 내부에서 자체적으로 코드를 구현하는 게 아니라 각각을 함수로 만들어준다.

이렇게 기능별로 코드를 나누다보면 자연스레 SRP를 만족하게 된다.

 

def increment(number):
    def inner_increment():
        return number + 1
    return inner_increment()


# Call inner_increment()
inner_increment()

'''
Traceback (most recent call last):
  File "<input>", line 1, in <module>
    inner_increment()
NameError: name 'inner_increment' is not defined
'''

3. Encapsulation을 제공한다.

말그대로 내부 선언 함수는 상위 함수에서만 호출이 가능하다는 특징이다.

increment 함수 내부에, 숫자를 1 늘려주는 inner 함수를 따로 작성한 뒤, return해준다.

어떤 수 N이 있을 때 increment(N)을 하면 N+1을 얻을 수 있다.

하지만 직접적으로 inner_increment()를 호출하는 것은 불가능하다.

 

def generate_power(exponent):
    def power(base):
        return base ** exponent
    return power
 
raise_two = generate_power(2)
raise_three = generate_power(3)

raise_two(4)	# 16
raise_two(5)	# 25

raise_three(4)	# 64
raise_three(5)	# 125

다른 말로 Closure를 제공한다고도 표현한다. Closure는 폐쇄, 금지 등의 뜻을 갖고 있다.

Closure의 예시를 잘 보여준 함수가 위에 있다.

generate_power라는 부모 함수는 exponent(지수)를 매개변수로 받는다.

하지만 exponent는 중첩 함수 power 내부에서만 가두어져 사용이 가능하다.

부모 함수의 정보를 외부에서 직접 접근하는 것을 막으면서, 연산은 가능하게 해주는 것. 이를 Closure라고 한다.

 └ 부모 함수의 매개변수 뿐만 아니라, 부모 함수의 변수를 중첩 함수에서만 변형 가능하다면 이 또한Closure다. 

 

generate_power(2)처럼 부모 함수에 인자(exponent = 2)를 넣어준다.

그리고 부모 함수가 담긴 변수(raise_two)에 중첩 함수 인자 값(base = 4)을 넣어주면 함수 return(16)을 구할 수 있다.

코드를 잘 보면 func()이 아니라 func을 return한다. 함수명을 그대로 반환한다는 점에 유의하자. 

그외 다양한 Closure 예제는 RealPython을 참고하자.

 

https://velog.io/@inyong_pang/Python-Nested-Function-2wk42jt94r

이해가 안 된다면 사진으로 이해하면 더 와닿는다.

위에서 설명한 코드와는 살짝 다른 코드지만, 전체적인 흐름을 보면 이해가 빠르다.

 └ 위의 코드는 RealPython의 코드로 부모 함수 매개변수가 exponent(지수)다.

 └ 사진의 코드는 타 블로그의 코드로 부모 함수 매개변수가 base(밑)다 

 

def add_messages(func):
    def _add_messages():
        print("This is my first decorator")
        func()
        print("Bye!")
    return _add_messages


@add_messages
def greet():
    print("Hello, World!")


greet()
# This is my first decorator
# Hello, World!
# Bye!

4. Decorator를 지원한다.

Decorator에 대해 조금 더 자세하게 다룰 필요가 있어 추후에 따로 작성한다.

간단하게 소개하자면, 함수를 매개변수로 받아, 중첩 함수를 실행하는 것이다.

중첩 함수(nested function)을 이용한 개념이기에 nested function을 return하는 함수만 decorator를 사용할 수 있다.

위 코드는 간단한 decorator 예제이다.

 

 

참고한 문서 자료 : https://realpython.com/inner-functions-what-are-they-good-for/

참고한 문서 자료 : https://en.wikipedia.org/wiki/SOLID

참고한 문서 자료 : https://en.wikipedia.org/wiki/Nested_function

참고한 문서 자료 : https://en.wikipedia.org/wiki/Single-responsibility_principle

참고한 문서 자료 : https://en.wikipedia.org/wiki/Closure_(computer_programming) 

참고한 웹 사이트 : https://velog.io/@inyong_pang/Python-Nested-Function-2wk42jt94r

참고한 웹 사이트 : https://softwareengineering.stackexchange.com/questions/232766/when-to-use-python-function-nesting

'Computer Science > 파이썬(Python)' 카테고리의 다른 글

Python float OverflowError  (0) 2023.09.19
Python Namespace(Scope)  (0) 2023.08.18
Python Overflow 심화  (0) 2023.08.18
Python Overflow  (0) 2023.08.17
Python Logical operator(AND, OR) 설명  (0) 2023.08.15

+ Recent posts