
이미지 출처: Pexels · Tobi and Chris · Pexels License
시험 점수 다섯 개에서 80점 넘는 것만 뽑습니다. 시즌 1에서 배운 대로 쓰면 이렇습니다.
scores = [88, 72, 95, 60, 81]
passed = []
for s in scores:
if s >= 80:
passed.append(s)
print(passed)
[88, 95, 81]
같은 일을 한 줄로 쓴 것이 오늘의 주인공입니다.
passed = [s for s in scores if s >= 80]
결과는 똑같이 [88, 95, 81]입니다. 다섯 줄이 한 줄이 됐는데, 줄 수보다 눈여겨볼 것은 읽는 방향입니다. 위 코드는 위에서 아래로 읽고, 아래 코드는 가운데부터 읽습니다. 이 글은 그 한 줄이 어디서 왔는지부터 시작해, 조건이 붙는 자리 둘, 그리고 어디까지 줄여도 되는지까지 갑니다.
리스트 컴프리헨션이 뭔가요 — 벨트 위에서 담는 새 상자
파이썬 공식 자습서는 리스트 컴프리헨션을 리스트를 만드는 간결한 방법이라고 정의합니다. 이미 있는 리스트나 범위를 한 바퀴 돌면서, 각 원소를 바꾸거나 골라서 새 리스트를 하나 만드는 문법입니다. 있는 리스트를 고치는 것이 아니라 새로 만드는 것이라는 점이 먼저입니다.
언제 쓰나. 리스트 하나를 재료로 다른 리스트를 만들 때입니다. 점수에서 합격만, 이름에서 대문자로, 가격에서 세금 붙여서. 세 경우 모두 재료 리스트를 돌면서 하나씩 처리해 새 리스트에 담는 일입니다.
성질은 넷입니다.
| 성질 | 뜻 |
|---|---|
| 새 리스트를 돌려준다 | 재료 리스트는 그대로 두고 결과가 새 리스트로 나온다 |
| 대괄호 안에 다 들어간다 | [표현식 for 변수 in 재료]가 기본 꼴이고 if가 뒤에 붙을 수 있다 |
| 안에서 쓴 변수가 밖에 남지 않는다 | for문은 돌고 나면 s가 남지만, 컴프리헨션은 남기지 않는다 |
| 표현식이 튜플이면 괄호가 필요하다 | [(x, y) for ...]처럼 감싸야 한다 |
이 편에서 쓰는 말은 이렇게 정합니다.
| 용어 | 이 글에서의 뜻 |
|---|---|
| 재료 | for 뒤에 오는, 한 바퀴 돌 리스트나 range |
| 표현식 | 맨 앞에 오는, 새 리스트에 담을 값을 만드는 식 |
| 골라내는 if | 맨 뒤에 붙어 담을지 말지를 정하는 조건 |
| 바꿔 넣는 if else | 맨 앞 표현식 안에 들어가 무엇을 담을지를 고르는 조건 |
비유는 정의 뒤에 붙입니다. 재료 리스트는 벨트 위를 지나가는 과일이고, 컴프리헨션은 벨트 옆에 선 사람 한 명입니다. 지나가는 것을 하나씩 집어 그대로 담거나, 다듬어 담거나, 상자에 안 넣고 보냅니다. 벨트 위의 과일 자체는 손대지 않습니다.

