Python에서 순회와 찾기를 추상화하기

구경회·2022년 2월 13일
3
post-thumbnail

다른 현대적인 함수형 언어에 비해 파이썬은 문법이 아름답지 못하다. 가령 다음과 같은 경우를 생각해보자.

xs = 1...1000
y = xs.filter { |x| x % 3 == 0 }.first || -1

이 문법을 파이썬으로 표현하면 어떻게 될까?

xs = range(1, 1000)
y = -1
for x in xs:
	if x % 3 == 0:
    	y = x

위와 같이 좀 더 길고, 값보다는 루프에 초점이 맞추어진 코드가 탄생하고 만다. 그러면, 이걸 어떻게 개선할 수 있을까?

next(x for x in xs if x % 3 == 0)
next((x for x in xs if x % 3 == 0), -1)

builtinsnext를 사용하면 위와 같이 쓸 수 있다. 하지만 default 값이 있는 경우 괄호를 한번 더 써줘야하는게 마음에 들지 않는다. 이것을 다음과 같이 클래스로 한 번 감싸주면 어떨까?

class First:
    def __init__(self, iterator):
        self._iter = iterator
        self._default = None

    def run(self):
        return next(self._iter, self._default)

    def or_else(self, default):
        self._default = default
        return self

위와 같이 first 라는 래퍼 클래스를 만들면 다음과 같이 쓸 수 있다.

First(x for x in xs if x % 3 == 0).run()
First(x for x in xs if x % 3 == 0).or_else(-1).run()

이제 우리가 원하는 만큼의 게으름과 예쁜 문법을 얻게 되었다. 하지만 약간의 불만이 남는다. 파이썬의 컴프리헨션은 아무리 봐도 눈에 잘 들어오지 않는다. 순회의 대상과 술어를 분리하는게 읽는데 도움이 된다. 바꾸어보자.

class FirstV2:
    def __init__(self, iterable):
        self._iterable = iterable
        self._default = None

    def filter(self, predicate):
        self._iterable = filter(predicate, self._iterable)
        return self

    def exclude(self, predicate):
        self._iterable = filterfalse(predicate, self._iterable)
        return self

    def run(self):
        return next(self._iterable, self._default)

    def or_else(self, default):
        self._default = default
        return self

이제 아래와 같이 사용하면 된다.

FirstV2(range(100, 10000))
	.filter(lambda x: x % 3 == 0)
    .filter(lambda x: x % 2 == 0)
    .exclude(lambda x: x % 12 == 0)
    .run()
profile
즐기는 거야

0개의 댓글