정적 타입 언어만 다뤄오다가 Python 공부를 시작하면서 코드는 짧아지지만 추상적인 표현들에 대해 거부감이 있었다. 그러다가 감탄과 함께 이런게 파이썬의 맛인가? 느낀 부분이 바로 이중 for loop를 다루는 방법이었다.
코드를 짧게 쓰는게 무조건 좋지는 않은데, 가독성이 떨어지기 때문이다. 짧아지는 과정에서 읽는 사람의 해석이 필요한 추상화 과정이 들어가고 쉽게 읽히지 않는 짧은 코드는 하나하나 풀어쓴 읽기 쉬운 긴 코드만 못하다고 생각한다.
그.러.나. 자주 사용하는 패턴의 코드중 하나가 이중 for loop인데, 두줄의 for loop와 추가되는 들여쓰기(indent)는 오히려 가독성을 떨어트리는게 아닐까란 생각이 들었다. 그리고 파이썬이라면 이거 한줄로 쓰는게 있지 않을까하고 찾아봤지. 당연히 있었고, 이런게 코딩의 아름다움인거 같다.
zip의 이용 : 두개의 iterator 동시에 진행시키기
먼저, zip이 어떻게 동작하는지 알아보자. 공식 문서를 우선 참조. 일단, 내장함수로 iterable 객체들을 인자로 받아 같은 인덱스의 아이템들로 tuple을 만들어 그 iterator를 반환한다. 말로 쓰니까 좀 장황한데, 다음 코드로 이해하면 될거 같다.
a = [1, 2, 3]
b = [4, 5, 6]
c = [7, 8, 9, 10]
zipped = zip(a, b, c)
print(list(zipped))
[(1, 4, 7), (2, 5, 8), (3, 6, 9)]
인자로 넘겨준 리스트들을 인덱스를 따라 tuple로 묶어 iterator가 반환된걸 볼 수 있다. 넘겨진 iterable들의 길이가 다른경우, 가장 짧은 것이 기준이된다.
zip은 병렬진행이라 생각할 수 있어 중첩된 for loop보다는 길이가 같은 별개의 for loop에 적합해 보인다.
a = [1000, 500, 700, 600]
b = [500, 300, 200, 500]
for i, k in zip(a, b):
print(f"i = {i}")
print(f"k = {k}")
itertools.product 의 이용
itertools는 찾아보면 알겠지만 활용도가 매우 높아 보인다. 여기서는 그중에서 itertools.product()만 살펴보려고 한다. 이름에서 눈치챘을지 모르겠지만, 두 벡터의 product 연산을한 결과를 만들어준다. 반환값은 zip()과 같이 iterator이다. zip과 다른점은 내장함수가 아니라서 itertools를 import 해줘야한다. 다음 예제를 보자.
import itertools
a = [1, 2, 3]
b = [4, 5, 6]
ab_product = itertools.product(a, b)
print(list(ab_product))
[(1, 4), (1, 5), (1, 6), (2, 4), (2, 5), (2, 6), (3, 4), (3, 5), (3, 6)]
결과를 보면, 두 리스트의 가능한 모든 조합의 튜플인 것을 알 수 있다.
그리드나 테이블에 값을 채워넣을 때 사용하면 좋아보인다. 여기서는 QT를 공부하며, grid layout의 각 영역에 동일하게 버튼을 채워넣는 코드를 만들어 보았다.
self.grid = QGridLayout()
for i, j in itertools.product(range(3), range(3)):
btn = QPushButton(f"Button{i}{j}")
btn.setProperty("row", i)
btn.setProperty("column", j)
btn.clicked.connect(self.clickMe)
self.grid.addWidget(btn, i, j)
뽀너스 : List Comprehensions
뽀너스로 list comprehension에 대해 살짝 알아보고 끝내자. list comprehension이란 간편하게 리스트를 생성하는 방법이다. lambda 표현식과 유사해 보일 수 있다. 공식 문서를 참고하여 예를들면,
>>> squares = []
>>> for x in range(10):
... squares.append(x**2)
...
>>> squares
위와 같은 코드를 다음처럼 한줄로 표현 가능하다.
squares = [x**2 for x in range(10)]
이중 for loop를 얘기하며, list comprehension을 언급한건 바로 다음처럼 사용가능해서 그렇다.
>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
굉장하지? 사람들이 python이 쉬운언어라고, 초보자에게 많이들 권하지만 내생각은 좀 다르다. 이거 상급사용자 용이야. 내 생각에 초보자용은 이렇게 다양하고 다이나믹한 솔루션들이 가능하면 안된다. 설치부터 사용방법이 가능한 단일화 되어 있고, 제약이 높아서 접근방법이 단일해야 쉬운언어지. 뭐, 이런 방법을 안쓰면 되겠지만 ㅋㅋ 암튼, 숙련도가 높아지면 엄청 강력한 무기가 될 것은 확실하다.