재료는 벨트 위를 그대로 지나가고, 담긴 것만 새 상자가 됩니다.
for 네 줄 시절 — 무엇이 번거로웠나
컴프리헨션이 없다고 치고 제곱수 열 개를 모아 보겠습니다. 공식 자습서에 나오는 그 예제입니다.
squares = []
for x in range(10):
squares.append(x**2)
print(squares)
print(x)📱 코드 실행(실습)은 태블릿·PC에서 이용할 수 있어요. 모바일에서는 위 코드를 눈으로 따라가며 확인하세요.
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
9
결과는 맞게 나옵니다. 번거로운 자리는 셋입니다.
먼저 빈 리스트를 미리 만들어야 합니다. squares = [] 한 줄이 없으면 append할 곳이 없습니다. 다음으로 담는 동작을 따로 적어야 합니다. 하고 싶은 일은 "제곱해서 모으기"인데 코드에는 "제곱한 것을 append 하기"라고 적힙니다. 마지막 줄의 출력이 세 번째입니다. 루프가 끝났는데 x가 9로 밖에 남아 있습니다. 공식 자습서도 이 점을 부작용이라고 적어 두었고, 컴프리헨션은 이 부작용 없이 같은 결과를 낸다고 설명합니다.
세 가지 다 치명적이지는 않습니다. 다만 같은 모양의 네 줄을 하루에 열 번 쓰다 보면, 이 네 줄이 하나의 동작이라는 것을 문법이 알아 줬으면 하게 됩니다.
한 줄이 된 뒤 — 읽는 순서는 가운데부터
같은 일을 컴프리헨션으로 쓰면 이렇습니다.
squares = [x**2 for x in range(10)]
print(squares)📱 코드 실행(실습)은 태블릿·PC에서 이용할 수 있어요. 모바일에서는 위 코드를 눈으로 따라가며 확인하세요.
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
빈 리스트도, append도 없습니다. x도 밖에 남지 않습니다. 여기서 마지막 줄에 print(x)를 붙이면 NameError가 납니다. 컴프리헨션 안에서 쓴 x는 대괄호가 닫히면 사라집니다.
처음 보면 앞뒤가 뒤집힌 것처럼 읽힙니다. 읽는 순서를 따로 익히면 풀립니다. 가운데 for부터, 그다음 맨 앞, 그다음 맨 뒤입니다.

1번 재료, 2번 담을 것, 3번 조건. 왼쪽부터가 아니라 가운데부터입니다.
for문으로 풀면 순서가 그대로 보입니다.
for x in range(10): # 1. 재료
if x % 2 == 0: # 3. 조건
result.append(x**2) # 2. 담을 것
컴프리헨션은 이 세 줄을 담을 것, 재료, 조건 순서로 한 줄에 눕힌 것입니다. 공식 자습서의 표현으로는 "표현식과 그 뒤를 따르는 for 절, 그리고 없거나 여러 개의 for나 if 절"입니다.
저는 이 문법을 처음 배우는 분에게 왼쪽부터 읽지 말라고 먼저 말합니다. 왼쪽부터 읽으면 아직 정해지지도 않은 x를 제곱하라는 말이 되어 막힙니다.
골라내는 if는 뒤에 붙습니다
조건이 붙는 자리가 둘이라 검색창에 "리스트 컴프리헨션 if else"가 자주 올라옵니다. 둘을 갈라 보겠습니다.
담을지 말지를 정하는 조건은 맨 뒤입니다. 처음 코드의 if s >= 80이 이것입니다.
scores = [88, 72, 95, 60, 81]
passed = [s for s in scores if s >= 80]
print(passed)
names = ["kim", "", "lee", "", "park"]
filled = [n for n in names if n != ""]
print(filled)📱 코드 실행(실습)은 태블릿·PC에서 이용할 수 있어요. 모바일에서는 위 코드를 눈으로 따라가며 확인하세요.
[88, 95, 81]
['kim', 'lee', 'park']
조건에 걸리지 않은 원소는 새 리스트에 아예 들어가지 않습니다. 그래서 결과 리스트가 재료보다 짧아집니다. 두 번째 예처럼 빈 문자열을 걸러 내는 데 자주 씁니다.
여기에는 else를 붙일 수 없습니다.
passed = [s for s in scores if s >= 80 else 0]
SyntaxError: invalid syntax
뒤에 붙는 if는 "담을까 말까"만 정하는 자리라 "아니면 이걸 담아라"가 들어갈 곳이 없습니다. else를 쓰고 싶다면 조건이 하는 일이 다른 것이고, 자리도 다릅니다.
바꿔 넣는 if else는 앞에 옵니다
무엇을 담을지를 고르는 조건은 맨 앞 표현식 안에 들어갑니다. 이때는 모든 원소가 담기고, 원소마다 값이 갈립니다.
scores = [88, 72, 95, 60, 81]
result = ["합격" if s >= 80 else "불합격" for s in scores]
print(result)
print(len(scores), len(result))📱 코드 실행(실습)은 태블릿·PC에서 이용할 수 있어요. 모바일에서는 위 코드를 눈으로 따라가며 확인하세요.
['합격', '불합격', '합격', '불합격', '합격']
5 5
결과 리스트의 길이가 재료와 같습니다. 하나도 버리지 않고 값만 바꿨기 때문입니다. "합격" if s >= 80 else "불합격" 부분은 파이썬의 조건 표현식이고, 컴프리헨션 문법이 아니라 그냥 값을 만드는 식입니다. 그래서 표현식 자리인 맨 앞에 옵니다.

