python first class function 에 대해 알아보자 (feat. Closure)

June 29,2020

, 3 min read

이미지를 불러오지 못했습니다..ㅠㅠ

First class citizen

먼저 Fisrt class citizen 의 개념을 배워야한다. 우리가 일반적으로 사용하는 변수의 성질과 같다고 생각하면 된다.

All items can be the actual parameters of functions

All items can be returned as results of functions

All items can be the subject of assignment statements

All items can be tested for equality.

First class function

함수를 Fisrt class citizen 처럼 다루는 것이 First class function 의 컨셉이다.

함수가 다른 함수의 파라미터로 사용되고, 함수가 return 되며, 변수에 할당되기도 한다.

Higher-order function 이 대표적인 예라고 한다. Higher-order function 에 대해 알아보자

Higher-order function

Higher-order function 은 이렇게 설명된다. 'function 을 arg 로 받아 function 의 result 를 return 한다.'라는 내용이다.

takes one or more functions as arguments (i.e. procedural parameters),

returns a function as its result.

코드로 보자.

 def twice(f):
    def result(a):
        return f(f(a))
    return result

plusthree = lambda x: x + 3

g = twice(plusthree)
print(g(7)) # 13

함수를 파라미터로 받아 두번 실행하는 함수를 생성하여, 변수에 할당하여, 실행한다.

다른 예제

다른 예제를 보자.

def square(x): 
    return x * x
def cube(x): 
    return x * x * x

def print_result(x, func):
    """recieve a function and execute it and return result"""
    return func(x)

do_square = square     # assigning square function to a variable
do_cube = cube         # assigning cube function to a variable

res = print_result(5, do_square)   # passing function to another function
print(res) # 25
res = print_result(5, do_cube)
print(res) # 125

이를 활용하여 Closure 에서 사용되는데, Closure 에 대해 알아보자.

Closure

In programming languages, a closure, also lexical closure or function closure, is a technique for implementing lexically scoped name binding in a language with first-class functions.

Operationally, a closure is a record storing a function together with an environment.

'lexically scoped name binding 을 실행 하기 위한 기술' 이라고 한다.

예제를 보면 쉽게 이해할 수 있다.

예제

코드를 보자, 상위 scope 에서 함수 내부 변수의 값을 할당하는 것을 볼 수 있다.

def f(x):
    def g(y):
        return x + y
    return g  # Return a closure.

def h(x):
    return lambda y: x + y  # Return a closure.

# Assigning specific closures to variables.
a = f(1)
b = h(1)


# Using the closures stored in variables.
assert a(5) == 6
assert b(5) == 6

# Using closures without binding them to variables first.
assert f(2)(5) == 7  # f(2) is the closure.
assert h(2)(5) == 7  # h(2) is the closure.

이를 활용하면 closure 내부에 data를 cash 할 수 있어, 적절히 잘 사용하면 성능 향상에도 도움이 될듯 하다.

def in_cache(func):
    cache = {}
    def wrapper(n):
        try :
            return cache[n]
        except KeyError:
            print("No caching ", end='')
            cache[n] = func(n)
            return cache[n]
        finally:
            print(cache)

    return wrapper

def factorial(n):
    ret = 1
    for i in range(1, n+1):
        ret *= i
    return ret

factorial = in_cache(factorial)

factorial(3)
factorial(5)
factorial(10)
# No caching {3: 6}
# No caching {3: 6, 5: 120}
# No caching {3: 6, 5: 120, 10: 3628800}
factorial(5)
factorial(5)
factorial(10)
# {3: 6, 5: 120, 10: 3628800}
# {3: 6, 5: 120, 10: 3628800}
# {3: 6, 5: 120, 10: 3628800}

참고