텍스트 파일 불러오기와 리스트 활용


수정 사항

  • 적절한 연습문제 추가 필요
  • 엑셀파일로 텍스트 파일, csv 파일 등을 열 때와 비교하기 추가하면 좋을 듯
    • 이미지 활용

처리해야 할 데이터 양이 많아지면 파일에 저장한 후에 필요한 경우 재활용해야 한다. 또한 개별 데이터를 따로따로 처리하기 보다는 하나의 데이터로 묶어서 처리할 수 있어야 한다. 많은 데이처를 하나의 데이터로 묶어서 처리하는 다양한 자료형이 제공되며 여기서는 파이썬의 리스트 자료형의 활용을 알아본다.

주요 내용

텍스트 파일의 내용을 읽어드리는 방법과 파이썬에 내장되어 있는 컬렉션 자료형 중의 하나인 리스트(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()                                       # 파일 닫기


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

주의사항

줄 사이에 새로운 줄이 포함된 이유는 파일을 작성하면서 줄바꾸기를 할 때 사용하는 엔터에 의해 줄바꾸기 기호(\n)가 각 줄의 맨 끝에 포함되기 때문이다. 따라서 줄바꾸기를 한 번 더 하는 것을 방지하기 위해서 strip 메소드를 활용하는 것이 좋다.


In [2]:
result_f = open("data/scores_list.txt")

for line in result_f: 
    print(line.strip())              # strip 메소드 활용하기

result_f.close()


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

주의사항

  • strip 메소드를 활용하여 데이터를 보다 깔끔하게 정리하는 것은 좋은 버릇이다.
  • 하지만 반드시 필요한 것은 아닐 수도 있기 때문에 사용여부를 판단해야 한다.
  • 경우에 따라 strip 메소드를 사용해도 되고 그렇지 않아도 된다.

이제 1등 점수를 확인하고자 한다. 이때 이전에 배운 예외처리 기술을 활용해보자.

예제

아래 예제는 없는 파일을 open 함수로 열려고 할 때 발생하는 문제를 처리하는 기술이다.

먼저 없는 파일을 열려고 할 때 오류가 발생함을 확인하자.


In [3]:
file = open('data/no_file.txt')


---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
<ipython-input-3-b7955c7e557c> in <module>()
----> 1 file = open('data/no_file.txt')

FileNotFoundError: [Errno 2] No such file or directory: 'data/no_file.txt'

이런 경우에는 열고자 하는 파일이 존재하지 않는다는 정보를 전달하는 것이 단순히 오류가 발생하면서 실행이 멈추는 것보다 훨씬 유익하다.


In [4]:
try:
    file = open('data/no_file.txt')
except:
    print("열고자 하는 파일이 존재하지 않습니다.")


열고자 하는 파일이 존재하지 않습니다.

1, 2, 3등 점수 확인하기

1등 점수 확인하기

앞서 파일 내용을 확인해 보았듯 각 줄마다 선수이름과 점수가 공백을 사이로 두고 각 줄에 적혀 있다. 따라서 아래와 같이 split 메소드를 활용하여 각 줄을 쪼개어 두 번째 항목을 확인할 수 있다.

주의사항

split 메소드의 기능을 확인해야 한다. 예를 들어 Name Score라는 문자열을 공백을 기준으로 쪼개면 길이가 두 개의 단어로 구성된 리스트가 생성된다.


In [5]:
'Name   Score'.split()


Out[5]:
['Name', 'Score']

파일의 각 줄이 동일한 모양을 갖고 있다는 점에 착안하여 아래와 같이 각줄의 내용 중에서 점수에 해당하는 부분을 아래와 같이 확인할 수 있다.

주의: 리스트의 색인도 문자열의 경우처럼 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()


Score
21.09
20.32
21.81
22.97
23.29
22.09
21.20
22.16

그런데 첫째 줄 내용은 점수를 비교하는 데에 필요없다. 따라서 무시하는 방법을 사용하도록 하자.

특정 줄을 무시하는 방법은 여러 기술이 있지만 여기서는 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, "입니다.")


1등 점수는 23.29 입니다.

2등 점수 확인하기

