12. 리스트와 튜플#

아래 표에 여섯 명의 이름, 전화번호, 나이, 키, 출생지 정보가 담겨 있다.

이름

전화번호

나이

출생지

김강현

010-1234-5678

20

172.3

제주

황현

02-9871-1234

19

163.5

서울

남세원

010-3456-7891

21

156.7

경기

최흥선

070-4321-1111

21

187.2

부산

김현선

010-3333-8888

22

164.6

광주

함중아

010-7654-2345

18

178.3

강원

예를 들어 김강현, 최흥선 등의 전화번호를 알고 싶으면 이름 칸에서 김강현과 최흥선이 위치한 행을 찾아 전화번호를 확인하면 된다. 하지만 만약에 여섯 명이 아니라 수천, 수만명의 정보를 담겨 있다면 특정인의 전화번호, 나이, 키, 출생지 등을 확인하는 일이 매우 어려워진다.

반면에 컴퓨터는 컴퓨터는 일런 일을 매우 빠르고 정확하게 처리한다. 단, 컴퓨터가 정보를 처리할 수 있도록 먼저 위 표의 내용을 하나의 값으로 저장되어 있어야 한다.

이름, 전화번호, 나이, 키 각각은 문자열, 정수, 부동소수점 등 적절한 자료형의 값으로 변수에 할당하여 저장할 수 있다. 예를 들어 김강현의 데이터는 아래와 같이 다룰 수 있다.

kgh_name = '김강현'
kgh_phone = '010-1234-5678'
kgh_age = 20
kgh_height = 172.3
kgh_birthplace = '제주'

반면에 지금까지 살펴 본 기초 자료형만으로는 아래 질문에 제대로 답할 수 없다.

  • 표에 언급된 여섯 명의 이름으로 구성된 목록을 하나의 값으로 다룰 수 있을까?

  • 이름과 전화번호를 하나의 쌍으로 묶어서 전화번호부를 만든 다음에 이름을 입력하면 전화번호를 확인하는 프로그램을 작성할 수 있을까?

즉, 기초 자료형 이외에 여러 개의 값을 하나로 묶어 처리할 수 있는 기능이 필요하다.

12.1. 파이썬 내장 자료 구조#

파이썬은 여러 개의 값을 하나의 값으로 묶어 처리할 수 있도록 도와주는 네 개의 내장 자료 구조built-in data structure를 제공한다. 여기서 내장built-in이라 함은 코드를 작성할 때 다른 추가 설정 없이 바로 사용할 수 있음을 의미한다.

반면에 자료 구조data structure는 여러 개의 값으로 구성된 보다 복잡한 대상을 가리킨다. 그리고 파이썬은 이런 대상도 하나의 값으로 취급한다. 즉 리스트, 튜플, 사전, 집합 또한 하나의 값이다.


12.1.1. 모음 자료형 대 스칼라 자료형#

리스트, 튜플, 사전, 집합은 각자 고유의 방식으로 여러 개의 값을 모아서 하나의 값으로 다룬다. 모음 자료형의 값에 포함된 항목의 수는 일반적으로 len() 함수를 이용하여 확인할 수 있다. 여러 개의 값을 모아 항목으로 포함한다는 의미에서 이들을 모음 자료형이라 부르며 경우에 따라 컬렉션collection, 컨테이너container 등으로도 불린다. 반면에 정수, 부동소수점, 불리언 등은 하나의 값으로만 구성되었다는 의미에서 스칼라scalar 자료형이라 부른다.

이것이 가능한 이유는 파이썬에서 다루는 모든 것이 객체object로 정의되기 때문이다. 파이썬 객체는 일반적으로 값과 값과 관련된 속성을 저장하며, 저장된 값과 속성을 다룰 수 있는 다양한 기능을 제공한다.

객체의 엄밀한 정의는 15장에서 클래스를 설명할 때 다룬다. 여기서는 변수에 할당될 수 있는 값을 표현하는 전문 용어로 이해하면 된다.

12.1.2. 모음 자료형 구분#

모음 자료형을 일반적으로 다음 두 가지 기준으로 구분한다.

첫째, 항목의 순서와 중복 허용 여부

  • 순차 자료형sequence type: 항목의 순서와 항목의 중복 사용 허용. 시퀀스sequences라고도 불림.

  • 비순차 자료형non-sequence type: 항목의 순서와 중복된 항목 무시

둘째, 항목 변경의 허용 여부

  • 가변 자료형mutable type: 항목의 추가, 삭제, 변경 등 허용

  • 불변 자료형immutable type: 생성된 객체의 어떠한 변경도 불가능

리스트와 튜플은 모두 순서를 고려하는 순차 자료형이다. 11장에서 살펴 본 문자열 또한 순차 모음 자료형으로 간주된다. 반면에 리스트는 수정이 가능한 가변 자료형이지만 튜플은 불변 자료형이다. 그리고 사전과 집합은 비순차 자료형이면서 동시에 가변 자료형이다.

여기서는 리스트와 튜플, 즉 두 개의 순차(시퀀스) 자료형을 생성, 수정, 활용하는 다양한 방식을 살펴보고, 다음 장에서는 비순차 자료형인 사전과 집합을 소개한다.

12.2. 리스트#

리스트는 관련된 값들을 모아서 하나의 값으로 취급하는 자료형이다. 대괄호 []로 항목들의 목록을 감싸고, 각각의 항목은 쉼표 ,로 구분한다. 예를 들어 위 표에 포함된 이름으로 구성된 리스트를 가리키는 변수 name_list는 다음과 같이 선언한다.

name_list = ['김강현', '황현', '남세원', '최흥선', '김현선', '함중아']

그리고 아래 one2five 변수는 1부터 5까지의 정수로 구성된 리스트를 가리킨다.

one2five = [1, 2, 3, 4, 5]

리스트 항목의 자료형

리스트의 항목이 반드시 동일한 자료형을 가질 필요는 없으며, 서로 다른 자료형의 항목이 사용될 수도 있다. 예를 들어 아래 리스트는 김강현과 황현의 이름, 전화번호, 나이, 키, 출생지로 구성된 리스트를 정의한다. 전화번호가 문자열로 지정되었음에 주의한다.

kgh = ['김강현', '010-1234-5678', 20, 172.3, '제주']
whang = ['황현', '02-9871-1234', 19, 163.5, '서울']

