데이터 파일 읽고 쓰기

특정 파일을 열어 저장된 데이터를 읽거나 특정 데이터를 특정 파일에 저장해야 하는 일이 매우 빈번하게 발생한다.

  • 파일을 열어 저장된 데이터 읽기
  • 읽은 데이터 다루기: 계산, 필터링 등등
  • 다룬 결과를 특정 파일에 저장하기

상황 설정: 마트에서 장보기

마트에서 장을 보기 위해 상품 목록을 미리 작성하여 가격을 확인한다.

품목   개수   단가
-----------------
빵     1개   1.39
토마토  6개   0.26
우유    3개   1.45

이제 장을 보기 위해 얼마가 필요한지 계산을 해야 한다.

보통은 계산기를 이용하여 품목별 개수와 단가를 곱해 합하면 된다. 그렇다면 텍스트 파일로 저장된 장보기 메모장을 입력하면 총비용이 얼마인지 계산해주는 함수를 짤 수는 없을까?

준비사항

장보기 목록 메모장을 입력하면 총비용을 계산해주는 함수를 예를 들어 total_expense라 하자. total_expense 함수를 코딩하기 위해 필요한 사항은 아래와 같다.

  • 메모내용을 특정 파일로 저장할 수 있어야 한다.
  • 파일을 열어서 내용을 확인할 수 있어야 한다.
  • 확인된 내용에서 원하는 데이터를 추출해서 활용할 수 있어야 한다.

파일 생성, 내용 추가 및 내용 읽기

예를 들어 test.txt 라는 파일명을 가진 텍스트파일을 생성하고자 할 때 다음과 같이 한다.

주의: 현재 파이썬이 실행되는 폴더에 test.txt 파일이 없음을 확인하고 시작하도록 한다. 예를 들어 ipython에서는 터미널 명령어인 ls를 실행하면 현재 폴더에 들어 있는 파일들을 확인할 수 있다. 이미 있다면 파일을 옮기거나 파일명을 변경하도록 한다.

주의:

터미널 명령어는 파이썬 함수가 아니라, 윈도우 커맨드 창이나, 리눅스 또는 맥용 셸 창에서 사용되는 명령어이다. python은 기본적으로 터미널 명령어를 지원하지 않지만 ipython과 spyder는 지원한다.


In [1]:
ls


NB-01-Introduction.ipynb
NB-02-Functions-and-Modules.ipynb
NB-03-Python-Coding-Style.ipynb
NB-04-If-Else-Conditionals.ipynb
NB-05-Sequences.ipynb
NB-06-Loops.ipynb
NB-07-More_About_Variables.ipynb
NB-08-More_About_Functions.ipynb
NB-09-Exceptions-Printing_techniques.ipynb
NB-09-Reading_Writing_Data_Files.ipynb
html/
test.txt

In [2]:
f = open('test.txt', 'w')
  • open 함수는 지정된 파일명을 가진 파일을 생성하고 파일의 위치를 리턴한다.
  • 'w' 인자는 쓰기 전용으로 파일을 생성한다는 의미이며 모드(mode)라 부른다.
  • 이미 test.txt 파일이 존재하면 기존의 내용을 삭제하고 빈 파일로 만들어 파일을 연다.
  • 모드에는 쓰기 모드 'w', 읽기 모드 'r', 추가 모드 'a' 세 종류가 있다.
  • 생성된 객체는 file 자료형이다.

In [3]:
type(f)


Out[3]:
file

file 클래스에는 파일의 내용을 어떻게 읽을지, 다룰지 등에 대한 많은 메소드가 포함되어 있다. 여기서는 write, readlines, readline, read, close, seek 메소도의 활용법을 살펴본다.


In [4]:
dir(f)