2등 점수까지 확인하려면 2등 점수를 기억할 변수가 하나 더 필요하며 확인된 점수가 기존의 1등 점수보다 큰지, 2등 점수보다 큰지 여부에 따라 1, 2등 점수를 기억하는 변수의 값들을 업데이트 해야 한다.


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, "입니다.")


1등 점수는 23.29 입니다.
2등 점수는 22.97 입니다.

3등 점수 확인하기

이제 3등 점수까지 확인하려면 코드를 더 많이 수정해야 하며, 더 많은 변수와 조건문을 사용해야 한다.


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등 점수는 23.29 입니다.
2등 점수는 22.97 입니다.
3등 점수는 22.16 입니다.

나쁜 프로그래밍

앞서 1등까지, 2등까지, 3등까지 점수를 확인하는 코드는 각자 다르며, 점처 길어지고 복잡해졌다. 코드를 이런 식으로 구현하면 안된다.

무엇보다도 원하는 등수에 따라 코드 자체가 수정되어야 하는 방식으로 프로그래밍을 하면 절대 안된다. 그럼 어떻게 할까?

앞선 코드의 근본적인 문제점은 각 선수의 점수를 따라따로 관리하기 때문에 발생한다. 따라서 선수의 점수를 모아서 한꺼번에 처리하는 기술이 요구된다. 여기서는 리스트 자료형을 활용하여 원하는 등수와 선수의 수에 상관없이 동일한 코드로 원하는 결과를 리턴하는 프로그램을 구현하고자 한다.

리스트 활용

몇 등 점수를 알아내야 하는가와 상관없이 모든 질문에 답을 하는 하나의 프로그램을 리스트를 활용하여 구현하고자 하며, 아이디어는 다음과 같다.

  • 서핑 대회 참가선수들의 점수만을 따로 모아 놓은 리스트를 생성한다.
  • 리스트의 항목들을 숫자크기 역순으로 정렬(sorting)한다.
  • 역순, 즉 내림차순으로 정렬된 리스트의 색인을 이용하여 원하는 등수의 점수를 확인한다.

기본 아이디어

질문: 그렇다면 점수만 뽑아서 모은 다음에 점수들을 순서대로 나열하는 방법이 있으면 좋지 않을까?

답: 매우 그렇다.

방법: split(), append() 메소드를 아래와 같이 for 문과 함께 활용하면 됨.


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등 점수


The top scores were:
23.29
22.97
22.16

주의사항

위 코드의 4번 줄에 사용된 line.split()이 선수이름과 점수를 쪼개는 과정이다.

아래 코드는 위 코드를 좀 더 세련되게 구현한 것이다. 아래 코드의 4번 줄 내용은 split() 메소드를 이용하여 선수 이름과 점수로 쪼개진 각각의 값을 갖는 변수를 동시에 선언하고 있다. (주의: split()의 결과로 길이가 2인 리스트를 얻는다는 것을 미리 예상하였음에 주의하라.)

(name, score) = line.split()

위와 같이 하면 다음 처럼 한 것과 동일한 일을 하게 된다.

name = line.split()[0]
score = line.split()[1]

주의사항

아래 두 줄의 코드는 리스트를 내림차순으로 정렬한다.

score_list.sort()
score_list.reverse()

위 두 줄의 코드를 아래와 같이 한 줄로 구현할 수 있다.

score_list.sort(reverse=True)

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])


The top scores were:
23.29
22.97
22.16

함수 활용

앞서 살펴본 코드를 함수로 추상화하면 원하는 등수의 점수를 함수호출로 간단하게 확인할 수 있다.

주의: 함수의 정의화 기초적인 활용법에 대한 자세한 설명은 여기를 참조한다.


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))


23.29 22.97 22.16

연습문제

연습

빈 리스트는 아무 것도 포함하지 않는 리스트를 의미한다.

In [14]:
empty_list = []

빈 리스트의 길이는 0이다.


In [15]:
len(empty_list)


Out[15]:
0

빈 리스트는 아무 것도 포함하지 않는다. 따라서 0번 인덱스 값도 없다.


In [16]:
empty_list[0]


---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-16-29438dbc2832> in <module>()
----> 1 empty_list[0]

IndexError: list index out of range

반면에 아래 리스트는 빈 리스트가 아니다.


In [17]:
a_singleton = [[]]

위 리스트는 빈 리스트를 포함한 리스트이다. 따라서 길이가 1이다.