빈 리스트

빈 리스트는 아무것도 포함하지 않는 리스트를 의미한다. 다음 두 가지 방식으로 빈 리스트를 선언할 수 있다.

  • 방법 1: 대괄호 활용

empty_list = []
  • 방법 2: list() 함수 활용

empty_list = list()

중첩 리스트

임의의 값이 리스트의 항목으로 사용될 수 있다. 따라서 리스트가 다른 리스트의 항목으로 허용된다. 아래 코드는 김강현과 황현 두 사람의 정보로 구성된 길이가 2인 리트스를 정의한다.

kgh_whang = [kgh, whang] # 김강현, 황현 두 사람 정보

확인하면 리스트의 리스트, 즉 중첩 리스트가 된다.

kgh_whang
[['김강현', '010-1234-5678', 20, 172.3, '제주'],
 ['황현', '02-9871-1234', 19, 163.5, '서울']]

kgh_whang이 가리키는 리스트에 포함된 항목의 개수, 리스트의 길이는 2다.

len(kgh_whang)
2

반면에 kghwhang 두 변수가 가리키는 리스트의 길이는 5다. 즉, 중첩 리스트의 길이는 항목으로 사용된 리스트의 길이와 무관하다.

len(kgh)
5
len(whang)
5

아래 코드에서 info_list는 나머지 4명의 정보도 항목으로 포함하는 (중첩) 리스트를 가리킨다.

namgung = ['남세원', '010-3456-7891', 21, 156.7, '경기']
choihs = ['최흥선', '070-4321-1111', 21, 187.2, '부산']
sjkim = ['김현선', '010-3333-8888', 22, 164.6, '광주']
ja = ['함중아', '010-7654-2345', 18, 178.3, '강원']
info_list = [kgh, whang, namgung, choihs, sjkim, ja]
info_list
[['김강현', '010-1234-5678', 20, 172.3, '제주'],
 ['황현', '02-9871-1234', 19, 163.5, '서울'],
 ['남세원', '010-3456-7891', 21, 156.7, '경기'],
 ['최흥선', '070-4321-1111', 21, 187.2, '부산'],
 ['김현선', '010-3333-8888', 22, 164.6, '광주'],
 ['함중아', '010-7654-2345', 18, 178.3, '강원']]

info_list 변수가 가리키는 리스트의 길이는 6이다.

len(info_list)
6

리스트는 시퀀스!

리스트는 시퀀스 자료형이기에 항목의 순서가 다르거나 특정 항목의 개수가 다르면 서로 다른 리스트로 간주된다.

  • 순서가 다른 경우: one2five는 1부터 5까지의 순서로 되어 있기에 5부터 1까지로 구성된 리스트와 다르다고 간주된다.

one2five != [5, 4, 3, 2, 1]
True
  • 특정 항목의 개수가 다른 경우: name_list에는 김강현이 한 번 사용되었기에 두 번 사용된 리스트와는 다른 리스트로 간주된다.

name_list != ['김강현', '김강현', '황현', '남세원', '최흥선', '김현선', '함중아']
True

12.2.1. 리스트 연산#

문자열을 포함하여 여기서 소개하는 리스트와 튜플, 그리고 다음 장에서 소개하는 사전, 집합에 대해서 in 연산자를 이용하여 항목의 포함 여부를 확인한다.

in 연산자

예를 들어 3은 one2five 리스트에 포함되어 있다.

3 in [1, 2, 3, 4, 5]
True

반면에 이강현이란 이름은 김강현의 정보를 담은 리스트인 kgh에 포함되어 있지 않다.

'이강현' in name_list
False

물론 김강현은 들어 있다.

'김강현' in name_list
True

+* 연산자

문자열, 리스트, 튜플 세 종류의 시퀀스 자료형 모두 이어붙이기 연산(+)과 복제 연산(*)을 지원한다. 먼저, + 연산자는 리스트 두 개를 이어붙여서 새로운 리스트를 생성한다.

['a', 'b'] + ['c', 'd', 'e']
['a', 'b', 'c', 'd', 'e']

김강현과 황현 두 사람의 정보를 이어 붙인 결과는 다음과 같다.

kgh + whang
['김강현',
 '010-1234-5678',
 20,
 172.3,
 '제주',
 '황현',
 '02-9871-1234',
 19,
 163.5,
 '서울']

* 연산자는 리스트와 양의 정수를 대상으로 하며 리스트를 지정된 정수 만큼 복제해서 이어붙이는 방식으로 새로운 리스트를 생성한다. 정수와 리스트의 순서는 상관 없다.

a2c = ['a', 'b', 'c']

a2c * 3
['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c']

아래 코드는 김강현 정보를 두 번 연속 저장한 리스트를 생성한다.

2 * kgh
['김강현',
 '010-1234-5678',
 20,
 172.3,
 '제주',
 '김강현',
 '010-1234-5678',
 20,
 172.3,
 '제주']

12.2.2. 인덱싱과 슬라이싱#

문자열, 리스트, 튜플 등 모든 시퀀스 자료형은 대괄호와 정수를 사용하는 인덱싱과 슬라이싱을 지원하며, 사용 방식도 거의 동일하다.

인덱싱

리스트 인덱싱은 정수 인덱스가 가리키는 위치의 항목을 확인하거나 수정할 때 사용한다. 예를 들어 김강현의 정보를 담은 리스트 kgh 에서 이름은 0번 인덱스에 위치한다.

print(kgh)
['김강현', '010-1234-5678', 20, 172.3, '제주']
kgh[0]
'김강현'

김강현의 전화번호는 1번 인덱스에 위치한다.

kgh[1]
'010-1234-5678'

-1, -2, -3 등 음수 인덱스는 리스트 오른쪽에서부터 위치를 찾는다. 따라서 김강현의 출생지는 -1번 인덱스로 확인한다.

kgh[-1]
'제주'

-1번 인덱스는 리스트의 길이에서 1을 뺀 인덱스와 동일한 위치를 가리킨다. 즉, 다음이 성립한다.

kgh_last_index = len(kgh) - 1
kgh[kgh_last_index] == kgh[-1]
True

김강현의 키는 리스트의 오른쪽 끝에서 두 번째 항목이기에 -2번 인덱스로 확인된다.

kgh[-2]
172.3