Out[4]:
['__class__',
 '__delattr__',
 '__doc__',
 '__enter__',
 '__exit__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__iter__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 'close',
 'closed',
 'encoding',
 'errors',
 'fileno',
 'flush',
 'isatty',
 'mode',
 'name',
 'newlines',
 'next',
 'read',
 'readinto',
 'readline',
 'readlines',
 'seek',
 'softspace',
 'tell',
 'truncate',
 'write',
 'writelines',
 'xreadlines']

write 메소드

write 메소드는 open 함수를 이용하여 w 모드 또는 a 모드로 열린 파일에 내용을 추가할 때 사용한다.


In [5]:
f.write("first line\nsecond line")

'\n'은 줄바꿈을 의미한다. 즉 Enter 키를 눌러 줄바꾸기를 한 것과 동일한 효과를 갖는다. 따라서 위 코드는 두 줄을 test.txt 파일에 입력하는 것을 나타낸다. 입력 내용은 다음과 같다.

first line
second line

close 메소드

  • close 메소드는 파일 내용을 더 이상 수정하거나 확인할 필요가 없어서 파일을 닫고자 할 때 반드시 사용해야 한다.

In [6]:
f.close()

파일을 닫으면 더 이상 파일 내용을 확인할 수 없다. 예를 들어 열려 있는 파일의 경우 read 메소드를 이용하여 내용을 확인할 수 있어야 하는데 이미 test.txt 파일을 닫았기 때문에 오류가 발생한다.


In [7]:
f.read()


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-7-bacd0e0f09a3> in <module>()
----> 1 f.read()

ValueError: I/O operation on closed file

ipython이 실행중일 경우 터미널 명령어인 cat를 사용하여 파일내용을 확인할 수 있다.


In [8]:
cat test.txt


first line
second line

다시 한 번 ls 명령어를 이용하여 test.txt 파일이 생성되었음을 확인할 수 있다.


In [9]:
ls


NB-01-Introduction.ipynb
NB-02-Functions-and-Modules.ipynb
NB-03-Python-Coding-Style.ipynb
NB-04-If-Else-Conditionals.ipynb
NB-05-Sequences.ipynb
NB-06-Loops.ipynb
NB-07-More_About_Variables.ipynb
NB-08-More_About_Functions.ipynb
NB-09-Exceptions-Printing_techniques.ipynb
NB-09-Reading_Writing_Data_Files.ipynb
html/
test.txt

터미널 명령어가 아닌 파이썬 명령어를 이용하여 파일 내용을 확인하고자 한다면 다시 열어야 한다. 이번에는 내용을 추가하기 위해 'a' 모드로 열어본다.


In [10]:
f = open('test.txt', 'a')

'third line' 이란 문자열을 새 줄에 추가한다.


In [11]:
f.write("\nthird line")

In [12]:
f.close()

새 줄이 추가되었음을 확인할 수 있다.


In [13]:
cat test.txt


first line
second line
third line

파일에 저장된 내용 읽어들이기

파일에 저장된 내용을 읽기 위해서는 아래 메소드들을 이용한다.

readlines
readline
read

파일 내용을 읽기 위해서는 먼저 파일을 열어야 한다. 기본적으로 읽기전용 모드는 'r' 모드를 사용한다.


In [14]:
f = open('test.txt', 'r')

readlines 메소드

readlines 메소드는 파일에 저장된 각 줄을 항목으로 갖는 리스트를 리턴한다.


In [15]:
a = f.readlines()

In [16]:
a


Out[16]:
['first line\n', 'second line\n', 'third line']

그런데 readlines 메소드를 다시 실행하면 빈 리스트를 리턴한다.


In [17]:
b = f.readlines()
b


Out[17]:
[]

이유는 오프셋(offset)이라는 책갈피 역할을 하는 기능때문이다.

  • 파일을 새롭게 열면 오프셋은 0으로 초기화된다.
  • readlines 메소드를 한 번 실행하면 오프셋은 파일에 저장된 내용에 해당하는 바이트 값을 갖는다.
  • 현재 text.txt 파일에 저장된 문자열은 총 33개이다. 따라서 readlines를 한 번 실행한 현재 오프셋의 값은 33이다.
  • 오프셋 관련 보다 자세한 설명은 다음 기회로 미룬다. 현재는 readlines를 한 번 이상 실행하면 아무 것도 읽지 못한다는 사실만 기억하면 된다.

