-
함수형 언어인 파이썬의 특징에 대해 '전문가를 위한 파이썬'을 바탕으로 정리하였습니다.
First-class function
파이썬은 일급 클래스(또는 객체) 함수(First class function)입니다. 일급 클래스 함수의 조건은 다음과 같습니다.
- 함수를 매개 변수(argument or parameter)로 사용할 수 있는가?
- 함수를 변수(variable)로 사용할 수 있는가??
- 함수를 반환(return)할 수 있는가???
- 런타임(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