리스트는 항목을 수정할 수 있는 가변 자료형이며 인덱싱을 이용하여 특정 위치의 항목을 수정할 수 있다. 예를 들어 아래 코드는 김강현의 출생지를 제주가 아닌 제주시로 수정한다.

kgh[kgh_last_index] = '제주시' # kgh의 마지막 인덱스에 '제주시'를 수정

물론 -1번 인덱스도 사용할 수 있다.

kgh[-1] = '제주시'

김강현의 출생지 정보가 제주시로 변경되었다.

kgh
['김강현', '010-1234-5678', 20, 172.3, '제주시']

인덱스 허용 범위

리스트 인덱싱에 사용되는 인덱스는 리스트의 길이에 의해 결정된다. 예를 들어 김강현의 정보를 담은 리스트의 길이가 5이기 때문에 -5부터 4까지의 정수만 인덱싱에 허용된다. 지정된 범위를 벗어난 인덱스를 사용하면 지정된 인덱스의 범위를 벗어났다(list index out of range)는 설명과 함께 IndexError오류가 발생한다.

kgh[5]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[34], line 1
----> 1 kgh[5]

IndexError: list index out of range

슬라이싱

슬라이싱은 두 개의 인덱스로 지정된 구간에 포함된 항목들을 확인하거나 수정할 때 사용한다. 경우에 따라 몇 걸음씩 건너뛸지 지정하기도 한다.

  • 시작 인덱스: 슬라이싱 구간 시작 인덱스. 생략되면 0을 기본값으로 사용.

  • 끝 인덱스: 슬라이싱 구간 끝 인덱스. 이 인덱스 이전 인덱스까지 항목 확인. 생략되면 오른쪽 끝까지를 의미함.

  • 보폭: 구간 시작부터 몇 개씩 건너뛰며 항목을 확인할 것인지 결정. 보폭이 1이면 생략 가능.

예를 들어 김강현의 이름, 전화번호, 나이, 키는 0번부터 3번 인덱스에 위치하기에 대괄호 안에 0:4를 입력하여 0번 인덱스부터 4번 인덱스 이전인 3번 인덱스의 항목으로 구성된 리스트가 확인된다.

kgh[0:4]
['김강현', '010-1234-5678', 20, 172.3]

보폭이 1인 경우와 동일하다.

kgh[0:4:1]
['김강현', '010-1234-5678', 20, 172.3]

시작 인덱스가 0이면 생략해도 된다. 단 콜론은 그대로 둬야 한다.

kgh[:4]
['김강현', '010-1234-5678', 20, 172.3]

슬라이싱의 결과는 항상 리스트이다. 구간의 크기가 1이라 해도 그렇다. 예를 들어 아래 코드는 김강현의 전화번호로만 구성되어 길이가 1인 리스트가 확인된다.

kgh[1:2]
['010-1234-5678']

보폭을 1보다 크게 지정하면 지정된 보폭만큼 건너 뛰며 항목을 확인한 결과를 리스트로 보여준다. 예를 들어 김강현의 전화번오와 키를 함께 확인하려면 1번과 3번 인덱스를 확인해야 하기에 다음과 같이 보폭을 2로 지정한다.

kgh[1:4:2]
['010-1234-5678', 172.3]

이름과 키를 확인하려면 0번부터 끝까지를 구간으로 하면서 보폭을 3으로 지정한다.

kgh[0::3]
['김강현', 172.3]

시작 인덱스를 생략해도 된다.

kgh[::3]
['김강현', 172.3]

리스트 전체를 대상으로 슬라이싱하려면 아래와 같이 한다.

kgh[:]
['김강현', '010-1234-5678', 20, 172.3, '제주시']

또는

kgh[::] # kgh 전체 대상 슬라이싱. 스텝은 1이 기본값.
['김강현', '010-1234-5678', 20, 172.3, '제주시']

슬라이싱에 리스트의 크기를 벗어나는 인덱스를 사용하더라도 오류가 발생하지 않는다. 대신 허용되는 인덱스의 구간에 대해서만 슬라이싱이 적용된다. 아래 코드는 5번 인덱스부터 9번 인덱스까지 확인하려 하지만 해당 인덱스의 위치를 찾을 수 없기에 빈 리스트를 생성한다.

kgh[5:10]
[]

아래 코드는 0번 인덱스부터 7번 인덱스까지 확인하려 하지만 결국엔 4번 인덱스까지만 확인하게 된다.

kgh[0:8]
['김강현', '010-1234-5678', 20, 172.3, '제주시']

보폭을 2 이상으로 지정해도 허용된 인덱스의 범위를 벗어난 인덱스는 무시된다.

kgh[0:8:2]
['김강현', 20, '제주시']

역순 슬라이싱

슬라이싱은 기본적으로 작은 인덱스에서 큰 인덱스 방향으로 확인한다. 음수 보폭을 지정하면 큰 인덱스에서 작은 인덱스 방향으로 움직이는 역순 슬라이싱이 실행된다.

예를 들어, 아래 코드는 보폭이 -1이고, 시작 인덱스와 끝 인덱스를 생략하면 문자열 전체를 역순으로 확인한다. 이 기법은 리스트 항목들의 순서를 뒤집는 데에 자주 활용된다.

kgh[:: -1]
['제주시', 172.3, 20, '010-1234-5678', '김강현']

보폭이 음수이면서 시작 인덱스가 끝 인덱스보다 작으면 빈 리스트가 생성된다.

kgh[2:5:-1]
[]

시작 인덱스가 끝 인덱스보다 크면 구간의 오른쪽부터 항목을 확인한다 점만 다를 뿐이다. 아래 코드는 -2번 인덱스부터 왼쪽으로 두 걸음씩 건너 뛰며 슬라이싱을 진행한다. 역순 인덱싱에서 끝 인덱스가 생략되면 왼쪽 끝까지를 의미힌다.

kgh[-2::-2]
[172.3, '010-1234-5678']

구간 수정

슬라이싱을 이용하여 리스트의 지정된 구간을 다른 리스트로 대체할 수 있다. 아래 코드는 김강현의 키와 출생지를 동시에 수정한다.

kgh[3:] = [172.5, '제주']

kgh
['김강현', '010-1234-5678', 20, 172.5, '제주']

