1. 변수 등에 할당 가능
2. 함수 인수 전달 가능
3. 함수 결과로 반환 가능
위의 세가지 조건이 충족된다면 일급함수(First Class Function)이다.
1. 변수 등에 할당 가능
def factorial(n):
if n == 1:
return 1
return n*factorial(n-1)
# 변수 할당
var_func = factorial
print('1 -', var_func)
print('2 -', var_func(5))
결과값
1 - <function factorial at 0x000001E2674F99D0>
2 - 120
위의 코드와 같이 함수를 정의한 후 변수(var_func)에 함수를 할당해서 여러가지 방식으로 사용 할 수 있습니다.
2. 함수 인수 전달 및 함수로 결과 반환(Higher-order Function = 고위함수)
print('1 -', map(var_func, range(1,10)))
print('2 -', list(map(var_func, range(1,10))))
print('3 -', list(map(var_func, filter(lambda x: x%2==0 , range(1,6)))))
결과값
1 - <map object at 0x0000025D61F764C0>
2 - [1, 2, 6, 24, 120, 720, 5040, 40320, 362880]
3 - [2, 24]
코드에서 사용된 map()함수는 두 번째 파라미터에(iterable가능한 객체여야 한다.) 첫 번째 파라미터(함수)를 적용한 결과를 원소로 갖는 iterable 객체를 반환한다.
이와같이 일급 함수가 있으면 함수형 프로그래밍이 가능하다.
그렇다면 함수형 프로그래밍의 특징 중 하나인 고위함수에 대해서 좀 더 알아보자.
고위함수는 함수를 인수로 받거나, 함수를 결과로 반환하는 함수다.
위에서 사용한 map() 함수도 고위함수이다. 그 밖에도 sorted() 함수가 있는데 key 파라미터로 함수를 전달받아 정렬할 각 원소에 적용한다.
아래의 코드를 보자.
fruits = ['apple', 'banana', 'strawberry', 'blueberry', 'melon']
print(sorted(fruits, key=len))
결과값
['apple', 'melon', 'banana', 'blueberry', 'strawberry']
위와 같이 단어의 길이순서로 정렬하기 위해 len()함수를 key파라미터에 전달하면 된다.
Parameter를 1개 받는 함수는 모두 key에 전달할 수 있다.
그 외에도 위해서 한번 사용했던 filter()와 같은 고위함수가 있다.
print(list(map(var_func, filter(lambda x: x%2 , range(1,6)))))
결과값
[1, 6, 120]
map(), filter()는 파이썬3의 빌트인 함수이지만, 지능형 리스트(Comprehending List)와 제너레이터 표현식이 소개된 후에 이 함수들의 중요성은 다소 떨어졌다. map()과 filter()는 제너레이터를 리턴하므로 제너레이터 표현식이 이 함수들을 대체할 수 있기 때문이다.
# Comprehending List 사용
print([var_func(i) for i in range(1,6) if i%2])
결과값
[1, 6, 120]
iterable한 객체의 원소들의 합계를 내릴 때 사용하던 reduce()는 sum() 함수로 대체할 수 있다.
from functools import reduce
from operator import add
print(reduce(add, range(1,11)))
print(sum(range(1,11)))
결과값
55
55
익명함수(lambda)
고위함수를 사용할 때 일회용 함수를 생성하는 게 편리할 때도 있다. lambda키워드는 파이썬 표현식 내에서 익명함수를 제공한다. 그러나 람다 함수의 본체는 순수한 표현식으로만 구성하도록 제한되어있다.
익명함수를 사용할 땐 가급적 주석을 사용하고, 익명함수를 사용하는 것 보단 함수를 사용하는 것들 PEP(Python Enhance Proposal)에서도 권장한다.
(이름이 없는 함수를 많이 사용하면 코드의 가독성이 떨어진다는 인식이 있다.)
print(reduce(lambda x, t: x+t, range(1,11)))
결과값
55
reduce()에 add()함수 대신 익명함수(lambda) 함수를 사용해도 똑같은 결과값이 출력된다.
Collable : 호출 연산자 -> 메소드 형태로 호출 가능한지 확인
import random
# 로또 추첨 클래스 선언
class LottoGame:
def __init__(self):
self._balls = [n for n in range(1,46)]
def pick(self):
random.shuffle(self._balls)
return sorted([random.choice(self._balls) for n in range(6)])
def __call__(self):
return self.pick()
__call__() 메서드를 오버로딩을 통해 구현하면 모든 객체를 콜러블 타입으로 만들 수 있다.
즉, 함수처럼 동작을 할 수 있게 해준다.
game = LottoGame()
# 호출 가능 확인
print(callable(str), callable(list), callable(factorial), callable(54))
print(game.pick())
print(game())
print(callable(game))
결과값
True True True False
[4, 20, 21, 34, 34, 38]
[6, 15, 16, 16, 19, 32]
True
__call__() 메소드가 구현되어 있으므로 LottoGame타입의 객체는 호출가능하며, return 값은 _balls에서 꺼낸 원소가 된다.