이번 글에서는 파이썬의 특별한 문법인 comprehension에 대해서 설명해보고, 사소한 견해를 함께 적고자 합니다.
파이썬의 Comprehension이라는 독특한 문법이 있습니다. 기본적으로 iterator(list, set, dictionary의 keys, values, item 등)의 요소에 순회하거나 값을 할당할 때 많이 사용합니다.
dictionary 자체는 iterator는 아니지만, dictionary 객체의 keys(), values(), items() 등의 메소드는 iterator를 반환합니다.
가장 쉬운 예시를 들어보겠습니다. 리스트 구조로 되어 있는 변수를 순회하며 값을 할당한다고 했을 때, 기본적으로 for문을 사용할 수 있겠습니다. 파이썬에서도 마찬가지에요. 간단하게 작성하자면 다음과 같을 수 있겠습니다.
lst = []
for i in range(10):
lst.append(i)
위를 파이썬의 comprehension의 문법으로 작성한다면, 다음처럼 쓰게 됩니다.
lst = [i for i in range(10)]
참 쉽죠?
파이썬의 comprehension은 코드의 간결성을 극대화합니다. 3줄 짜리 코드를 단 1줄로 쓸 수 있기 때문입니다. 리스트 값 할당 뿐만이 아니라 다음과 같은 경우에도 쓰일 수 있습니다.
# list를 dictionary로 만들 때
lst = [('김O연', '서울', 28), ('이O우', '부산', 30), ('최O주', '광주', 25)]
member_dict = {
'이름': name,
'지역': region,
'나이': age
for name, region, age in lst
}
# dictionary에서 사용 가능한 iterator를 comprehension 문법으로 사용할 때
animals = {'강아지': '포유류', '앵무새': '조류', '개구리': '양서류'}
lst = [
(key, value)
for key, value in animals.items()
]
# 이중 리스트 만들 때
from pprint import pprint
pprint([
[j for j in range(i, i+10)]
for i in range(0, 100, 10)
])
파이썬의 comprehension 문법은 간결성을 높이는 것 뿐만 아니라, 기본적으로 빠릅니다. 특히 list를 만들 때 그 속도 차이가 극명한데요.
다음 예제를 통해 확인을 할 수 있습니다.
from datetime import datetime
COUNT = 10000000
# list appending
st = datetime.now()
lst = []
for i in range(COUNT):
lst.append(i)
print(datetime.now() - st)
# list comprehension
st = datetime.now()
lst = [
i for i in range(COUNT)
]
print(datetime.now() - st)
0:00:01.116661 # list appending
0:00:00.590065 # list comprehension
위와 같이 list appending과 list comprehension의 시간 차이는 COUNT
의 값이 늘어남에 따라 차이가 2배 가량 날 수도 있니다. 그리고 이중 for문, 삼중 for문을 사용하면 그 차이는 더 날 수 있습니다.
이는 파이썬의 list append가 작동하는 방식에서 큰 차이가 생깁니다. 파이썬의 list는 길이가 늘어날 때 동적으로 메모리를 할당하고, 기존의 데이터를 복사하는 등의 비용이 발생하기 때문입니다.
comprehension는 이 부분을 최적화 하였기 때문에 훨씬 빠르게 리스트를 만들 수 있습니다.
하지만 프로젝트를 하다보면 comprehension 문법이 꼭 좋은지는 고민해봐야 합니다. 저는 comprehension 문법을 아주 pythonic하고 효율적이라고 생각하며, 가독성 또한 챙기는 좋은 문법이라고 생각합니다만 파이썬에 익숙하지 않은 분들에겐 다소 생소하고 오히려 가독성이 떨어지는 느낌이 있다고 합니다.
comprehension 쓰는 것으로 고려해봐야 할 때는 다음과 같습니다.
1) 2중, 3중 리스트 등 복잡한 객체를 만들 때, 오히려 가독성이 떨어진다.
2) list comprehension으로 표현하기에는 계산이 복잡할 때, comprehension으로 표현하는 것엔 한계가 있다.
이럴 때는 실제로 데이터의 길이와 수행할 로직의 복잡한 정도를 가늠해야 합니다. 기본적으로 데이터의 길이가 별로 길지 않거나(ex. 사람의 정보를 가져오는데 페이지네이션이 쓰여서 10개씩 정도만 가져오는 것이 확실할 때), 단순히 객체를 만든다거나 할 때는 굳이 comprehension을 쓰지 않아도 성능 자체에 크게 문제가 되지 않을 수 있습니다.
list append vs. list comprehension
예시에서도 보여드렸다시피, 가장 단순한 리스트 생성 로직에서는 천 만 건 정도의 데이터가 있고 나서야 append와 comprehension의 수행 시간 차이가 약 0.6초의 차이가 나기 때문입니다.
실제로 백엔드 업무를 할 때 list comprehension을 자주 씁니다. 데이터를 DB에서 가져와서 response를 만들어줄 때 특히 그렇습니다. 기능의 복잡도와 데이터의 양을 고려하면서 잘 쓰면 분명 좋은 문법입니다. 다만 상황에 맞춰 잘 활용하면 된다고 생각합니다.
백엔드 개발에서 기능 개발함에 있어 기능의 성능을 고려할 땐 '속도'가 아주 큰 부분을 차지하는 것은 맞습니다. 그래서 이전엔 무조건 0.001초라도 시간을 줄여야 해! 라는 강박 아닌 강박(?)이 있어 list comprehension에 목숨(!?) 걸듯이 사용한 적도 있었습니다.
다만, 이게 정말로 가독성이 있냐 없냐는 사람들의 취향의 차이가 있을 수 있기에 절대적으로 옳은 것은 없다 생각했습니다. 특히나 협업을 하는 입장에서는 코드를 가독성 있게 짠다
는 것은 매우 중요하기 때문입니다. 그런 이유에서 연차가 쌓여감에 따라 comprehension 문법은 목숨 걸듯이 사용하기보단 적절하게 사용하기로 맘먹게 됐습니다.