python 함수형 프로그래밍!

함수형 프로그래밍

파이썬에서도 함수형 프로그래밍을 통해 간결하고 효율적인 코드 작성이 가능하다.
아래와 같은 특징을 가진다.

  • 모듈성 - 외부 변수, 상태로부터 코드의 독립
  • 간결성
  • 동시성 - 스레드 세이프하게 동시실행 지원
  • 시험성 - 매우 많은 테스트에서도 동일한 결과 반환(멱등성) 지원

제너레이터 - yield, next

제너레이터는 이터레이터 처럼 작동하는 객체yield, next 키워드를 사용해 이터레이터 프로토콜 구현체 정의가 가능하다.

return 으로 값을 반환하고 함수가 종료되고 빠져나가는 일반적인 함수와 다르게
yield, next 키워드를 사용하면 값을 반환하면서 함수가 종료되지 않고 이어서 진행되는 제너레이터(함수) 정의가 가능하다.

def mygenerator():
    print("hello")
    yield 1 # StopIteration 발생
    print("world")
    yield 2 # StopIteration 발생
    print("python")
    yield 'a'

if __name__ == '__main__':
    g = mygenerator()
    print(type(g)) # <class 'generator'>
    print(next(g))  # hello \n 1
    print(next(g))  # world \n 2
    print(next(g))  # python \n a
    print(inspect.isgeneratorfunction(mygenerator))  # True

그리고 제너레이터로부터 generator 타입 반환값을 받는다.
generator 변수를 통해 next 를 호출할 때 마다 함수를 처음부터 실행하지 않고 yield 다음부터 이어 진행한다(체인형태로 스택에 쌓인다).
중단되 지점에 모든 상태(변수, 명령포인터, 연산스택 등)이 모두 보존된다.

inspect.isgeneratorfunction 함수로 제너레이터인지 확인 가능, inspect.getgeneratorstate 함수로 제너레이터의 현 상태(GEN_CREATED, GEN_SUSPENDED, GEN_CLOSED) 확인 가능하다.

아래 코드에서 사용하는 range 클래스 역시 제너레이터 형식으로 구현되어 있어
백만개의 정수를 모두다 만드는 것이 아니라 50000 에 이르렀을 때 종료시킨다.

for value in range(10000000):
    if value == 50000:
        print("Found it")
        break
mygen = (i for i in range(1))
print(mygen)  # <generator object <genexpr> at 0x102714190>

실제 아래와 같은 제너레이터 함수라 할 수 있다.

def generator(my_rage):
    for i in range(my_range):
        yield i

제너레이터 - send

next 메서드를 통해 yield 를 실행하면 반환값이 없지만
send 메서드를 통해 yield 를 실행하면 제너레이터에 값을 전달헤 반환값이 생긴다.

def shorten(string_list):
    length = len(string_list[0])
    for s in string_list:
        length = yield s[:length] 

주의할 점은 send 로 값을 넘기는 시점StopIteration 으로 인해 yield 대기중인 상태라는 것 최조 한번은 값을 넘기지 않도록 send(None) 을 호출하거나 next() 를 호출해서 StopIteration 상태로 만들어두어야 한다.

if __name__ == '__main__':
    my_str_list = ['loremipsum', 'dolorsit', 'ametfoobar', 'pythonic', 'test']
    short_str_list = shorten(my_str_list)
    result = []
    try:
        s = short_str_list.send(None)
        result.append(s)
        str_size = 5
        while True:
            s = short_str_list.send(str_size)
            result.append(s)
            str_size -= 1
    except StopIteration:
        pass

    print(result)  # ['loremipsum', 'dolor', 'amet', 'pyt', 'te']

첫번째 인수는 어그냥 10으로 설정되어 원본이 출력되고 두번째 인수부터 send 로 전달된 str_size 잘려서 반환된다.

List comprehension (한줄처리)

리스트 객체를 for 문을 통해 한줄로 생성 가능하다.

[(표현식) for (항목1) in (리스트) if (조건문)]
# 최좌측의 요소가 배열안으로 들어감
print([i for i in range(0,10)])
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print([i*2 for i in range(0,9) if i%2==0])
# [0, 4, 8, 12, 16]

람다

lambda [arg1, arg2, ...] : 표현식 

위와 같이 정의가능한 람다 표현식은

간단한 함수 정의부터
반복되는 리스트를 생성하는 지루한 코드처리,
람혀표현식을 지원하는 리스트 요소의 필터링, 정렬, 벙합 등을 수행가능한 함수들을 제공한다.

엄밀히 말하면 함수가 아닌 함수형태의 클래스

add = lambda a, b: a+b
add(1,2) # 3

map

filter

sort

any

all

zip

first