파이썬을 이용하여 함수형 프로그래밍을 하는 방법을 설명하는 Functional Programming HOWTO 내용 요약입니다.


프로그래밍 언어의 구분

프로그래밍 언어는 절차적 언어(C), 선언적 언어(SQL), 객체지향언어(Java), 함수형 언어(Haskell) 등으로 구분 할 수 있습니다.


함수형 언어로서의 파이썬

파이썬의 내부는 non-functional 로 구현 되어 있지만, functional-appearing 인터페이스를 제공 합니다.


iterators

- 파이썬의 for 문은 index 를 이용하지 않고, __iter__ 함수를 이용 합니다.

for i in [1,2,3]       # iterable container
for i in iter([1,2,3]) # iterator

iterator 에 관련 된 함수는 아래와 같습니다.

- next() : 다음 요소 반환

- list(), tuple(), dict() 로 iterator 를 materialize 가능

- max(), min(), in, not in : 최대, 최소값, 포함여부 찾기 함수

- dictionary 의 key, value, key-value pair 출력

dic = {1:'a', 2:'b', 3:'c'}
for k in iter(dic): print(k)
1
2
3
for v in dic.values(): print(v)
a
b
c
for (k, v) in dic.items(): print(k, v)
1 a
2 b
3 c

- open('pys/fio_out.txt').readlines() 함수도 list 를 반환 하므로 iterable 합니다.

Generator expression VS List comprehensions

- () 로 묶으면 generator 를 반환하고, [] 로 묶으면 list 반환합니다.

- 하나씩 순환하며 연산을 수행 해야 할 경우 generator 를, 한 번에 materailize 할 경우 list comprehension 을 사용합니다.

>>> (str.strip() for str in ['a ', ' b', ' c '])
<generator object <genexpr> at 0x01157788>
>>> [str.strip() for str in ['a ', ' b', ' c ']]
['a', 'b', 'c']

- (expression for expr in sequence1 if condition1 for expr2 in sequence2 if condition2) 는 중첩 for 문과 같습니다.

Generators

- yield 키워드 : 함수에서 return 은 결과 값을 반환하고, yield 는 결과에 대한 iterator 를 반환합니다.

- Python 설치 폴더의 하위 디렉토리 Lib/test/test_generators.py 에는 in-order 탐색, N-Queens 문제, Knight's Tour 문제 등 재미있는 코드가 많습니다. 한 번 찾아 봅시다.

- yield 키워드는 일반적으로 반복문 내에서 사용 됩니다. yield 로 반환된 iterator 를 이용하면 yield 반환 지점부터 해당 반복문을 재실행 할 수 있습니다. 파이썬 2.5 부터는 yield 로 iterator 를 반환 할 뿐 아니라, send() 함수를 이용하여 yield 호출 지점으로 값을 설정할 수도 있습니다.

>>> def counter(maximum):
     i=0
     while i<maximum:
          print('yield직전 : i = ' + str(i))
          val=(yield i)
          print('yield직후 : i = ' + str(i))
          if val is not None:
               i = val
          else:
               i += 1
               
>>> it = counter(10)
>>> next(it)
yield직전 : i = 0
0
>>> next(it) # send() 가 없으므로 val 에는 값이 할당되지 않습니다.
yield직후 : i = 0
yield직전 : i = 1
1
>>> it.send(8) # send() 를 호출 했으므로 val 에 값이 할당됩니다.
yield직후 : i = 1
yield직전 : i = 8
8
>>> next(it)
yield직후 : i = 8
yield직전 : i = 9
9
>>> next(it)
yield직후 : i = 9
Traceback (most recent call last):
  File "<pyshell#151>", line 1, in <module>
    next(it)
StopIteration

Built-in functions

- map() : 주어진 리스트에 함수를 매핑 합니다.

- filter() : 조건에 맞는 결과만을 필터링 합니다.

- enumerate() : index 를 붙인 tuple 을 반환 합니다.

- sorted(), any(), all() : 설명이 필요 없지요^^?

- zip(['a', 'b', 'c'], (1, 2, 3)) => ('a', 1), ('b', 2), ('c', 3)

itertools

itertools 는 iterator 와 관련된 다양한 함수를 제공하는 모듈입니다. iterator 를 생성 할 일이 있다면 파이썬 문서를 참고하여 소중한 시간을 아끼도록 합시다.

functools

함수를 매개변수로 받거나, 함수를 반환하는 함수를 고계함수(higher-order function)라 합니다. 파이썬 2.5 부터는 고계함수 구현을 돕는 functools 모듈이 추가 되었습니다. functools 모듈에는 reduce(), accumulate() 같은 함수도 있지만, 본 글의 저자는 이러한 함수들 보다는 for 문을 이용하는 것이 명확하다고 말합니다.

- partial() : 기존 함수를 변경하여, 일부 매개변수를 미리 채워 넣습니다.

operator module

고계함수에 함수를 매개변수로 전달하고자 할 때, 단순한 연산을 새로운 함수로 구현하기는 번거로운 면이 있습니다. 이러한 불편을 덜어 주기 위해, operator 모듈은 아래 함수를 제공 합니다.

Math operations: add(), sub(), mul(), floordiv(), abs(), ...

Logical operations: not_(), truth().

Bitwise operations: and_(), or_(), invert().

Comparisons: eq(), ne(), lt(), le(), gt(), and ge().

Object identity: is_(), is_not().


예컨대 두 리스트를 곱하고 싶다면 아래와 같이 operator 모듈의 mul 함수를 이용 할 수 있습니다.

from operator import mul
list(map(mul, [1,3,5], [2,4,8]))

Lambda

- 진~짜진짜 간단한 함수에만 사용 합시다. 람다를 쓰면 오히려 가독성이 떨어지는 경우가 많습니다. 물론, 의도적으로 MapReduce 모델을 사용하고자 하는 경우는 얘기가 조금 달라지겠죠..?

import functools
items = [['a', 1], ['b', 2], ['c', 3]]

# 뭐라고?
functools.reduce(lambda a,b: (0, a[1] + b[1]), items)[1]

# 조금 낫다
def combine(a, b):
     return 0, a[1] + b[1]
functools.reduce(combine, items)[1]

# 훨씬 낫다
total = 0
for a, b in items:
     total += b

# 우왕ㅋ굳
sum(b for a, b in items)


참고

Lazy evaluation

요약한 문서에서 소개하는 References