6.2절에서 for 문으로 리스트와 레인지를 순회해 보았다. 이 절에서는 for 문으로 더 다양한 컬렉션을 순회해보고, 중첩된 컬렉션을 순회하는 방법도 알아보자.

7.2.1 시퀀스 순회하기

6.2절에서 배운 것처럼 컬렉션을 순회할 때는 for 문을 사용하는 것이 편리하다.

코드 7-9 리스트 순회하기

for element in ['a', 'b', 'c', 'd']:
    print(element)

for 문은 리스트 뿐 아니라 시퀀스를 대상으로 실행할 수 있다.

코드 7-10 여러 가지 시퀀스 순회하기

for element in ('월', '화', '수'):  # 튜플 순회하기
    print(element)

for character in '가나다라':        # 문자열 순회하기
    print(character)

for n in range(10):                 # 레인지 순회하기
    print(n)

튜플을 순회하는 방법은 리스트를 순회하는 것과 거의 동일하다. 문자열은 각 문자를 요소로 하는 시퀀스이므로 순회가 가능하다. 레인지를 순회하는 것은 6장에서 이미 살펴본 바 있다. 시퀀스는 모두 순서가 있는 컬렉션이므로, 일정한 순서대로 요소를 순회할 수 있다.

7.2.2 집합과 매핑 순회하기

집합과 매핑은 순서를 기준으로 요소를 가리키지 않는다. 그래서 언뜻 생각하기에는 이들을 순회할 수 없을 것 같다. 하지만 요소를 가리키는 기준이 순서가 아닐 뿐, for 문을 이용해 순회하는 것이 가능하다.

집합 순회하기

집합을 순회하며 각 원소를 출력해 보자.

코드 7-11 집합 순회하기

for element in {'사자', '박쥐', '늑대', '곰'}:
    print(element)

실행 결과:

늑대
박쥐
사자
곰

집합도 순회가 가능하다. 단, 집합에는 원소의 순서가 없으므로 element 변수에 어떤 순서로 원소가 대입될지 알 수 없다. 집합을 순회할 때는 순서를 보장할 수 없다는 점을 염두에 두어야 한다.

매핑 순회하기

매핑을 순회할 때는 키를 순회할 것인지, 값을 순회할 것인지, 키-값 쌍을 함께 순회할 것인지를 고려해야 한다. for 문에 매핑을 그냥 입력하면 키를 순회한다. 음료 가격을 나타내는 사전을 여러 가지 기준으로 순회해보자.

코드 7-12 사전의 키를 순회하기

drinks = {
    '아메리카노': 2500,
    '카페 라테': 3000,
    '딸기 주스': 3500,
}

for k in drinks:
    print(k)          # ❶ 변수에는 사전의 키들이 대입된다
    print(drinks[k])  # ❷ 키를 이용해 값을 구할 수도 있다.

실행 결과:

아메리카노
2500
카페 라테
3000
딸기 주스
3500

파이썬 3.7 이상 버전에서는 사전의 각 요소의 순서가 요소가 사전에 입력된 순으로 유지된다. 그래서 출력 결과를 보면, ‘아메리카노’, ‘카페 라테’, ‘딸기 주스’ 순으로 요소를 순회했다. (파이썬 3.5 이하 버전에서는 사전에 입력된 요소의 순서가 고정되지 않으니 주의해야 한다. 파이썬 3.6 버전에서는 인터프리터마다 구현이 다르다.)

for 문에 사전을 넘기면 ❶에서 확인할 수 있듯이 사전의 각 키를 순회한다. 키가 있으면 값을 구할 수 있으므로, ❷와 같이 값을 구해 사용할 수도 있다. 이렇게 하는 것이 불편하다면, 사전의 키-값 쌍의 시퀀스를 구하는items() 메서드를 활용해 키와 값을 나란히 순회할 수도 있다.

코드 7-13 사전의 키와 값을 나란히 순회하기

for k, v in drinks.items():
    print(k)  # ❶
    print(v)  # ❷

이 코드는 사전 drinks를 직접 순회하는 대신 drinks.items()를 순회한다. 화면에 출력되는 결과는 코드 7-12과 동일하다. drinks.items()는 키-값 쌍(('아메리카노', 2500)과 같이 요소 두 개를 가진 튜플)을 요소로 갖는 시퀀스이며, for 문의 각 반복 주기에서는 변수에 이 키와 값의 쌍이 대입된다. 키와 값의 쌍은 하나의 변수에 튜플로 대입할 수도 있지만, 예제에서는 언패킹(5.5절)을 이용해 k, v 두 요소에 나누어 대입하였다. 이 변수들은 ❶, ❷와 같이 for 문의 본문에서 이용할 수 있다.

개념 정리

  • 시퀀스, 집합, 매핑 모두 for 문으로 순회할 수 있다.
  • 매핑을 순회할 때는 키, 값, 키-값 쌍 중 무엇을 순회할지 고려해야 한다.

연습문제

연습문제 7-4 국가별 행복도 출력하기

다음은 몇몇 국가의 행복도를 나타낸 사전이다.