슬라이싱 구간의 크기와 새롭게 대체하는 리스트의 크기가 다를 수 있다. 설명을 위해 다음 리스트를 이용한다.

a2f = ['a', 'b', 'c', 'd', 'e', 'f']

소문자 c와 d를 대문자 C와 D로 수정하고 곧바로 대문자 X를 추가하고자 한다면 2번, 3번 인덱스 구간을 슬라이싱하면서 동시에 값을 다음과 같이 3개 지정하면 된다.

a2f[2:4] = ['C', 'D', 'Z']
a2f
['a', 'b', 'C', 'D', 'Z', 'e', 'f']

만약 보폭을 2이상으로 지정하면 슬라이싱된 항목의 개수와 새롭게 지정된 리스트의 길이가 같아야 한다. 아래 코드는 홀수 인덱스에 위치한 항목을 모두 해당 인덱스로 대체한다.

a2f[1::2] = [1, 3, 5]
a2f
['a', 1, 'C', 3, 'Z', 5, 'f']

보폭이 1이 아닌 경우 대체 리스트의 길이가 슬라이싱된 항목의 개수와 다르면 두 리스트의 크기가 다르다는 설명과 함께 ValueError가 발생한다. 아래 코드는 슬라이싱된 항목은 2개이지만 길이가 3인 리스트를 대체 리스트로 지정하기에 오류가 발생한다.

a2f[:3:2] = ['X', 'Y', 'Z']
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[54], line 1
----> 1 a2f[:3:2] = ['X', 'Y', 'Z']

ValueError: attempt to assign sequence of size 3 to extended slice of size 2

12.2.3. 중첩 리스트의 인덱싱/슬라이싱/반복문#

중첩 리스트는 리스트의 항목 또한 리스트이기 때문에 항목의 항목을 확인/추출/변경 하려면 인덱싱, 슬라이싱, for 반복문을 연속적으로 또는중첩해서 적용해야 한다.

인덱싱 연속 적용

예를 들어 김강현의 이름은 info_list의 첫째 항목 리스트의 첫째 항목이다.

info_list
[['김강현', '010-1234-5678', 20, 172.5, '제주'],
 ['황현', '02-9871-1234', 19, 163.5, '서울'],
 ['남세원', '010-3456-7891', 21, 156.7, '경기'],
 ['최흥선', '070-4321-1111', 21, 187.2, '부산'],
 ['김현선', '010-3333-8888', 22, 164.6, '광주'],
 ['함중아', '010-7654-2345', 18, 178.3, '강원']]

김강현의 이름은 info_list의 0번 인덱스에 포함되어 있다. 따라서 먼저 김강현의 정보를 담은 리스트를 인덱싱으로 추출한다.

kgh_name = info_list[0]
kgh_name
['김강현', '010-1234-5678', 20, 172.5, '제주']

다시 0번 인덱스를 적용하면 김강현의 이름이 확인된다.

kgh_name[0]
'김강현'

인덱싱을 연속 적용하는 과정을 다음과 같이 줄여서 하나의 표현식으로 나타낼 수 있다.

info_list[0][0]
'김강현'

아래 코드는 유사한 방식으로 황현의 이름과 나이를 확인한다.

hwang_name = info_list[1][0]
hwang_age = info_list[1][2]

print(f"{hwang_name}의 나이: {hwang_age}세")
황현의 나이: 19세

슬라이싱과 인덱싱 연속 적용

황현, 최흥선, 함중아만 대상으로 나이를 확인하려 한다. 그런데 세 사람의 정보는 info_list가 가리키는 리스트의 1번, 3번, 5번 인덱스에 위치한다.

info_list[1::2]
[['황현', '02-9871-1234', 19, 163.5, '서울'],
 ['최흥선', '070-4321-1111', 21, 187.2, '부산'],
 ['함중아', '010-7654-2345', 18, 178.3, '강원']]

아래 코드는 세 사람의 정보를 대상으로 for 반복문과 인덱싱을 인덱싱을 적용하면 세 사람의 나이 정보를 추출한다.

for person in info_list[1::2]:
    print(f"{person[0]}:\t{person[2]}세")
황현:	19세
최흥선:	21세
함중아:	18세

인덱싱과 슬라이싱 연속 적용

아래 코드는 4번 인덱스에 위치한 김현선의 이름, 전화번호, 나이를 확인한다.

info_list[4][:3]
['김현선', '010-3333-8888', 22]

중첩 반복문 활용

  • 예제 1

중첩 for 반복문을 사용하여 6명 각자의 정보를 일일이 나열할 수 있다.

for person in info_list:  # 6명 모두를 대상으로 반복
    for item in person:
        print(item)       # 한 사람의 모든 정보 출력. 항목들 사이는 탭으로 구분

    print()               # 사람들 사이의 구분을 위해 줄 바꿈
김강현
010-1234-5678
20
172.5
제주

황현
02-9871-1234
19
163.5
서울

남세원
010-3456-7891
21
156.7
경기

최흥선
070-4321-1111
21
187.2
부산

김현선
010-3333-8888
22
164.6
광주

함중아
010-7654-2345
18
178.3
강원

print() 함수의 end='\n' 키워드 옵션을 변경하여 한 사람의 정보를 한 줄에 출력할 수 있다.

for person in info_list:
    for item in person:
        print(item, end='\t') # 한 사람의 정보는 탭으로 구분

    print()             
김강현	010-1234-5678	20	172.5	제주	
황현	02-9871-1234	19	163.5	서울	
남세원	010-3456-7891	21	156.7	경기	
최흥선	070-4321-1111	21	187.2	부산	
김현선	010-3333-8888	22	164.6	광주	
함중아	010-7654-2345	18	178.3	강원	
  • 예제 2

아래 코드는 나이가 21살인 사람의 정보만 출력한다.

for person in info_list:
    if 21 == person[2]:           # 나이가 21살인 사람만 선택
        for item in person:
            print(item, end='\t')
    
        print()
남세원	010-3456-7891	21	156.7	경기	
최흥선	070-4321-1111	21	187.2	부산	
  • 예제 3

이름에 “현” 자가 포함된 사람의 정보만 출력하러면 다음과 같이 한다.

for person in info_list:
    if '현' in person[0]: # 이름에 "현" 자가 포함된 사람만 선택
        for item in person:
            print(item, end='\t')
    
        print()
