처리해야 할 데이터 양이 많아지면 파일에 저장한 후에 필요한 경우 재활용해야 한다. 또한 개별 데이터를 따로따로 처리하기 보다는 하나의 데이터로 묶어서 처리할 수 있어야 한다. 많은 데이처를 하나의 데이터로 묶어서 처리하는 다양한 자료형이 제공되며 여기서는 파이썬의 리스트 자료형의 활용을 알아본다.
텍스트 파일의 내용을 읽어드리는 방법과 파이썬에 내장되어 있는 컬렉션 자료형 중의 하나인 리스트(list)를 활용하는 방법에 대해 알아본다.
리스트(lists): 파이썬에서 사용할 수 있는 임의의 값들을 모아서 하나의 값으로 취급하는 자료형
사용 형태: 대괄호 사용
even_numbers_list = [2, 4, 6, 8, 10]
todays_datatypes_list = ['list', 'tuple', 'dictionary']
특징: 임의의 자료형 값들을 섞어서 항목으로 사용 가능
mixed_list = [1, 'abs', [2.1, 4.5]]
인덱스 또는 슬라이싱을 이용하여 각각의 항목에 또는 여러 개의 항목에 대한 정보를 활용할 수 있다. 사용법은 문자열의 경우와 동일.
리스트는 수정 가능하다. 즉, 가변 자료형이다.
리스트와 관련되어 많이 사용되는 메소드는 다음과 같다.
append()
: 기존의 리스트 끝에 항목 추가extend()
: 두 개의 리스트 이어붙이기insert()
: 기존의 리스트 중간에 항목 삽입pop()
, remove()
, del
: 항목 삭제count()
: 리스트에 포함된 특정 항목이 몇 번 나타나는지 세어 줌.index()
: 특정 항목의 인덱스가 몇 번인지 확인해 줌.data
디렉토리에 위치한
scores_list.txt
파일은 선수 여덟 명의 점수를 담고 있다.
txt
Name Score
player1 21.09
player2 20.32
player3 21.81
player4 22.97
player5 23.29
player6 22.09
player7 21.20
player8 22.16
목표: 위 파일로부터 1~3등 선수의 점수를 아래와 같이 확인하기
txt
1등 23.29
2등 22.97
3등 22.16
참조: Head First Programming(한빛미디어) 4장
즉, 첫째 줄은 선수이름(Name)과 점수(Score)의 항목이 표시되어 있으며 둘째 줄부터 선수이름과 점수가 작성되어 있다.
위 파일의 내용을 아래와 같이 파이썬 코드로 확인할 수 있다.
In [1]:
result_f = open("data/scores_list.txt") # 파일 열기
for line in result_f: # 각 줄 내용 출력하기
print(line)
result_f.close() # 파일 닫기
In [2]:
result_f = open("data/scores_list.txt")
for line in result_f:
print(line.strip()) # strip 메소드 활용하기
result_f.close()
이제 1등 점수를 확인하고자 한다. 이때 이전에 배운 예외처리 기술을 활용해보자.
In [3]:
file = open('data/no_file.txt')
이런 경우에는 열고자 하는 파일이 존재하지 않는다는 정보를 전달하는 것이 단순히 오류가 발생하면서 실행이 멈추는 것보다 훨씬 유익하다.
In [4]:
try:
file = open('data/no_file.txt')
except:
print("열고자 하는 파일이 존재하지 않습니다.")
앞서 파일 내용을 확인해 보았듯 각 줄마다 선수이름과 점수가 공백을 사이로 두고 각 줄에 적혀 있다.
따라서 아래와 같이 split
메소드를 활용하여 각 줄을 쪼개어 두 번째 항목을 확인할 수 있다.
In [5]:
'Name Score'.split()
Out[5]:
파일의 각 줄이 동일한 모양을 갖고 있다는 점에 착안하여 아래와 같이 각줄의 내용 중에서 점수에 해당하는 부분을 아래와 같이 확인할 수 있다.
주의: 리스트의 색인도 문자열의 경우처럼 0부터 시작한다. 따라서 리스트의 둘째 항목의 색인은 1인다.
In [6]:
result_f = open("data/scores_list.txt")
for line in result_f:
record = line.split()
print(record[1])
result_f.close()
그런데 첫째 줄 내용은 점수를 비교하는 데에 필요없다. 따라서 무시하는 방법을 사용하도록 하자.
특정 줄을 무시하는 방법은 여러 기술이 있지만 여기서는 try ... except ...
명령문을 이용한 예외처리 기술을 활용한다.
주의: 여기서 예외처리 기술을 이용하는 이유는 다음과 같다.
split
메소드로 쪼개진 값들은 모두 문자열로 처리된다.float
함수를 적용하면 오류가 발생한다.float
함수를 실행할 때 오류가 발생하면 무시하고 다음 줄로 넘어가는 식으로 오류처리를 하였다.
In [7]:
result_f = open("data/scores_list.txt")
highest_score = 0 # 1등 점수 저장
for line in result_f:
record = line.split()
try: # 첫줄 제외 용도
score = float(record[1])
except:
continue
if highest_score < score: # 1등 점수 갱신 경우 확인
highest_score = score
else:
continue
result_f.close()
print("1등 점수는", highest_score, "입니다.")
In [8]:
result_f = open("data/scores_list.txt")
highest_score = 0
second_highest_score = 0 # 2등 점수 저장
for line in result_f:
record = line.split()
try:
score = float(record[1])
except:
continue
if highest_score < score: # 1, 2등 점수 갱신 경우 확인
second_highest_score = highest_score
highest_score = score
elif second_highest_score < score: # 2등 점수 갱신 경우 확인
second_highest_score = score
else:
continue
result_f.close()
print("1등 점수는", highest_score, "입니다.")
print("2등 점수는", second_highest_score, "입니다.")
In [9]:
result_f = open("data/scores_list.txt")
highest_score = 0
second_highest_score = 0
third_highest_score = 0 # 3등 점수 저장
for line in result_f:
record = line.split()
try:
score = float(record[1])
except:
continue
if highest_score < score: # 1, 2, 3등 점수 갱신 확인
third_highest_score = second_highest_score
second_highest_score = highest_score
highest_score = score
elif second_highest_score < score: # 2, 3등 점수 갱신 확인
third_highest_score = second_highest_score
second_highest_score = score
elif third_highest_score < score: # 3등 점수 갱신 확인
third_highest_score = score
else:
continue
result_f.close()
print("1등 점수는", highest_score, "입니다.")
print("2등 점수는", second_highest_score, "입니다.")
print("3등 점수는", third_highest_score, "입니다.")
앞서 1등까지, 2등까지, 3등까지 점수를 확인하는 코드는 각자 다르며, 점처 길어지고 복잡해졌다. 코드를 이런 식으로 구현하면 안된다.
무엇보다도 원하는 등수에 따라 코드 자체가 수정되어야 하는 방식으로 프로그래밍을 하면 절대 안된다. 그럼 어떻게 할까?
앞선 코드의 근본적인 문제점은 각 선수의 점수를 따라따로 관리하기 때문에 발생한다. 따라서 선수의 점수를 모아서 한꺼번에 처리하는 기술이 요구된다. 여기서는 리스트 자료형을 활용하여 원하는 등수와 선수의 수에 상관없이 동일한 코드로 원하는 결과를 리턴하는 프로그램을 구현하고자 한다.
몇 등 점수를 알아내야 하는가와 상관없이 모든 질문에 답을 하는 하나의 프로그램을 리스트를 활용하여 구현하고자 하며, 아이디어는 다음과 같다.
In [10]:
result_f = open("data/scores_list.txt")
score_list = [] # 점수 저장 리스트 생성
for line in result_f:
(name, score) = line.split() # 각 줄을 두 단어의 리스트로 쪼개기
try:
score_list.append(float(score)) # 첫째 줄 제외. 숫자만 scores 리스트에 추가
except:
continue
result_f.close()
score_list.sort() # 리스트를 크기순으로 정렬(오름차순)
score_list.reverse() # 리스트의 항목들의 순서 뒤집기
print("The top scores were:")
print(score_list[0]) # 0번 색인값 = 1등 점수
print(score_list[1]) # 1번 색인값 = 2등 점수
print(score_list[2]) # 2번 색인값 = 3등 점수
위 코드의 4번 줄에 사용된 line.split()
이 선수이름과 점수를 쪼개는 과정이다.
아래 코드는 위 코드를 좀 더 세련되게 구현한 것이다.
아래 코드의 4번 줄 내용은 split()
메소드를 이용하여 선수 이름과 점수로 쪼개진
각각의 값을 갖는 변수를 동시에 선언하고 있다.
(주의: split()
의 결과로 길이가 2인 리스트를 얻는다는 것을 미리 예상하였음에 주의하라.)
(name, score) = line.split()
위와 같이 하면 다음 처럼 한 것과 동일한 일을 하게 된다.
name = line.split()[0]
score = line.split()[1]
In [11]:
result_f = open("data/scores_list.txt")
score_list = []
for line in result_f:
(name, score) = line.split()
try:
score_list.append(float(score))
except:
continue
result_f.close()
score_list.sort(reverse=True) # 리스트를 내림차순으로 정렬
print("The top scores were:")
print(score_list[0])
print(score_list[1])
print(score_list[2])
앞서 살펴본 코드를 함수로 추상화하면 원하는 등수의 점수를 함수호출로 간단하게 확인할 수 있다.
주의: 함수의 정의화 기초적인 활용법에 대한 자세한 설명은 여기를 참조한다.
In [12]:
def ranking(rank): # 원하는 등수를 인자로 사용
result_f = open("data/scores_list.txt")
score_list = []
for line in result_f:
(name, score) = line.split()
try:
score_list.append(float(score))
except:
continue
result_f.close()
score_list.sort(reverse=True)
return score_list[rank-1] # 원하는 등수의 점수 리턴
이제 1, 2, 3등 점수를 가볍게 확인 할 수 있다.
In [13]:
print(ranking(1), ranking(2), ranking(3))
In [14]:
empty_list = []
빈 리스트의 길이는 0이다.
In [15]:
len(empty_list)
Out[15]:
빈 리스트는 아무 것도 포함하지 않는다. 따라서 0번 인덱스 값도 없다.
In [16]:
empty_list[0]
반면에 아래 리스트는 빈 리스트가 아니다.
In [17]:
a_singleton = [[]]
위 리스트는 빈 리스트를 포함한 리스트이다. 따라서 길이가 1이다.
In [18]:
len(a_singleton)
Out[18]:
포함된 유일한 항목은 빈 리스트이다.
In [19]:
a_singleton[0]
Out[19]:
리스트는 중첩을 허용한다. 아래 리스트는 3중 리스트이다.
In [20]:
a_nested_list = [1, 2, [3, 4], [[5, 6, 7], 8]]
[3, 4]
이다.[5, 6, 7]
과 정수 8로 이루어진 리스트 [[5, 6, 7], 8]
이다.질문: 위 리스트에서 2를 인덱스로 얻는 방법은?
견본답안:
In [21]:
a_nested_list[1]
Out[21]:
질문: [3, 4]
를 인덱스로 얻는 방법은?
견본답안:
In [22]:
a_nested_list[2]
Out[22]:
질문: 3
을 인덱스로 얻는 방법은?
견본답안: 인덱스를 연속해서 적용한다.
In [23]:
a_nested_list[2][0]
Out[23]:
질문: [5, 6, 7]
을 인덱스로 얻는 방법은?
견본답안: 역시 인덱스를 연속해서 적용한다.
In [24]:
a_nested_list[3][0]
Out[24]:
질문: 7
을 인덱스로 얻는 방법은?
견본답안: 역시 인덱스를 연속해서 적용한다.
In [25]:
a_nested_list[3][0][2]
Out[25]:
In [26]:
animals = ['dog', 'cat', 'pig']
문자열에 포함된 문자들의 순서가 중요하듯 리스트에 포함된 항목들의 순서도 절대적으로 중요하다. 문자열과는 달리 리스트는 수정이 가능하다.
예를 들어, append()
메소드는 리스트의 끝에 항목을 하나 추가한다.
In [27]:
animals.append('coq')
animals
Out[27]:
동시에 여러 개의 항목을 추가하고자 할 때는 append()
메소드를 아래처럼 이용하면 된다고 생각하면 안된다.
In [28]:
animals.append(['eagle', 'bear'])
animals
Out[28]:
위에서는 원래의 리스트에 다른 리스트 하나를 마지막 항목으로 추가한 것이다.
그게 아니라 eagle
과 bear
두 개의 항목을 원래의 리스트에 추가하고자 한다면 append()
메소드를 두 번 적용하거나
아니면 extend()
메소드를 사용하면 된다.
먼저 앞서 추가한 항목을 제거하자.
In [29]:
animals.remove(['eagle', 'bear'])
animals
Out[29]:
extend()
메소드의 활용은 다음과 같다.
In [30]:
animals.extend(['eagle', 'bear'])
animals
Out[30]:
두 개의 리스트를 덧셈 기호를 이용하여 확장할 수도 있다. 하지만 원래의 리스트를 변경하는 게 아니라 새로운 리스트를 생성한다.
항목 추가 및 제거 이외에도 항목 자체를 변경할 수도 있다.
cat
를 cow
으로 변경해보자.
방법은 간단하게 인덱싱을 사용한다.
In [31]:
animals[1] = 'cow'
animals
Out[31]:
리스트에 포함된 항목의 인덱스를 알고자 한다면 index()
메소드를 이용한다.
In [32]:
animals.index('pig')
Out[32]:
주의: 만약에 'pig'
가 여러 번 포함되어 있으면 index()
메소드는 가장 작은 인덱스를 리턴한다.
In [33]:
animals.append('pig')
animals
Out[33]:
In [34]:
animals.index('pig')
Out[34]:
pop()
메소드는 인자가 없을 경우 맨 끝에 위치한 항목을 삭제하며, 인덱스를 인자로 사용하면 해당 항목을 삭제한다.
In [35]:
animals.pop()
Out[35]:
In [36]:
animals.pop(2)
Out[36]:
animals
에 할당된 리스트가 어떻게 변경되었는지 확인해야 한다.
In [37]:
animals
Out[37]:
특정 인덱스 위치에 항목을 추가할 경우 insert()
메소드를 사용한다.
In [38]:
animals.insert(5, 'leopard')
animals
Out[38]:
주의: 각 메소드의 리턴값에 주의해야 한다.
pop()
: 리스트에서 삭제한 항목을 리턴한다.append()
, remove()
, insert()
등은 기존의 리스트를 변경하지만 리턴값은 None
, 즉 아무 것도 리턴하지 않는다. 주의: pop()
메소드는 인덱스를 사용하거나 아니면 맨 끝 항목만 삭제한다. 인덱스 번호를 모를 경우에 특정 항목을 삭제하고자 한다면 remove()
메소드를 사용한다.
In [39]:
animals.insert(2, 'hamster')
print(animals)
In [40]:
removed_pet = animals.remove('hamster')
print(animals)
주의:
특정 항목이 여러 번 포함되어 있을 경우 remove()
메소드는 맨 왼쪽에 위치한 항목 하나만 삭제한다.
더 삭제하려면 또 사용해야 한다.
remove()
, index()
등은 삭제 또는 찾고자 하는 항목이 없을 경우 오류를 발생시킨다.
In [41]:
animals.remove('hamster')
animals
이외에 del
함수를 이용하여 리스트의 일부 또는 전체를 삭제할 수 있다.
주의: del
함수(메소드 아님)는 매우 주의해서 사용해야 한다. 잘못하면 데이터 자체를 메모리에서 삭제시킬 수 있다.
In [42]:
del animals[-1]
animals
Out[42]:
In [43]:
animals_sample = ['dog']
In [44]:
del animals_sample
In [45]:
animals_sample
reverse()
메소드는 리스트의 순서를 뒤집는다.
In [46]:
print('기존 동물 리스트: ', animals)
animals.reverse()
print('뒤집어진 동물 리스트: ', animals)
sort()
메소드를 이용하여 리스트의 항목들을 정렬할 수 있다.
In [47]:
print('기존 동물 리스트', animals)
animals.sort()
print('정렬된 동물 리스트', animals)
주의:
sort()
와 reverse()
메소드는 원래의 리스트 자체를 변경한다. sorted()
또는 reversed()
함수(메소드 아님)를 사용한다.
In [48]:
animals.append('horse')
print(animals)
print(sorted(animals))
print(animals)
reversed()
함수에 대해서는 자세히 알아보지 않는다.