happiness = {
    '호주': 7.95,
    '노르웨이': 7.9,
    '미국': 7.85,
    '일본': 6.2,
    '한국': 5.75,
}

이 사전을 순회하며 다음과 같은 텍스트를 화면에 출력해 보아라.

호주 사람들은 7.95 만큼 행복합니다.
노르웨이 사람들은 7.9 만큼 행복합니다.
미국 사람들은 7.85 만큼 행복합니다.
일본 사람들은 6.2 만큼 행복합니다.
한국 사람들은 5.75 만큼 행복합니다.

7.2.3 중첩 컬렉션 순회하기

7.1절에서 리스트와 사전을 중첩해 보았다. for 문을 중첩하여 실행하면 중첩된 컬렉션을 순회할 수 있다. 중첩 리스트로 작성된 체스판을 순회하며, 화면에 체스판을 출력해 보자.

코드 7-14 체스판을 나타내는 이차원 리스트

board = [
    [['black', '룩'], None, None, None, None, None, None, None],
    [None, None, None, ['black', '킹'], None, None, None, None],
    [None, None, None, None, None, None, None, None],
    [None, None, None, None, None, None, None, None],
    [None, None, ['white', '비숍'], None, None, None, None, None],
    [None, None, None, None, None, None, None, None],
    [None, None, None, None, None, None, None, None],
    [None, None, None, None, ['white', '킹'], None, None, None],
]

첫번째 단계로, 바깥쪽 리스트를 순회하며 요소(체스판의 각 행)를 출력해 보자.

코드 7-15 체스판의 각 행 출력하기

for row in board:  # 체스판(바깥쪽 리스트)의 각 행(요소)을 순회한다
    print(row)     # 각 행을 화면에 출력한다

출력 결과:

[['black', '룩'], None, None, None, None, None, None, None]
[None, None, None, ['black', '킹'], None, None, None, None]
[None, None, None, None, None, None, None, None]
[None, None, None, None, None, None, None, None]
[None, None, ['white', '비숍'], None, None, None, None, None]
[None, None, None, None, None, None, None, None]
[None, None, None, None, None, None, None, None]
[None, None, None, None, ['white', '킹'], None, None, None]

보다시피 안쪽 리스트가 리스트의 형태 그대로 출력되어 아직 체스판처럼 보이지 않는다. for 문의 각 반복 주기에서 for 문을 중첩해 안쪽 리스트를 순회하도록 해 보자.

코드 7-16 체스판 전체 순회하며 출력하기

for row in board:                 # ❶ 체스판(바깥쪽 리스트)의 각 행(요소)을 순회한다
    for piece in row:             # ❷ 행(안쪽 리스트)의 각 체스말(요소)을 순회한다
        if piece:                 # ❸ 체스말이 있으면 I를, 없으면 .을 출력한다
            print('I', end=' ')
        else:
            print('.', end=' ')
    print()                       # ❹ 바깥쪽 리스트를 순회할 때마다 행을 바꾼다

❶ 바깥쪽 for 문의 본문은 두 개의 명령을 수행한다. ❷ 안쪽 for 문과 ❹ 체스판의 각 행마다 개행하는 print() 함수다. ❷ 안쪽 for 문은 다시 각각의 행(row)을 순회한다. 행의 요소(체스말)은 순서대로 piece 변수에 대입되고, 안쪽 for 문의 본문에서는 ❸ 체스말이 체스말의 정보를 나타내는 리스트인지, 아니면 (체스말이 없음을 나타내는) None인지 검사한 후 그에 맞는 텍스트를 출력한다. 이 코드의 실행 결과는 다음과 같다.

실행 결과:

I . . . . . . . 
. . . I . . . . 
. . . . . . . . 
. . . . . . . . 
. . I . . . . . 
. . . . . . . . 
. . . . . . . . 
. . . . I . . . 

화려하지는 않지만, 체스판 데이터를 깔끔하게 묘사했다. 이 프로그램을 조금 더 개선하면 체스말의 종류나 팀에 따라 출력되는 문자를 다르게 할 수도 있을 것이다. 그건 여러분의 몫으로 남겨둔다. 이처럼 여러 번 중첩된 컬렉션이라도 for 문을 중첩하면 전체 요소를 빠짐없이 순회할 수 있다.

개념 정리

  • for 문을 중첩하여 중첩된 컬렉션을 빠짐 없이 순회할 수 있다.

연습문제

연습문제 7-5 육십갑자표

과거에는 연도를 나타내기 위해 육십갑자를 사용했다. 육십갑자는 10개의 천간(십간)과 16개의 지지(십이지)를 교차하여 나열한 것이다. 다음과 같이 천간 리스트와 지지 리스트를 정의하라.

천간 = ['갑', '을', '병', '정', '무', '기', '경', '신', '임', '계']
지지 = ['자', '축', '인', '묘', '진', '사', '오', '미', '신', '유', '술', '해']

그리고 이들을 교차 출력하여 육십갑자표를 출력하라.

출력 결과는 다음과 같아야 한다.