김강현	010-1234-5678	20	172.5	제주	
황현	02-9871-1234	19	163.5	서울	
김현선	010-3333-8888	22	164.6	광주	

“현”으로 끝나는 경우만 다루려면 endswith() 문자열 메서드를 이용한다.

for person in info_list:
    if person[0].endswith('현'): # 이름이 "현" 자로 끝나는 사람만 선택
        for item in person:
            print(item, end='\t')
    
        print()
김강현	010-1234-5678	20	172.5	제주	
황현	02-9871-1234	19	163.5	서울	

startswith() 문자열 메서드를 이용하여 김씨 성만 추출할 수도 있다.

for person in info_list:
    if person[0].startswith('김'): # 김씨 성 정보만 선택
        for item in person:
            print(item, end='\t')
    
        print()
김강현	010-1234-5678	20	172.5	제주	
김현선	010-3333-8888	22	164.6	광주	

12.2.4. 리스트 메서드#

모음 자료형의 기본 용도는 여러 개의 값을 모아 하나의 값으로 다루면서 필요에 따라 유용한 항목을 탐색하고 추출하는 기능이다. 11장에서 살펴 본 문자열 메서드처럼 리스트 자료형에 대해서만 사용할 수 있는 함수들인 리스트 메서드가 다양하게 제공된다.

문자열 자료형과는 달리 리스트는 변경을 허용하는 가변 자료형이기에 항목의 탐색뿐만 아니라 리스트 자체를 수정하는 항목의 추가와 삭제, 항목들의 정렬을 수행하는 메서드 또한 제공된다. 간단한 예제를 이용하여 리스트의 아래 표에 언급된 주요 리스트 메서드의 기능을 살펴 본다.

Table 12.1 리스트 주요 메서드#

기능

메서드

설명

복사

copy()

리스트의 사본 반환

탐색

count()

리스트에서 지정된 항목이 등장한 횟수 반환

index()

지정된 항목이 처음 사용된 인덱스 반환

추가/삽입/확장

append()

리스트 끝에 항목 추가. 반환값은 None

insert()

지정된 인덱스에 항목 삽입. 반환값은 None

extend()

다른 리스트를 연결하여 확장. 반환값은 None

삭제

pop()

지정된 인덱스의 항목 삭제 후 반환.

remove()

가장 왼쪽에 위치한 지정된 항목 삭제. 반환값은 None

정렬

sort()

리스트의 항목을 크기 순으로 정렬. 반환값은 None

reverse()

리스트의 항목들 순서 뒤집기. 반환값은 None

문자열 수정 메서드

언급된 메서드 중에서 copy(), count(), index() 를 제외한 다른 메서드 모두 주어진 리스트 자체를 수정한다.

copy() 메서드

리스트는 가변 자료형이기에 인덱싱, 슬라이싱을 포함하여 이어서 소개하는 많은 메서드에 의해 수정될 수 있다. 따라서 경우에 따라 주어진 원본 리스트는 전혀 건드리지 않으면서 리스트를 이용할 필요가 있다. 그럴 때 copy() 메서드로 사본을 만들어 이용한다.

예를 들어, 아래 코드는 변수 x가 가리키는 리스트의 사본을 만들어 변수 y에 할당한다. 그 다음에 y가 가리키는 리스트의 0번 인덱스 항목을 수정하지만 변수 x가 가리키는 리스트는 전혀 변하지 않는다.

x  = [1, 2, 3]
y = x.copy()
y[0] = 10      # 0번 인데스 항목 수정

print("y:", y) # 수정됨
print("x:", x) # 불변
y: [10, 2, 3]
x: [1, 2, 3]

아래 그림은 두 변수 xy가 선언된 순간의 메모리 상태를 보여준다. xy가 동일하게 생겼지만 각각 서로 다른 리스트 객체를 가리킨다.

<그림 출처: Python Tutor>

아래 그림은 y가 가리키는 리스트의 첫째 항목이 10으로 업데이트된 이후의 메모리 상태를 보여주며, 변수 x가 가리키는 리스트는 전혀 수정되지 않는다.

<그림 출처: Python Tutor>

반면에 아래 코드에서처럼 사본을 만들지 않으면 리스트 원본이 함께 수정된다. 이유는 xy가 동일한 리스트 객체를 가리키기 때문이다.

x  = [1, 2, 3]
y = x
y[0] = 10      # 0번 인데스 항목 수정

print("x:", x)
print("y:", y)
x: [10, 2, 3]
y: [10, 2, 3]

아래 그림은 두 변수 xy가 선언된 순간의 메모리 상태를 보여준다. xy가 동일한 리스트 객체를 가리킨다.

<그림 출처: Python Tutor>

아래 그림은 y가 가리키는 리스트의 첫째 항목이 10으로 업데이트되면 x가 동일한 리스트를 가리키기에 0번 인덱스 항목이 똑같이 10이 된다.

<그림 출처: Python Tutor>

count() 메서드

인자로 지정된 항목이 리스트에 몇 번 사용되었는지를 반환한다. 예를 들어, [1, 2, 3, 1, 2]에 1과 2는 두 번, 3은 한 번 사용된다.

oneTwoThree = [1, 2, 3, 1, 2]
oneTwoThree.count(1)
2
oneTwoThree.count(2)
2
oneTwoThree.count(3)
1

항목이 아니면 0을 반환한다.

oneTwoThree.count(4)
0

index() 메서드

인자로 지정된 항목이 위치한 인덱스를 반환한다. 항목이 여러 번 사용된 경우 가장 왼쪽에 위치한 곳의 인덱스를 선택한다. 예를 [1, 2, 3, 1, 2, 3]에서 2가 두 번 사용되었지만 1번 인덱스에서 가장 먼저 사용된다.

oneTwoThree.index(2)
1

반면에 3은 2번 인덱스에서 처음 사용된다.

oneTwoThree.index(3)
2

리스트의 항목으로 존재하지 않으면 ValueError 오류가 발생한다.

oneTwoThree.index(5)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[78], line 1
----> 1 oneTwoThree.index(5)

ValueError: 5 is not in list

참고로 문자열의 경우와는 다르게 리스트는 find() 메서드를 제공하지 않는다.

append() 메서드

리스트의 오른쪽 끝에 항목을 추가한다. 반환값은 None이다. 아래 코드는 oneTwoThree가 가리키는 리스트에 3을 추가한다.