파일을 다시 처음부터 읽고 싶다면 우선은 열린 파일을 닫고 다시 열면 된다. 오프셋을 이용한 방식이 있기는 하지만 여기서는 다루지 않는다.


In [18]:
f.close()
f = open('test.txt', 'r')

In [19]:
a1 = f.readlines()
a1


Out[19]:
['first line\n', 'second line\n', 'third line']

In [20]:
f.close()

read 메소드

read 메소드는 readlines 메소드 처럼 파일내용 전체를 읽는다. 하지만 각 줄의 내용으로 쪼개지 않고 전체 내용을 하나의 문자열로 리턴한다.


In [21]:
f = open('test.txt', 'r')
a2 = f.read()
a2


Out[21]:
'first line\nsecond line\nthird line'

readlines 경우와 마찬가지로 read를 한 번 실행하면 오프셋이 파일의 끝을 가리킨다. 따라서 read를 한 번 실행하면 아무것도 읽지 못한다.


In [22]:
a3 = f.read()
a3


Out[22]:
''

In [23]:
f.close()

read 메소드는 readlines 메소드와 사실상 동일한 기능을 수행한다. 문자열 관련 메소드인 split을 사용하기만 하면 된다.

split(str)은 기존 문자열을 str을 기준으로 쪼개어 리스트로 리턴하는 기능을 수행한다. 인자를 넣지 않으면 줄바꾸기('\n') 또는 스페이스(' ')을 기본값으로 해서 줄바꾸기 또는 스페이스를 기준으로 쪼개어 리스트로 리턴한다.


In [24]:
c = a2.split('\n')
c


Out[24]:
['first line', 'second line', 'third line']

readline 메소드

readline 메소드는 파일 내용을 한줄한줄 읽는다.


In [25]:
f = open('test.txt', 'r')
b1 = f.readline()
b1


Out[25]:
'first line\n'

readline을 반복적으로 실행할 때마다 다음 줄이 읽힌다. 오프셋이 줄 단위로 이동하기 때문이다.


In [26]:
b2 = f.readline()
b2


Out[26]:
'second line\n'

In [27]:
b3 = f.readline()
b3


Out[27]:
'third line'

오프셋이 파일 끝에 도달하면 더 이상 읽을 내용이 없어 빈 문자열을 리턴한다.


In [28]:
b4 = f.readline()
b4


Out[28]:
''

파일을 더이상 다루지 않는다면 항상 닫도록 한다.


In [29]:
f.close()

확인된 파일 내용에서 원하는 자료 추출하기

저장된 파일 내용을 확인한 후 원하는 자료만 추출하는 기본적인 방법을 배운다.

예제: 아래처럼 스페이스로 구분된 숫자들로 구성된 문자열이 있다고 가정하자.

"1 2.0 14 3.3 5"

어떻게 하면 스페이스로 구분된 숫자들의 합을 계산할 수 있을까? 즉,

1 + 2.0 + 14 + 3.3 + 5 = ?

우선 해당 문자열을 스페이스를 기준으로 쪼개는 split함수를 활용해야 한다. 이후에는 순수하게 숫자들오 구성된 문자열을 숫자 자료형(int 또는 float)로 형변환해야 한다.

int() 함수와 float() 함수를 활용하면 된다.


In [30]:
num_str = '1 2.0 14 3.3 5'
list_num_str = num_str.split()
sum = 0
for num in list_num_str:
    sum = sum + float(num)
    
print("The total sum is {}!".format(sum))


The total sum is 25.3!

total_expense 함수 정의하기