In [18]:
len(a_singleton)


Out[18]:
1

포함된 유일한 항목은 빈 리스트이다.


In [19]:
a_singleton[0]


Out[19]:
[]

연습

리스트는 중첩을 허용한다. 아래 리스트는 3중 리스트이다.


In [20]:
a_nested_list = [1, 2, [3, 4], [[5, 6, 7], 8]]
  • 첫째, 둘째 항목은 정수인 1과 2이다.
  • 셋쩨 항목은 3과 4로 이루어진 길이가 2인 리스트 [3, 4]이다.
  • 넷째 항목은 리스트 [5, 6, 7]과 정수 8로 이루어진 리스트 [[5, 6, 7], 8]이다.

질문: 위 리스트에서 2를 인덱스로 얻는 방법은?

견본답안:


In [21]:
a_nested_list[1]


Out[21]:
2

질문: [3, 4]를 인덱스로 얻는 방법은?

견본답안:


In [22]:
a_nested_list[2]


Out[22]:
[3, 4]

질문: 3을 인덱스로 얻는 방법은?

견본답안: 인덱스를 연속해서 적용한다.


In [23]:
a_nested_list[2][0]


Out[23]:
3

질문: [5, 6, 7]을 인덱스로 얻는 방법은?

견본답안: 역시 인덱스를 연속해서 적용한다.


In [24]:
a_nested_list[3][0]


Out[24]:
[5, 6, 7]

질문: 7을 인덱스로 얻는 방법은?

견본답안: 역시 인덱스를 연속해서 적용한다.


In [25]:
a_nested_list[3][0][2]


Out[25]:
7

연습: 리스트의 중요 메소드 활용


In [26]:
animals = ['dog', 'cat', 'pig']

문자열에 포함된 문자들의 순서가 중요하듯 리스트에 포함된 항목들의 순서도 절대적으로 중요하다. 문자열과는 달리 리스트는 수정이 가능하다.

예를 들어, append() 메소드는 리스트의 끝에 항목을 하나 추가한다.


In [27]:
animals.append('coq')
animals


Out[27]:
['dog', 'cat', 'pig', 'coq']

동시에 여러 개의 항목을 추가하고자 할 때는 append() 메소드를 아래처럼 이용하면 된다고 생각하면 안된다.


In [28]:
animals.append(['eagle', 'bear'])
animals


Out[28]:
['dog', 'cat', 'pig', 'coq', ['eagle', 'bear']]

위에서는 원래의 리스트에 다른 리스트 하나를 마지막 항목으로 추가한 것이다.

그게 아니라 eaglebear 두 개의 항목을 원래의 리스트에 추가하고자 한다면 append() 메소드를 두 번 적용하거나 아니면 extend() 메소드를 사용하면 된다.

먼저 앞서 추가한 항목을 제거하자.


In [29]:
animals.remove(['eagle', 'bear'])
animals


Out[29]:
['dog', 'cat', 'pig', 'coq']

extend() 메소드의 활용은 다음과 같다.


In [30]:
animals.extend(['eagle', 'bear'])
animals


Out[30]:
['dog', 'cat', 'pig', 'coq', 'eagle', 'bear']

두 개의 리스트를 덧셈 기호를 이용하여 확장할 수도 있다. 하지만 원래의 리스트를 변경하는 게 아니라 새로운 리스트를 생성한다.

항목 추가 및 제거 이외에도 항목 자체를 변경할 수도 있다.

catcow으로 변경해보자. 방법은 간단하게 인덱싱을 사용한다.


In [31]:
animals[1] = 'cow'
animals


Out[31]:
['dog', 'cow', 'pig', 'coq', 'eagle', 'bear']

리스트에 포함된 항목의 인덱스를 알고자 한다면 index() 메소드를 이용한다.


In [32]:
animals.index('pig')


Out[32]:
2

주의: 만약에 'pig'가 여러 번 포함되어 있으면 index() 메소드는 가장 작은 인덱스를 리턴한다.


In [33]:
animals.append('pig')
animals


Out[33]:
['dog', 'cow', 'pig', 'coq', 'eagle', 'bear', 'pig']

In [34]:
animals.index('pig')


Out[34]:
2

