세 줄이 한 줄로
새 기능이 아니라 짧게 쓰는 방법입니다.
리스트를 만들어 값을 추가하는 코드는 대개 같은 구조입니다.
nums = [1, 2, 3, 4, 5]
squares = [] # 빈 리스트를 먼저 만들고
for n in nums: # 하나씩 순회하면서
squares.append(n * n) # 추가한다
print(squares) # [1, 4, 9, 16, 25]빈 리스트를 만들고, 순회하면서, 값을 추가합니다. 이 구조가 워낙 자주 나오므로 파이썬에는 이를 한 줄로 쓰는 문법이 따로 있습니다.
nums = [1, 2, 3, 4, 5]
squares = [n * n for n in nums] # 반복문으로 쓴 세 줄과 「같은 리스트」를 만든다
print(squares) # [1, 4, 9, 16, 25]리스트 내포(list comprehension)
순회하면서 만든 값으로 리스트를 바로 만드는 문법입니다. 대괄호 안에 리스트에 넣을 값을 먼저 쓰고 for를 뒤에 붙입니다. 이렇게 만든 리스트는 빈 리스트를 만들고 순회하며 요소를 추가하는 세 줄의 결과와 같습니다.
빈 리스트를 만들지 않습니다
squares = []가 없어졌습니다. 내포는 리스트를 한 번에 완성해 변수에 대입합니다. 그래서 빈 리스트를 미리 만드는 줄을 빠뜨릴 일이 없습니다.
스스로 확인
[n * n for n in nums]를 실행한 뒤 n을 출력하면 어떻게 될까요? 반복문으로 쓴 코드에서 같은 일을 하면 결과가 어떻게 다를까요?
세 부분으로 나누어 보기
쓰는 순서와 실행 순서가 다릅니다.
내포 한 줄은 세 부분으로 이루어져 있습니다.
| 부분 | 쓰는 위치 | 하는 일 |
|---|---|---|
| 넣을 값 | 대괄호 맨 앞 | 리스트에 들어갈 값을 만든다 |
for |
가운데 | 무엇을 순회할지 정한다 |
if |
맨 뒤 (없어도 된다) | 조건이 참인 값만 남긴다 |
맨 앞에 쓰지만 맨 나중에 실행됩니다
코드는 넣을 값 → for → if 순서로 쓰지만, 실제로는 for로 순회하고 → if로 조건을 검사하고 → 리스트에 넣습니다. 쓰는 순서를 실행 순서로 읽으면 헷갈립니다. 「무엇을 만들지 먼저 밝히고, 어디서 가져올지는 뒤에 쓴다」고 읽으면 됩니다.
스스로 확인
[n if n % 2 == 0 for n in nums]처럼 값을 골라내는 if를 for보다 앞에 써서 실행하면 어떻게 될까요? 이 조건을 for 뒤에 두어야 하는 이유를 실행 순서로 설명해 보세요.
조건 붙이기
값을 골라내는 일까지 한 줄에 들어갑니다.
for 뒤에 if를 붙이면 조건이 참인 값만 리스트에 넣습니다.
nums = [1, 2, 3, 4, 5, 6]
evens = [n for n in nums if n % 2 == 0] # 조건이 참인 값만 넣는다
print(evens) # [2, 4, 6]
# 같은 일을 반복문으로 쓰면
evens2 = []
for n in nums:
if n % 2 == 0:
evens2.append(n)
print(evens2) # [2, 4, 6]두 코드는 같은 일을 합니다. 반복문으로 쓴 쪽은 네 줄, 내포로 쓴 쪽은 한 줄입니다.
if는 리스트에 넣기 전에 검사합니다
조건이 거짓인 값은 아예 리스트에 들어가지 않습니다. 그래서 결과 리스트의 길이가 원래 리스트보다 짧아질 수 있습니다. 순회하는 횟수와 리스트에 들어가는 요소의 개수가 다를 수 있다는 점을 기억해야 합니다.
문자열을 정리하거나 변환할 때도 자주 씁니다.
line = "학생 A,90,65"
values = line.split(",")
scores = [int(v) for v in values[1:]] # 이름 뒤의 값이 점수다
print(scores) # [90, 65]
names = [" 사과 ", "포도 ", " 바나나"]
clean = [name.strip() for name in names]
print(clean) # ['사과', '포도', '바나나'][int(v) for v in values[1:]]은 「이름 뒤의 값을 하나씩 정수로 변환해 리스트로 만들어라」라는 뜻입니다. 텍스트 파일에서 읽은 값은 언제나 문자열이므로 이런 코드가 자주 나옵니다.
대괄호 대신 중괄호를 쓰면 딕셔너리나 집합이 만들어집니다.
names = ["학생 A", "학생 B", "학생 C"]
scores = [90, 85, 72]
table = {name: score for name, score in zip(names, scores)}
print(table) # {'학생 A': 90, '학생 B': 85, '학생 C': 72}
votes = ["사과", "포도", "사과", "바나나"]
kinds = {v for v in votes} # 중괄호를 쓰면 「집합」이 된다
print(len(kinds)) # 3{키: 값 for …}이면 딕셔너리, {값 for …}이면 집합입니다. 콜론이 있는지가 둘을 구분합니다.
스스로 확인
[n for n in nums if n % 2 == 0]과 [n % 2 == 0 for n in nums]는 각각 어떤 값으로 리스트를 만들까요?
어디까지 한 줄로 쓸까
짧게 쓸 수 있다는 것과 그래야 한다는 것은 다릅니다.
내포는 줄 수를 줄여 주지만, 코드를 늘 읽기 쉽게 만들어 주지는 않습니다.
rows = [["학생 A", 90, 65], ["학생 B", 85, 80], ["학생 C", 40, 55]]
# 읽기 어려운 쪽 — 한 줄에 너무 많은 일을 넣었다
bad = [[r[0], round(sum(r[1:]) / 2, 1)] for r in rows if sum(r[1:]) / 2 >= 60]
# 읽기 쉬운 쪽 — 계산 결과를 변수에 저장하고 반복문으로 풀었다
good = []
for row in rows:
name = row[0]
avg = round(sum(row[1:]) / 2, 1)
if avg >= 60:
good.append([name, avg])
print(bad) # [['학생 A', 77.5], ['학생 B', 82.5]]
print(good) # [['학생 A', 77.5], ['학생 B', 82.5]]내포로 쓴 한 줄은 조건을 통과한 학생마다 평균을 두 번 계산합니다. 조건을 검사할 때 한 번, 리스트에 넣을 값을 만들 때 한 번입니다. 반복문으로 쓴 쪽은 평균을 avg라는 변수에 저장해 한 번만 계산합니다.
기준은 「한 줄에 들어간 일의 수」입니다
값을 만드는 일 하나에 조건 하나까지는 한눈에 읽힙니다. 여기에 더해 계산 결과를 변수에 저장해야 하거나 중간 결과를 다시 써야 한다면 반복문으로 풀어 쓰는 편이 낫습니다. 글자 수가 아니라 일의 수가 기준입니다.
코드를 읽는 사람에는 나 자신도 포함됩니다
코드는 쓰는 시간보다 읽는 시간이 깁니다. 한 줄로 줄여 놓고 일주일 뒤에 스스로 읽지 못한다면 줄인 것이 오히려 손해입니다.
파일을 읽어 평균을 구하는 프로그램에 내포를 쓰면 다음과 같습니다.
with open("성적.csv", "r", encoding="utf-8") as f:
rows = [line.strip().split(",") for line in f]
for row in rows:
name = row[0]
scores = [int(v) for v in row[1:]]
print(f"{name}: 평균 {round(sum(scores) / len(scores), 1)}")
# 학생 A: 평균 77.5
# 학생 B: 평균 82.5줄을 쉼표를 기준으로 나누는 일과 점수를 정수로 변환하는 일은 각각 일이 하나뿐이라 내포 한 줄이 알맞습니다. 학생마다 이름과 점수를 변수에 저장해 평균을 출력하는 일은 반복문으로 풀었습니다. 한 프로그램 안에서도 일마다 알맞은 방법을 따로 고릅니다.
스스로 확인
내포로 쓴 한 줄에서 평균이 두 번 계산되는 것을 어떻게 확인할 수 있을까요? 평균을 구하는 함수가 호출될 때마다 화면에 한 줄을 출력한다면 모두 몇 줄이 출력될까요?
핵심 정리
새 기능이 아니라 쓰는 방법이다
[n * n for n in nums]는 빈 리스트를 만들고 순회하며 요소를 추가하는 세 줄과 같은 리스트를 만든다.
세 부분으로 이루어진다
넣을 값 · for · if. if는 없어도 되고, 있으면 리스트에 넣기 전에 조건을 검사한다.
쓰는 순서와 실행 순서가 다르다
맨 앞에 쓴 넣을 값이 실제로는 맨 나중에 계산된다. 순회하고 → 조건을 검사하고 → 리스트에 넣는다.
중괄호를 쓰면 다른 것이 나온다
{k: v for …}는 딕셔너리, {v for …}는 집합이다. 콜론이 있는지가 둘을 구분한다.
기준은 글자 수가 아니라 일의 수다
값을 만드는 일 하나에 조건 하나까지는 한눈에 읽힌다. 계산 결과를 변수에 저장해야 하면 반복문으로 풀어 쓰는 편이 낫다.
확인 퀴즈
배운 내용을 확인해 봅시다.
1. [n * 2 for n in [1, 2, 3]]의 결과는?
2. [n for n in nums if n > 0]에서 if가 하는 일은?
3. 다음 중 내포로 쓰지 않는 편이 나은 경우는?