functional programming, JS > python : 기본 func

김장훈·2022년 5월 1일
0

fp-with-python

목록 보기
1/3

과거에 학습만 했던 함수형 프로그래밍:인프런 을 다시금 공부하면서 JS 기반 강의 내용을 python 적용하고 있다.

물론 python 도 좋은 FP 라이브러리가 있으나 해당 패키지들을 모두 샅샅히 찾아본 것은 아니라서 강의에서 들은 내용을 바탕으로 따로 작성해야하는 것들만 변경중.
(현재 사용중인 라이브러리는 toolz)

다만 기존 map, filter 등을 curry 하기 위해선 새로이 만들어서 사용하는게 편하긴 하다

  • curry 는 toolz 에서 제공해준다.
@curry
def custom_map(func: Callable, iterable: iter):
    res = []
    for value in iterable:
        res.append(func(value))
    return res


@curry
def custom_filter(func: Callable, iterable: iter):
    res = []
    for value in iterable:
        if func(value):
            res.append(value)
    return res

lazy map, lazy filter

강의 내용(javascript)

L.map = function *(func, iterable) { 
  for(const e of iterable) 
    yield func(e); 
  };

L.filter = function *(func, iterable) { 
  for(const e of iterable) 
    if(func(e)) 
      yield e; 
  };

python

  • python 에서는 yield, yield from(3.3+) 이 존재한다
  • for ... yield 형태도 괜찮지만 yield from 을 통해서 더 간단하게 표현할 수 있다.
def lazy_filter(func: Callable, iterable: iter):
    # yield from seq is equivalent to for i in seq: yield i
    yield from (value for value in iterable if func(value))

def lazy_map(func: Callable, iterable: iter):
    yield from (func(value) for value in iterable)

reduce

기존의 reduce가 존재하지만(functools) 재정의하였다.

강의내용(javascript)

const reduce = (f, acc, iter) => {
  if(!iter) {
    iter = acc[Symbol.iterator]();
    acc = iter.next().value;
  }

  for(const i of iter) {
    acc = f(acc, i);
  }

  return acc;
}

python

def custom_reduce(func: Callable, acc=None, iterable: iter = []):
    if not iterable:
        iterable = acc[1:]
        acc = acc[0]

    for value in iterable:
        acc = func(acc, value)
    return acc

성능비교

map, filter <> lazy 와의 실제 성능은 어떤 차이가 있을까

base_counts 를 수정해가면서 직접확인할 수 있다(다만 너무 크면 느려진다)

def test_lazy_funcs_should_fast_then_normal():
    base_counts = 1000000
    start = time.time()
    res = go(
        range(base_counts),
        custom_map(lambda x: x + 10),
        custom_filter(lambda x: x % 2 == 0),
        custom_take(2),
    )
    end_time = time.time()
    normal = end_time - start
    assert res

    start = time.time()
    res = go(
        range(base_counts),
        lazy_map(lambda x: x + 10),
        lazy_filter(lambda x: x % 2 == 0),
        custom_take(2),
    )
    assert res

    end = time.time()
    lazy = end - start
    print(f"Normal: {normal} sec, lazy: {lazy} sec")
    assert normal > lazy

결과

$pytest -k test_lazy_funcs_should_fast_then_normal -s
Normal: 0.26944684982299805 sec, lazy: 0.0002028942108154297 sec
  • 일반적인 경우 각 단계(함수) 마다 모든 동작을 다 한 후에 넘어가므로 속도 자체를 보면 확실히 lazy 가 빠르다.
profile
읽기 좋은 code란 무엇인가 고민하는 백엔드 개발자 입니다.

0개의 댓글