pop() 메소드는 인자가 없을 경우 맨 끝에 위치한 항목을 삭제하며, 인덱스를 인자로 사용하면 해당 항목을 삭제한다.


In [35]:
animals.pop()


Out[35]:
'pig'

In [36]:
animals.pop(2)


Out[36]:
'pig'

animals에 할당된 리스트가 어떻게 변경되었는지 확인해야 한다.


In [37]:
animals


Out[37]:
['dog', 'cow', 'coq', 'eagle', 'bear']

특정 인덱스 위치에 항목을 추가할 경우 insert() 메소드를 사용한다.


In [38]:
animals.insert(5, 'leopard')
animals


Out[38]:
['dog', 'cow', 'coq', 'eagle', 'bear', 'leopard']

주의: 각 메소드의 리턴값에 주의해야 한다.

  • pop(): 리스트에서 삭제한 항목을 리턴한다.
  • append(), remove(), insert() 등은 기존의 리스트를 변경하지만 리턴값은 None, 즉 아무 것도 리턴하지 않는다.

주의: pop() 메소드는 인덱스를 사용하거나 아니면 맨 끝 항목만 삭제한다. 인덱스 번호를 모를 경우에 특정 항목을 삭제하고자 한다면 remove() 메소드를 사용한다.


In [39]:
animals.insert(2, 'hamster')
print(animals)


['dog', 'cow', 'hamster', 'coq', 'eagle', 'bear', 'leopard']

In [40]:
removed_pet = animals.remove('hamster')
print(animals)


['dog', 'cow', 'coq', 'eagle', 'bear', 'leopard']

주의:

  • 특정 항목이 여러 번 포함되어 있을 경우 remove() 메소드는 맨 왼쪽에 위치한 항목 하나만 삭제한다. 더 삭제하려면 또 사용해야 한다.

  • remove(), index() 등은 삭제 또는 찾고자 하는 항목이 없을 경우 오류를 발생시킨다.


In [41]:
animals.remove('hamster')
animals


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-41-23a85b0eacf2> in <module>()
----> 1 animals.remove('hamster')
      2 animals

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

이외에 del 함수를 이용하여 리스트의 일부 또는 전체를 삭제할 수 있다.

주의: del 함수(메소드 아님)는 매우 주의해서 사용해야 한다. 잘못하면 데이터 자체를 메모리에서 삭제시킬 수 있다.


In [42]:
del animals[-1]
animals


Out[42]:
['dog', 'cow', 'coq', 'eagle', 'bear']

In [43]:
animals_sample = ['dog']

In [44]:
del animals_sample

In [45]:
animals_sample


---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-45-2fd0a356f881> in <module>()
----> 1 animals_sample

NameError: name 'animals_sample' is not defined

reverse() 메소드는 리스트의 순서를 뒤집는다.


In [46]:
print('기존 동물 리스트: ', animals)
animals.reverse()
print('뒤집어진 동물 리스트: ', animals)


기존 동물 리스트:  ['dog', 'cow', 'coq', 'eagle', 'bear']
뒤집어진 동물 리스트:  ['bear', 'eagle', 'coq', 'cow', 'dog']

sort() 메소드를 이용하여 리스트의 항목들을 정렬할 수 있다.

  • 숫자의 경우는 크기 순서대로.
  • 문자열의 경우는 사전식으로.

In [47]:
print('기존 동물 리스트', animals)
animals.sort()
print('정렬된 동물 리스트', animals)


기존 동물 리스트 ['bear', 'eagle', 'coq', 'cow', 'dog']
정렬된 동물 리스트 ['bear', 'coq', 'cow', 'dog', 'eagle']

주의:

  • sort()reverse() 메소드는 원래의 리스트 자체를 변경한다.
  • 원래의 리스트를 건드리지 않으면서 정렬된 또는 뒤집어진 리스트를 생성하고자 한다면 sorted() 또는 reversed() 함수(메소드 아님)를 사용한다.

In [48]:
animals.append('horse')
print(animals)
print(sorted(animals))
print(animals)


['bear', 'coq', 'cow', 'dog', 'eagle', 'horse']
['bear', 'coq', 'cow', 'dog', 'eagle', 'horse']
['bear', 'coq', 'cow', 'dog', 'eagle', 'horse']

reversed() 함수에 대해서는 자세히 알아보지 않는다.