갑자 갑축 갑인 갑묘 갑진 갑사 갑오 갑미 갑신 갑유 갑술 갑해 
을자 을축 을인 을묘 을진 을사 을오 을미 을신 을유 을술 을해 
병자 병축 병인 병묘 병진 병사 병오 병미 병신 병유 병술 병해 
정자 정축 정인 정묘 정진 정사 정오 정미 정신 정유 정술 정해 
무자 무축 무인 무묘 무진 무사 무오 무미 무신 무유 무술 무해 
기자 기축 기인 기묘 기진 기사 기오 기미 기신 기유 기술 기해 
경자 경축 경인 경묘 경진 경사 경오 경미 경신 경유 경술 경해 
신자 신축 신인 신묘 신진 신사 신오 신미 신신 신유 신술 신해 
임자 임축 임인 임묘 임진 임사 임오 임미 임신 임유 임술 임해 
계자 계축 계인 계묘 계진 계사 계오 계미 계신 계유 계술 계해 

7.2.4 여러 개의 컬렉션을 나란히 순회하기

서로 독립적인 여러 개의 컬렉션을 나란히 순회해야 할 때가 있다.

코드 7-17 서로 독립된 여러 개의 컬렉션

seasons = ['봄', '여름', '가을', '겨울']
mountains = ['금강산', '봉래산', '풍악산', '개골산']

이 두 리스트를 나란히 순회하면서 아래와 같은 형태로 출력하는 프로그램을 작성해 보자.

원하는 실행 결과:

봄에는 금강산
여름에는 봉래산
가을에는 풍악산
겨울에는 개골산

중첩된 구조를 순회할 때처럼 for 문을 중첩하여 순회하도록 하면 될까?

코드 7-18 반복을 중첩하는 것으로는 두 리스트를 나란히 순회할 수 없다

for season in seasons:
    for mountain in mountains:
        print(season + '에는 ' + mountain)

실행 결과:

봄에는 금강산
봄에는 봉래산
봄에는 풍악산
봄에는 개골산
여름에는 금강산
여름에는 봉래산
여름에는 풍악산
여름에는 개골산
가을에는 금강산
가을에는 봉래산
가을에는 풍악산
가을에는 개골산
겨울에는 금강산
겨울에는 봉래산
겨울에는 풍악산
겨울에는 개골산

뭔가 잘못되었다. 두 리스트는 중첩된 관계가 아니기 때문에 for 문을 중첩하는 것으로는 원하는 결과를 얻을 수 없다. 우리가 원하는 처리 과정은 seasons의 첫번째 요소와 mountains의 첫번째 요소를 동시에 출력하고, 그 다음으로 seasons의 두번째 요소와 mountains의 두번째 요소를 동시에 출력하는 식의 과정이다. 하지만 중첩된 for 문은 seasons의 각 요소마다 mountains의 모든 요소를 출력해 버린다.

zip() 함수로 엮기

이 문제는 zip() 함수를 사용하여 해결할 수 있다. zip() 함수는 여러 개의 컬렉션을 입력받아 각 요소를 순서대로 엮는 함수다. 예를 들어 [1, 2, 3], ['a', 'b', 'c'], ['가', '나', '다'] 세 개의 시퀀스를 zip() 함수로 엮으면 다음과 같이 요소들을 엮은 튜플들의 시퀀스를 구할 수 있다.

코드 7-19 zip() 함수로 시퀀스 엮기

>>> list(zip([1, 2, 3], ['a', 'b', 'c'], ['가', '나', '다']))
[(1, 'a', '가'), (2, 'b', '나'), (3, 'c', '다')]

각 시퀀스의 첫 번째 요소인 1, 'a', '가'(1, 'a', '가')와 같이 튜플로 묶였음을 확인하자. 두 번째 요소와 세 번째 요소도 마찬가지로 묶였다. 그러면 zip() 함수를 이용해 두 seasons 리스트와 mountains 리스트를 나란히 순회해 보자.

코드 7-20 zip() 함수를 이용해 두 리스트를 나란히 순회하기

for season, mountain in zip(seasons, mountains):
    print(season + '에는 ' + mountain)

실행 결과:

봄에는 금강산
여름에는 봉래산
가을에는 풍악산
겨울에는 개골산

코드 7-20은 seasonsmountains를 직접 순회하지 않고, 두 리스트를 zip() 함수로 묶은 후 순회하였다. zip() 함수가 반환하는 시퀀스의 각 요소는 seasonsmountains의 각 요소를 순서대로 엮은 튜플이기 때문에 이를 언패킹하여 seasons 변수와 mountain 변수에 대입하도록 했다. 이런 요령으로 여러 개의 컬렉션을 나란히 순회할 수 있다.

개념 정리

  • zip() 함수는 여러 컬렉션의 요소들을 순서대로 엮는다.
  • zip() 함수를 이용하여 컬렉션들을 엮어 나란히 순회할 수 있다.

연습문제

연습문제 7-6 두 시퀀스의 요소 합하기

두 시퀀스의 요소를 합하는 함수 plus_elements()를 정의하여라. 이 함수는 두 개의 시퀀스를 전달받은 후 각 요소를 순서대로 합한 리스트를 반환한다. 예를 들어, (1, 2, 3)[4, 5, 6]을 전달받은 경우 [5, 7, 9]를 반환한다.