oneTwoThree.append(3)

반면에 oneTwoThree가 가리키는 리스트 자체가 수정되었다.

oneTwoThree
[1, 2, 3, 1, 2, 3]

아래 for 반복문은 1, 2, 3을 한 번씩 더 추가한다.

for i in range(1, 4):
    oneTwoThree.append(i)
oneTwoThree
[1, 2, 3, 1, 2, 3, 1, 2, 3]

insert() 메서드

insert() 메서드는 인덱스를 이용하여 특정 위치에 항목을 삽입한다. 반환값은 None이다. 항목이 삽입되면 원래 그 위치를 포함해서 오른쪽에 위치했던 항목들은 모두 한 칸씩 오른쪽으로 이동된다.

아래 코드는 1번 인덱스 자리에 2를 삽입하여 1부터 4까지의 정수로 구성된 리스트를 완성한다.

one2four = [1, 3, 4]
one2four.insert(1, 2) # 1번 인덱스에 2 삽입
one2four
[1, 2, 3, 4]

아래 for 반복문은 4, 3, 2, 1을 차례대로 0번 인덱스에 추가한다.

for i in range(4, 0, -1):
    one2four.insert(0, i)
one2four
[1, 2, 3, 4, 1, 2, 3, 4]

extend() 메서드

append() 메서드는 기존의 리스트 오를쪽 끝에 하나의 항목을 추가한다. 반면에 extend() 메서드는 기존 리스트의 오른쪽 끝에 인자로 지정된 리스트를 연결한다. 반환값은 None이다.

아래 코드는 one2four가 가리키는 리스트에 [1, 2, 3, 4]를 연결한다.

one2four.extend([1, 2, 3, 4])
one2four
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]

참고로 + 연산자는 리스트 두개를 이어붙여 완전히 새로운 리스트를 생성한다. 예를 들어 아래 코드는 one2four가 가리키는 리스트와 [1, 2, 3, 4]를 연결한 새로운 리스트를 생성한다.

one2four + [1, 2, 3, 4]
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]

one2four가 가리키는 리스트는 수정되지 않았다.

one2four
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]

pop() 메서드

pop() 메서드는 지정된 인덱스의 항목을 반환하는 동시에 리스트에서 삭제한다. 설명을 위해 one2four 변수를 계속 활용한다. 아래 코드는 3번 인덱스에 위치한 정수 4를 리스트에서 삭제하면서 동시에 반환한다.

four_index3 = one2four.pop(3)

pop() 함수는 삭제된 항목을 반환한다.

four_index3
4

3번 인덱스의 항목인 4가 one2four에서 삭제되었다. 삭제된 항목의 오른쪽에 위치한 항목은 한 칸씩 왼쪽으로 이동한다.

one2four
[1, 2, 3, 1, 2, 3, 4, 1, 2, 3, 4]

one2four를 원래대로 되돌려 놓기 위해 insert() 메서드를 이용한다.

one2four.insert(3, four_index3)
one2four
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]

pop() 메서드의 인자를 지정하지 않으면 리스트의 마지막 항목이 삭제된 후 반환된다.

one2four.pop()
4

마지막 항목이기에 이번엔 append() 메서드로 원래대로 되돌린다.

one2four.append(4)
one2four
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]

remove() 메서드

remove() 메서드는 리스트에서 지정된 항목을 삭제할 뿐 해당 항목을 반환하진 않는다. 즉 None을 반환한다. 지정된 항목이 리스트에 여러 번 포함되었을 경우 가장 왼편에 위치한 항목을 삭제한다.

아래 코드는 1을 삭제한다.

one2four.remove(1)

0번 인덱스에 있었던 1만 삭제되었다.

one2four
[2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]

삭제할 항목이 없으면 ValueError 오류가 발생한다.

one2four.remove(0)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[101], line 1
----> 1 one2four.remove(0)

ValueError: list.remove(x): x not in list

for 반복문을 이용하여 2, 3, 4를 차례로 삭제하자.

for num in [2, 3, 4]:
    one2four.remove(num)
one2four
[1, 2, 3, 4, 1, 2, 3, 4]

sort() 메서드

리스트의 항목을 크기 순으로 정렬하는 방식으로 리스트를 수정한다. 즉, 리스트 자체가 수정되며, 반환값은 None이다. 예를 들어 아래 코드는 one2four의 항목을 크기 순으로 정렬한다.

one2four.sort()
one2four
[1, 1, 2, 2, 3, 3, 4, 4]

reverse=True 키워드 인자를 지정하면 내림차순으로 정렬한다.

one2four.sort(reverse=True)
one2four
[4, 4, 3, 3, 2, 2, 1, 1]

reverse() 메서드

단순히 리스트에 포함된 항목들의 순서를 뒤집는다. 반환값은 None이다.

설명을 위해 아래 리스트를 이용한다.

acbgf = ['a', 'c', 'b', 'g', 'f']

아래 코드는 acbgf에 포함된 순서를 뒤집는다.

acbgf.reverse()
acbgf
['f', 'g', 'b', 'c', 'a']

reverse() 메서드를 한 번 더 적용하면 원래 순서대로 돌아온다.

acbgf.reverse()
acbgf
['a', 'c', 'b', 'g', 'f']

12.3. 튜플#

튜플은 리스트와 아주 비슷하다. 다만 한 번 생성되면 항목의 추가, 삭제, 변경 등 일체의 수정이 불가능한 불변immutable 자료형이라는 점이 다르다. 무엇보다도 수정을 허용하지 않기에 튜플의 메서드는 몇 개 없다. 또한 튜플의 항목을 활용하는 방법은 리스트의 그것과 동일하다. 예를 들어, 연산, 인덱싱, 슬라싱, for 반복문 활용 등은 리스트의 경우와 완전히 동일하다.

튜플은 항목들을 소괄호 ()로 감싸고, 각각 항목은 쉼표(,)로 구분된다. 예를 들어, 1부터 5까지의 정수로 구성된 튜플을 가리키는 변수를 다음과 같이 선언한다.

one2five_tuple = (1, 2, 3, 4, 5)

다음 튜플은 영어 알파벳 a부터 c까지로 구성된다.

a2c_tuple = ('a','b', 'c')

소괄호를 생략해도 튜플로 자동 처리된다.