뒤의 if는 개수를 줄이고, 앞의 if else는 개수를 지킵니다.
둘을 한 줄에 같이 쓸 수도 있습니다. 앞에서 값을 고르고, 뒤에서 담을지를 정합니다.
result = ["A" if s >= 90 else "B" for s in scores if s >= 80]
['B', 'A', 'B']
80 미만은 뒤의 if에서 빠지고, 남은 셋이 앞의 if else로 A나 B가 됩니다. 되기는 하지만 한 줄에 조건이 둘이면 읽는 사람이 두 번 멈춥니다. 이 정도가 한 줄의 한계에 가깝다고 봅니다.
elif는 없습니다. 조건 표현식에 elif가 없기 때문입니다. 등급 셋을 나누려면 "A" if s >= 90 else "B" if s >= 80 else "C"처럼 겹쳐 쓸 수는 있는데, 이쯤 되면 함수를 하나 만들어 [grade(s) for s in scores]로 쓰는 편이 읽기 쉽습니다.
이중 for는 되지만 한 줄이 두 줄보다 나은지는 따로 봅니다
for를 두 번 쓸 수도 있습니다. 공식 자습서의 예제입니다.
pairs = [(x, y) for x in [1, 2, 3] for y in [3, 1, 4] if x != y]
print(pairs)
combs = []
for x in [1, 2, 3]:
for y in [3, 1, 4]:
if x != y:
combs.append((x, y))
print(combs)📱 코드 실행(실습)은 태블릿·PC에서 이용할 수 있어요. 모바일에서는 위 코드를 눈으로 따라가며 확인하세요.
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
두 결과가 같습니다. 규칙은 하나입니다. for와 if의 순서가 for문으로 풀었을 때와 같아야 합니다. 바깥 for x가 먼저, 안쪽 for y가 다음, 조건이 마지막입니다. 표현식이 (x, y)처럼 튜플이면 괄호로 감싸야 합니다. 괄호를 빼고 [x, y for ...]라고 쓰면 파이썬이 "컴프리헨션 대상에 괄호를 잊었느냐"고 되묻는 SyntaxError가 납니다.
이 편의 함정이 여기 있습니다. 되는 것과 읽히는 것은 다릅니다. 3×4 행렬의 행과 열을 바꾸는 코드를 공식 자습서는 이렇게 씁니다.
matrix = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
]
transposed = [[row[i] for row in matrix] for i in range(4)]
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
맞는 코드이고 한 줄입니다. 그런데 자습서는 바로 뒤에 복잡한 흐름문보다 내장 함수를 쓰라고 적고, 같은 일을 list(zip(*matrix))로 보여 줍니다. 한 줄로 쓸 수 있다는 것이 한 줄로 써야 한다는 뜻은 아닙니다.
제가 쓰는 기준은 이렇습니다. for가 하나이고 if가 하나까지면 컴프리헨션, for가 둘이거나 if가 둘이면 일단 for문으로 풀어 써 보고, 그래도 한 줄이 더 잘 읽힐 때만 다시 줄입니다. 대개 다시 줄이지 않게 됩니다.
남은 한계 — 컴프리헨션이 못 하는 것
한 줄이 만능은 아닙니다. 안 되거나 안 어울리는 자리가 셋 있습니다.
중간에 다른 일을 끼울 수 없습니다. 돌면서 print를 찍거나, 조건에 따라 break로 멈추거나, 두 리스트에 나눠 담는 일은 for문의 몫입니다. 컴프리헨션은 새 리스트 하나를 돌려주는 식이지 문장이 아닙니다.
result = [print(s) for s in scores]
print(result)
88
72
95
60
81
[None, None, None, None, None]
찍히기는 하는데 None 다섯 개짜리 리스트가 같이 만들어집니다. 쓰지도 않을 리스트를 만드는 셈이라, 출력만 하려면 for문이 맞습니다.
결과가 크면 메모리를 한 번에 씁니다. 컴프리헨션은 리스트 전체를 만들어 놓습니다. 백만 개를 만들면 백만 개가 한꺼번에 자리를 차지합니다. 하나씩 꺼내 쓰기만 할 거라면 대괄호를 소괄호로 바꾼 (x**2 for x in range(1000000)) 꼴이 따로 있는데, 그것은 이 시리즈 뒤쪽에서 따로 다루고, 여기서는 이름만 적어 둡니다.
딕셔너리와 집합도 같은 꼴로 만듭니다. 이건 한계가 아니라 덤입니다. 지난 편의 중괄호를 그대로 씁니다.
names = ["kim", "lee", "park"]
lengths = {n: len(n) for n in names}
print(lengths)
initials = {n[0] for n in names}
print(initials)📱 코드 실행(실습)은 태블릿·PC에서 이용할 수 있어요. 모바일에서는 위 코드를 눈으로 따라가며 확인하세요.
{'kim': 3, 'lee': 3, 'park': 4}
{'k', 'l', 'p'}
중괄호 안에 키: 값이면 딕셔너리, 값만 있으면 집합입니다. 3편에서 본 대로 집합은 순서를 보장하지 않으니 출력 순서는 달라질 수 있습니다.
네 줄과 한 줄, 어느 쪽을 쓸까
| 상황 | 쓸 것 | 이유 |
|---|---|---|
| 리스트에서 골라내기 | [x for x in 재료 if 조건] | 가장 흔한 꼴, 한눈에 읽힌다 |
| 리스트 원소마다 바꾸기 | [식 for x in 재료] | append 없이 새 리스트 |
| 원소마다 값을 둘 중 하나로 | [a if 조건 else b for x in 재료] | if else는 앞, 개수는 그대로 |
| 조건이 셋 이상 | 함수 + [f(x) for x in 재료] | elif가 없고 겹친 if else는 안 읽힌다 |
for가 둘 | for문으로 먼저 써 본다 | 한 줄이 더 잘 읽힐 때만 줄인다 |
| 돌면서 출력·중단·두 리스트 | for문 | 컴프리헨션은 새 리스트 하나를 돌려주는 식이다 |
기억할 것은 셋입니다. 가운데 for부터 읽습니다. 골라내는 if는 뒤, 바꿔 넣는 if else는 앞입니다. 한 줄에 for나 if가 둘 이상이면 한 번 풀어 써 봅니다.
한 줄만 바꾸면 결과는 어떻게 될까요
미니 퀴즈
참고 자료
- 파이썬 공식 자습서 5.1.3 "리스트 컴프리헨션" — 정의, 제곱수 예제와 변수가 남는 부작용, 일반 꼴(표현식 + for 절 + for/if 절), 튜플은 괄호 https://docs.python.org/ko/3/tutorial/datastructures.html
- 파이썬 공식 자습서 5.1.4 "중첩된 리스트 컴프리헨션" — 행렬 전치 예제와 "복잡한 흐름문보다 내장 함수를" https://docs.python.org/ko/3/tutorial/datastructures.html
- 파이썬 언어 레퍼런스 6.13 "조건 표현식" —
a if 조건 else bhttps://docs.python.org/ko/3/reference/expressions.html
포켓코딩(PYTHON2) 4편 · 3편(파이썬 튜플과 집합)에서 이어집니다. 예제 코드는 강사 개인 GitHub 저장소의 강의 자료와 파이썬 공식 자습서의 예제를 바탕으로 다시 썼습니다. 다음 편은 함수 심화입니다. 인자를 몇 개든 받는 *args와 **kwargs, 그리고 기본값에 리스트를 쓰면 왜 호출할 때마다 같은 리스트가 따라오는지를 다룹니다.