ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 객체로서의 함수
    Python 2019. 8. 18. 23:05

    함수형 언어인 파이썬의 특징에 대해 '전문가를 위한 파이썬'을 바탕으로 정리하였습니다.

     

    First-class function

    파이썬은 일급 클래스(또는 객체) 함수(First class function)입니다. 일급 클래스 함수의 조건은 다음과 같습니다.

    1. 함수를 매개 변수(argument or parameter)로 사용할 수 있는가?
    2. 함수를 변수(variable)로 사용할 수 있는가??
    3. 함수를 반환(return)할 수 있는가???
    4. 런타임(run-time)에 생성할 수 있는가???

    다른 언어(일급 클래스 함수를 사용하지 않는)와 달리 함수 자체를 변수, 매개 변수 및 반환 값으로 사용할 수 있습니다. 아래의 코드는 '전문가를 위한 파이썬'에서 일급 클래스 함수를 설명하기 위한 코드입니다.

    # factiorial 함수 정의
    >>> def factorial(n):
    ... return 1 if n < 2 else n * factorial(n-1)
    
    # 함수를 변수에 할당
    >>> fact = factorial  
    >>> fact
    <function factorial at 0x...>  
    # fact 변수는 함수 factorial을 갖으며 함수로써 사용할 수 있음
    >>> fact(5)  # 
    120
    # map()의 인수로 factorial()를 사용
    >>> map(factorial, range(11))
    <map object at 0x...>
    >>> list(map(fact, range(11)))
    [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]

     

    lambda : 익명 함수(anonymous function)

    익명 함수는 주로 고위 함수(함수를 인수로 받거나 반환하는 함수-map, fileter, reduce..)의 인수로 사용하길 지향합니다. 가독성이 떨어지고, lambda 본체에 파이썬 문장(할당, while, try 등)을 사용할 수 없는 제한이 있기 때문입니다. 

    • f = lambda a, b: a+b

      → 변수를 설정하지 않을 경우 다음 라인에서 lambda function은 메모리에서 사라짐

      → lambda 자체에 return을 포함하기 때문에 lambda 식을 구현할 경우 return을 사용하지 않음

    x = lambda a: a + 10
    print(x(5))  # 익명함수를 함수로 사용(지양)
    
    def myfunc(n):
        return lambda a: a * n  # 반환값으로 사용할 수 있다(함수 식을 반환)
    
    mydoubler = myfunc(2)  # myfunc(2) -> lambda a: a * 2
    
    print(mydoubler)  # <function myfunc.<locals>.<lambda> at 0x10196d1e0>
    print(myfunc)  # <function myfunc at 0x10196d268>
    print(mydoubler(11))  # mydoubler(11) -> lambda 22: 11 * 2
    

    익명 함수를 이용한 문자수 카운터

    from functools import reduce
    
    li = ['a', 'b', 'a', 'b', 'b', 'a', 'c', 'a']
    result = reduce(lambda dic, ch: dic.update({ch: dic.get(ch, 0)+1}) or dic, li, {})
    # dic.update()가 반영 되었는지 확인하기 위해서 or를 사용
    	# -> dic.update()는 None을 반환하므로 (dic.update or a) 연산을 진행할 경우 항상 a를 반환
    	# dic.update 실행 후 None이 반환 -> or에 의해 dic 실행 -> 업데이트 된 dic을 리턴
    print(result)
    # Output:
    # {'a': 4, 'b': 3, 'c': 1}

     

    High-order function

    함수를 인수로 받거나, 함수를 반환할 수 있는 함수를 고위 함수(high-order function)라고 합니다. 자주 사용하는 고위 함수로는 map, filter, reduce(3.0부터 내장 함수에서 deprecated)이 있습니다. 하지만 이러한 고위 함수는 같은 기능에 더 나은 가독성을 갖는 지능형 리스트 및 제너레이트 표현식으로 대체되고 있습니다. 아래는 고위 함수를 이용한 간단한 예제입니다.

     

    filter(첫 번째 인수(함수)에 두 번째 인수(순환 객체)를 하나씩 대입하여 결과가 true 일 경우 반환) - Doc

    li = [-3, 5, 1, 2, -5, -4, 14]
    f = filter(lambda element: element > 0, li)  
    print(next(f))  # 5
    print(next(f))  # 1
    print(next(f))  # 2
    print(next(f))  # 14
    print(next(f))  # StopIteration 발생

    map(첫 번째 인수(함수)에 두 번째 인수(순환 객체)를 매핑하여 결과를 반환) - Doc  

    li = [2, 3, 5, 7]
    
    m = map(lambda x: x ** 2, li)
    print(next(m))  # 4
    print(next(m))  # 9
    print(next(m))  # 25
    print(next(m))  # 49

    (filter 및 map은 제너레이터(반복 가능 객체)를 반환하기 때문에 list()를 감싸 하나의 list 객체로 생성하거나, next() 함수의 호출이 필요)

     

    functools.reduce(두 번째 인자인 순환 객체를 첫 번째 인자의 함수와 누적 연산하여 하나의 결과값으로 출력) - Doc

    from functools import reduce
    
    li = [2, 3, -5, 6, -2, 1, -10]
    # container의 순서대로 값을 가져온다(ex : li[0], li[1] ...)
    result = reduce(lambda a, b: a + b, li)  
    # original container에 영향을 주지 않는다
    print(result)  # -5

    high-order function vs list comprehension

    def factorial(n):
        '''returns n!'''
        return 1 if n < 2 else n * factorial(n - 1)
    
    fact = factorial
    a = list(map(fact, range(6)))  
    # output: [1, 1, 2, 6, 24, 120]
    b = [fact(n) for n in range(6)]  
    # output: [1, 1, 2, 6, 24, 120]
    
    c = list(map(factorial, filter(lambda n: n % 2, range(6))))
    # output: [1, 6, 120]
    d = [factorial(n) for n in range(6) if n % 2] 
    # output: [1, 6, 120]

     

    고위 함수는 제너레이터를 반환하기 때문에 함수의 실행 시점을 결정할 수 있습니다. 이는 원하는 시점에 함수를 실행시켜 그 결과를 반환할 수 있고, 설계에 따라 메모리를 절약할 수 있다는 장점을 갖습니다. 이렇게 한꺼번에 실행되지 않고 원하는 시점에 사용자가 접근할 수 있는 프로그래밍 기법을 "느긋한 계산법(lazy evaluation)"이라고 합니다.

    li = [-3, 5, 1, 2, -5, -4, 14]
    
    def func(x):
        print("func executed")
        return x > 0
    
    f = filter(func, li)
    print("다른 작업")
    print(next(f))   # next 함수를 이용해 특정 시점의 결과에 접근 가능
    print("another job")
    print(next(f))
    print("another job")
    print(next(f))
    print("another job")
    print(next(f))
    print("another job")
    
    # Output:
    다른 작업
    func executed    # (-3)음수일 경우 "func excuted"를 출력하고 반환값은 False가 된다
    func executed    # 이는 return이 아니라 함수 내부의 print()에 의한 것
    5	# <--여기까지가 첫 번째 next(f)에 의해 실행된 시점
    another job
    func executed
    1
    another job
    func executed
    2
    another job
    func executed    # (-5)
    func executed    # (-4)
    func executed
    14
    another job

     

     

     

     

     

     

    일급 객체 클래스 함수에 대해 간단히 정리하고자 시작했는데.. 몇 시간동안 정리를 했는지 모르겠네요. 다시보니 새롭게 느껴지는 내용도 있고, 알았던 내용도 다른 사람들도 참고할 수 있다는 생각에 다시 한번 확인하고 자료를 검색해보면서 정리하게 되었습니다. 작성 중 본문에 포함되지 않은 제너레이터에 대한 개념, 지능형 리스트, 순환(iterable, iterator), callable 객체, 그리고 일급 클래스 함수의 조건에 해당하는 'run-time에 생성할 수 있는가?(이부분은 새롭게 알게된 부분이며 intepreter language의 run-time 시점을 이야기하는지 공부가 필요할 것 같습니다)'에 대한 내용을 모두 포함하려 했습니다. 하지만 이 글의 주제에 맞지 않는 것 같아 포함시키지 않았지만, 짧더라도 추후에 작성할 예정입니다. 

     

    다시 처음부터 읽어보니 많이 초라하네요;; 첫 게시글이고 내용이 많이 부실하지만, 지속적으로 업데이트를 내용을 채워나가도록 하겠습니다.

    'Python' 카테고리의 다른 글

    Python - MRO(Method Resolution Order)  (1) 2019.09.07

    댓글

Designed by Tistory.