a2c_tuple = 'a','b', 'c'
a2c_tuple
('a', 'b', 'c')

길이가 1인 튜플인 경우 항목 뒤에 반드시 쉽표를 붙혀야 한다.

tup_3 = (3, )
type(tup_3)
tuple

쉼표를 사용하지 않으면 튜플로 간주되지 않을 수 있다. 아래 코드에서 non_tup_3은 3을 포함한 튜플이 아닌 정수 3을 가리킨다.

non_tup_3 = (3)
type(non_tup_3)
int

튜플의 항목으로 여러 종류의 값이 사용될 수 있다. 다음 mixed_tuple은 항목으로 정수, 부동소수점, 문자열, 부울값, 리스트, 튜플을 포함한다.

mixed_tuple = (1, 2.3, 'abc', False, ['ab', 'c'], (2, 4))

아래 코드는 김강현의 정보를 튜플로 선언한다. 항목으로 문자열, 정수, 부동소수점 세 종류가 사용된다.

kgh_tuple = tuple(kgh)
kgh_tuple
('김강현', '010-1234-5678', 20, 172.3, '제주시')

빈 튜플

빈 튜플empty tuple은 아무것도 포함하지 않는 튜플을 의미한다. 다음 두 가지 방식으로 빈 튜플을 선언할 수 있다.

  • 방법 1: 소괄호 활용

empty_tuple = ()
  • 방법 2: tuple() 함수 활용

empty_tuple = tuple()

12.3.1. 튜플 연산#

in 연산자

튜플의 항목으로 등장하는지 여부를 알려주는 논리 연산자이다.

'김강현' in kgh_tuple
True
'서울' in kgh_tuple
False
20 in kgh_tuple
True

이어붙이기/복제

리스트의 경우처럼 튜플도 이어붙이기와 복제 연산을 이용하여 새로운 튜플을 생성할 수 있다. 아래 코드는 김강현과 황현의 정보를 함께 담은 튜플을 생성한다.

whang_tuple = tuple(whang)
whang
['황현', '02-9871-1234', 19, 163.5, '서울']
kgh_tuple + whang_tuple
('김강현',
 '010-1234-5678',
 20,
 172.3,
 '제주시',
 '황현',
 '02-9871-1234',
 19,
 163.5,
 '서울')

아래 코드는 김강현의 정보를 두 번 담은 튜플을 생성한다.

2 * kgh_tuple
('김강현',
 '010-1234-5678',
 20,
 172.3,
 '제주시',
 '김강현',
 '010-1234-5678',
 20,
 172.3,
 '제주시')

len()함수

튜플의 길이, 즉 튜플에 포함된 항목의 개수를 반환한다.

len(kgh_tuple)
5
len(2 * kgh_tuple)
10

12.3.2. 튜플 인덱싱/슬라이싱#

튜플도 문자열과 리스트처럼 인덱싱과 슬라이싱이 가능하다.

kgh_tuple[0]
'김강현'
kgh_tuple[-1]
'제주시'
kgh_tuple[::2]
('김강현', 20, '제주시')

튜플은 불변 자료형이라 값을 수정할 수 없다. 예를 들어, 김강현의 나이를 21세로 수정하려 시도하면 오류가 발생한다.

kgh_tuple[2]
20
kgh_tuple[2] = 21
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[52], line 1
----> 1 kgh_tuple[2] = 21

TypeError: 'tuple' object does not support item assignment

12.3.3. 튜플 메서드#

튜플 자료형의 메서드는 많지 않다. 많이 사용되는 두 개의 메소드를 살펴보자.

메서드

설명

count()

지정된 항목이 등장한 횟수 반환

index()

지정된 항목이 처음 위치한 인덱스 반환

count() 메서드

아래 코드는 김강현의 정보를 두 번 연속으로 갖고 있는 튜플에서 김강현의 이름이 두 번 사용되었음을 학인해준다.

double_kgh = 2 * kgh_tuple
double_kgh
('김강현',
 '010-1234-5678',
 20,
 172.3,
 '제주시',
 '김강현',
 '010-1234-5678',
 20,
 172.3,
 '제주시')
double_kgh.count("김강현")
2

index() 메서드

아래 코드는 김강현의 출생지는 김강현의 정보에서 4번 인덱스에 위치함을 확인해준다.

kgh_tuple.index('제주시')
4

12.4. 순차 자료형 해체#

순자 자료형의 항목 각각에 대해 변수 할당을 진행할 수 있다. 단, 사용되는 변수의 수는 항목의 수와 일치해야 한다.

문자열 해체

a, b, c = "123"
print(a)
print(b)
print(c)
1
2
3

a, b, c 각각의 자료형은 문자열이다.

type(a)
str

리스트 해체

아래 코드는 김강현의 정보 각각에 대해 변수를 선언한다.

name, phone, age, height, birth_place = kgh
print(name) 
print(phone) 
print(age)
print(height)
print(birth_place)
김강현
010-1234-5678
20
172.3
제주시

튜플 해체

아래 코드는 황현의 정보 각각에 대해 변수를 선언한다.

name, phone, age, height, birth_place = whang_tuple
print(name) 
print(phone) 
print(age)
print(height)
print(birth_place)
황현
02-9871-1234
19
163.5
서울

밑줄 기호 _ 활용

변수의 이름이 굳이 필요 없다면 밑줄underscore(_) 기호를 대신 사용한다. 예를 들어, 전화번호 정보가 필요없다면 phone 변수 대신에 밑줄 _ 기호를 사용하는 게 일반적이다.

name, _, age, height, birth_place = kgh
print(name) 
print(age)
print(height)
print(birth_place)
김강현
20
172.3
제주시

주의사항

항목의 개수와 변수의 개수가 일치하지 않으면 오류가 발생한다. 오류를 피하려면 위해 밑줄 등을 반드시 활용해야 한다.

name, age, height, birth_place = kgh
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[67], line 1
----> 1 name, age, height, birth_place = kgh

ValueError: too many values to unpack (expected 4)

별표 기호 * 활용

앞에 몇 개의 값에만 변수 할당에 사용하고 나머지는 하나의 리스트로 묶어서 퉁쳐버릴 수도 있다. 이를 위해 별표 기호 * 를 하나의 변수명과 함께 사용한다.