이제 장볼 때 필요한 비용을 자동을 계산해주는 함수 total_expense 함수를 정의할 준비가 되었다.

먼저 장보기 목록 메모장을 만들어 보자. 새로운 장보기 목록임으로 파일을 새롭게 연다.


In [31]:
f = open('shopping_list', 'w')

장보기 목록을 입력한다.


In [32]:
f.write("bread 1 1.39\ntomatoes 6 0.26\nmilk 3 1.45")

더 이상 구입할 목록이 없으면 메모장을 닫는다.


In [33]:
f.close()

내용을 확인하면 다음과 같다.


In [34]:
cat shopping_list


bread 1 1.39
tomatoes 6 0.26
milk 3 1.45

이제 장을 볼 목록을 확인하기 위해 파일을 다시 연다.

  • readlines 메소드 이용하기

In [35]:
f = open('shopping_list', 'r')
buy_list = f.readlines()
f.close()
buy_list


Out[35]:
['bread 1 1.39\n', 'tomatoes 6 0.26\n', 'milk 3 1.45']
  • readline 메소드 이용하기: 단순하지 않지만 어렵지는 않다. 아래 코드를 이해하도록 노력해보자.

In [36]:
f = open('shopping_list', 'r')
buy_list = []

while True:
    line = f.readline()
    if line != '':
        buy_list.append(line)
    else:
        break

f.close()
buy_list


Out[36]:
['bread 1 1.39\n', 'tomatoes 6 0.26\n', 'milk 3 1.45']
  • read 메소드 이용하기

In [37]:
f = open('shopping_list', 'r')
buy_list = f.read().split('\n')
f.close()
buy_list


Out[37]:
['bread 1 1.39', 'tomatoes 6 0.26', 'milk 3 1.45']

세 가지 방식 중에서 어떤 것도 사용할 수 있지만 지금 경우에 있어서는 read 메소드를 통해 좀 더 깔끔한 값을 얻었다.

이제 각 품목에 대해 필요한 경비를 계산할 수 있다. split으로 각 항목을 쪼갠 후 인덱싱을 이용하기만 하면 된다.

  • 빵값 비용

In [38]:
int(buy_list[0].split()[1]) * float(buy_list[0].split()[2])


Out[38]:
1.39
  • 토마토 비용

In [39]:
int(buy_list[1].split()[1]) * float(buy_list[1].split()[2])


Out[39]:
1.56
  • 우유 비용

In [40]:
int(buy_list[2].split()[1]) * float(buy_list[2].split()[2])


Out[40]:
4.35

빵값, 토마토, 우유 비용을 계산하는 코드를 자세히 살펴보면 buy_list 인덱싱에 사용되는 인덱스 값만 0, 1, 2 순으로 변한 것을 알 수 있다. 따라서 총비용 계산은 for 문을 이용하면 된다.


In [41]:
sum = 0

for item in buy_list:
    d = item.split()
    item_price = int(d[1]) * float(d[2])
    print("The price of {} is ${}.".format(d[0], item_price))
    sum = sum + item_price
    
print("The total expense is ${}.".format(sum))


The price of bread is $1.39.
The price of tomatoes is $1.56.
The price of milk is $4.35.
The total expense is $7.3.

따라서 total_expense 함수를 아래와 같이 코딩할 수 있다.


In [42]:
def total_expense(memo):
    f = open(memo,'r')
    a = f.readlines()

    sum = 0
    for item in a:
        d = item.split()
        item_price = int(d[1]) * float(d[2])
        print("The price of {} is ${}.".format(d[0], item_price))
        sum = sum + item_price
    print("The total expense is ${}.".format(sum))

이제 오늘 장을 보기 위해 필요한 총비용을 쉽게 확인할 수 있다.


In [43]:
total_expense('shopping_list')


The price of bread is $1.39.
The price of tomatoes is $1.56.
The price of milk is $4.35.
The total expense is $7.3.