name, phone, *etc = whang_tuple
print(name)
print(phone)
print(etc)
황현
02-9871-1234
[19, 163.5, '서울']

나머지 항목들을 무시하고 싶다면, 별표와 밑줄을 함께 사용한다.

name, phone, *_ = whang_tuple
print(name)
print(phone)
황현
02-9871-1234

12.5. 순차 자료형에 유용한 함수#

문자열, 튜플, 리스트처럼 항목들의 순서가 중요한 순차 자료형과 함께 유용하게 사용되는 두 개의 함수를 소개한다.

  • enumerate() 함수

  • zip() 함수

12.5.1. enumerate() 함수#

튜플과 리스트는 항목들의 인덱스를 바로 제공하지 않는다. 하지만 항목과 해당 항목의 인덱스 정보를 함께 활용해야 할 때가 있는데 이때 enumerate() 함수가 매우 유용하다. 이를 위해 아래 리스트를 이용한다.

some_list = ['foo', 'bar', 'baz', 'pyt', 'thon']

enumerate() 함수는 리스트를 받아서 리스트의 항목과 인덱스를 쌍으로 갖는 enumerate 객체를 준비시킨다. 이렇게 준비된 객체는 range 객체처럼 바로 확인할 수는 없다.

enumerate(some_list)
<enumerate at 0x7f77ec1e72e0>

하지만 리스트로 변환하면 인덱스와 항목의 튜플로 이루어져 있음을 확인 수 있다.

list(enumerate(some_list))
[(0, 'foo'), (1, 'bar'), (2, 'baz'), (3, 'pyt'), (4, 'thon')]

예제

인덱스를 활용하여 원하는 항목만 추출할 수 있다. 예를 들어, 아래 코드는 짝수 인덱스의 값들만 출력하도록 한다. 인덱스와 항목으로 구성된 길이가 2인 튜플을 해체하기 위해 항목의 인덱스를 가리키는 idx 변수와 항목 자체를 가리키는 item 변수를 함께 이용함에 주의하라.

for idx, item in enumerate(some_list):
    if idx % 2 == 0:
        print(item)
foo
baz
thon

enumerate() 함수를 사용하지 않으면서 동일한 기능을 구현하려면 다음과 같이 range() 함수를 이용하여 인덱스에 대해 for 반복문을 이용해야 한다.

for i in range(len(some_list)):
    if i & 2 == 0:
        print(some_list[i])
foo
bar
thon

예제

아래 코드는 인덱스와 리스트의 항목의 순서를 바꾼 튜플의 리스트를 생성한다.

mapping = []

for idx, item in enumerate(some_list):
    mapping.append((item, idx))

mapping
[('foo', 0), ('bar', 1), ('baz', 2), ('pyt', 3), ('thon', 4)]

enumerate() 함수를 사용하지 않으면서 동일한 기능을 구현하려면 인덱스를 직접 이용해야 한다.

mapping = []

for idx in range(len(some_list)):
    mapping.append((some_list[idx], idx))

mapping
[('foo', 0), ('bar', 1), ('baz', 2), ('pyt', 3), ('thon', 4)]

12.5.2. zip() 함수#

문자열, 리스트, 튜플 등 순차 자료형 여러 개를 묶어 하나의 순차 자료형으로 사용되는 zip 객체를 만들며, i 번째 항목은 i 번째 항목들의 튜플로 구성된다.

zip() 함수의 반환값 또한 항목을 바로 확인시켜주지는 않는다.

zip("abc", [1, 2, 3])
<zip at 0x7f77ec1e3b40>

반면에 for 반복문을 이용하여 항목을 확인할 수는 있다. 아래 코드는 새로 생성된 객체의 항목이 동일한 인덱스를 갖는 항목들의 튜플로 구성되었음을 잘 보여준다.

for item in zip("abc", [1, 2, 3]):
    print(item)
('a', 1)
('b', 2)
('c', 3)

list로 변환해도 항목을 확인할 수 있다.

list(zip("abc", [1, 2, 3]))
[('a', 1), ('b', 2), ('c', 3)]

zip() 함수의 인자는 3개 이상도 가능하다. 단. 길이가 다르면 가장 짧은 길이에 맞춰서 짝을 짓고, 나머지 항목은 모두 무시한다. 아래 코드는 가장 짧은 [5, 10, 15]에 맞춰서 2번 인덱스까지만 튜플로 묶는다.

list(zip("abcdefgh",(1, 2, 3, 4, 5), [5, 10, 15]))
[('a', 1, 5), ('b', 2, 10), ('c', 3, 15)]

동시 반복

zip() 함수를 이용하면 여러 개의 리스트, 튜플을 대상으로 동시에 반복문을 실행할 수 있다. 아래 코드는 두 개의 리스트에 대해 동시에 반복문을 실행한다.

letters = ['a', 'b', 'c']
numbers = [0, 1, 2]

for l, n in zip(letters, numbers):
    print(f'문자: {l}', end=', ')
    print(f'숫자: {n}')
문자: a, 숫자: 0
문자: b, 숫자: 1
문자: c, 숫자: 2

zip() 함수를 사용하지 않으면 2중으로 중첩된 for 반복문과 인덱싱을 함께 이용해야 한다.

for idx1 in range(len(letters)):
    for idx2 in range(len(numbers)):
        if idx1 == idx2:
            print(f'문자: {letters[idx1]}', end=', ')
            print(f'숫자: {numbers[idx2]}')
문자: a, 숫자: 0
문자: b, 숫자: 1
문자: c, 숫자: 2
letters = ['a', 'b', 'c']
numbers = [0, 1, 2]
operators = ['*', '/', '+']

for l, n, o in zip(letters, numbers, operators):
    print(f'{l} {o} {n}')
a * 0
b / 1
c + 2

zip() 함수를 사용하지 않으면 3중으로 중첩된 for 반복문과 인덱싱을 함께 이용해야 한다.

for idx1 in range(len(letters)):
    for idx2 in range(len(numbers)):
        for idx3 in range(len(operators)):
            if idx1 == idx2 and idx2 == idx3:
                print(f'{letters[idx1]} {operators[idx3]} {numbers[idx2]}')
a * 0
b / 1
c + 2

12.6. 필수 예제#

참고: (필수 예제) 리스트와 튜플

12.7. 연습문제#

참고: (실습) 리스트와 튜플