파일 입출력(공학자를 위한 Python)
공학자를 위한 Python 책을 참고하여 작성하였습니다.
- 책 제목: 공학자를 위한 Python
- 지은이: 조정래
- url: https://wikidocs.net/book/1704
1. 파일 입출력
Python의 파일 입출력은 C/C++의 stdio.h에 정의된 방식과 유사하다. 다음은 텍스트 파일을 읽어 strlist라는 str의 list에 저장한 후 출력한 예이다.
f = open('mytext.txt', 'rt') # f = open('mytext.txt')와 동일
strlist = f.readlines()
f.close()
print(strlist)
f = open(file, mode = ‘r’, encoding = None, …)으로 파일 객체를 만들고 이후 파일 객체를 대상으로 멤버 함수를 호출하는 방식으로 파일을 다룬다.
- mode는 C의 fopen()처럼 읽기/쓰기/추가(각각 ‘r’, ‘w’, ‘a’), 텍스트 파일/이진 파일(각각 ‘t’, ‘b’) 등과 같이 파일 오픈 모드를 문자열로 지정한다. 디폴트는 읽기(‘r’)와 텍스트 파일(‘t’)이다.
- 텍스트 파일로 파일을 열면 파일에 입출력은 유니코드 문자열인 str 객체를 통해 수행하게 된다. 이 때 텍스트 인코딩(encoding)과 디코딩(decoding)이 발생하게 된다.
- 이진 파일로 파일을 열면 파일에 입출력은 bytes 객체를 통해 수행하게 된다. bytes 객체는 단순히 1 바이트 단위의 배열(또는 ASCII 문자열)로, 파일에 저장되어 있는 데이터를 바이트 단위로 변환없이 입출력한다는 것을 의미한다.
- encoding은 텍스트 파일로 파일을 열 때만 의미가 있으며, 디폴트인 None은 시스템의 디폴트 인코딩(Windows의 경우 cp949)을 사용한다는 것을 의미한다.
1-1. 파일 입출력 관련 주요 함수
이진 파일은 io.BufferedReader 또는 io.BufferedWriter라는 클래스가 생성되며, 텍스트 파일은 it.TextIOWrapper라는 클래스 객체가 생성되는 데 모두 io.IOBase라는 클래스에서 상속받고 있다. 파일을 다룰 때 복잡한 클래스 상속에 대한 지식은 필요 없으며, 단지 File Object에서 사용되는 주요한 함수를 기억하면 된다.
- f = open(file, mode = ‘r’, encoding = None, …): 파일 열기
- f.name: 파일 이름
- f.mode: 파일 열기 모드 조회
- f.encoding: 현재 사용되고 있는 인코딩(텍스트 파일에서만 유효)
- f.close(): 닫기. 강제로 사용이 끝나 파일을 닫는다. 이를 호출하지 않아도 파일 객체가 파괴될 때 파일을 닫는다.
- f.flush(): 버퍼 즉시 지우기
- f.closed: 파일이 닫혔는지 여부를 True, False로 리턴
- f.readable(), f.writable(): 읽거나 쓸 수 있는지 True, False로 리턴
- f.seek(offset, whence = SEEK_SET), f.tell(): 파일 포인터 위치 설정. 기준 위치 whence에 SEEK_SET(0), SEEK_CUR(1), SEEK_END(2) 지정 가능. offset이 음수이면 기준 위치로부터 역방향으로 위치 지정
- f.tell(): 파일 포인터 위치 조회
- f.read([size]), f.readline([size]), f.readlines([sizehint]): 읽기 함수
- f.write(str), f.writelines(sequenc): 쓰기 함수
1-2. with 구문의 사용
사용이 끝난 파일 객체는 f.close()로 닫고 재사용 가능하다. 다른 방법은 with 구문을 이용하여 구문 내에서만 유효한 파일 객체를 사용하는 것이다.
with open('test.txt') as f:
strlist = f.readlines()
process strlist...
위에서 f.close()로 명시적으로 파일을 닫지 않아도 with 블록이 끝날 때 파일이 자동으로 닫히게 된다.
1-3. 예외 처리
주어진 파일명의 파일이 존재하지 않거나 인코딩 오류가 발생하는 등과 같은 여러 예외가 발생할 수 있다. 이들 오류는 모두 IOError로 처리할 수 있다. 다음은 예외 처리를 포함한 간단한 코드 형태를 나타낸 것이다.
try:
f = open(filename, "rb")
try:
data = f.read()
finally:
f.close()
except IOError:
print("IOError occured")
2. 텍스트 파일
2-1. 텍스트 파일 인코딩
텍스트 파일은 항상 2바이트로 문자를 표시하는 배열, 즉 유니코드 문자열인 str로 파일을 읽고 쓰게 된다. 문제는 실제 파일은 바이트 단위를 저장한다는 점이다. 예를 들어 다음과 같은 두 개의 문자열이 존재한다고 가정해보자.
eng = 'Hello'
kor = '안녕하세요'
위에서 eng와 kor은 모두 5개의 문자로 구성된 유니코드 문자열이므로 메모리 상에서는 한 문자당 2바이트의 메모리가 필요하므로 10바이트를 사용한다.(실제로는 str 객체의 정보가 포함되어 있으므로 더 많은 메모리 소요). 파일로 출력할 때는 어떤 방식으로 인코딩해야 할 지 결정해야 한다. 주로 사용하는 인코딩 방식은 ‘CP949’와 ‘UTF-8’가 있다.
- ‘CP949’: Windows의 디폴트 인코딩 방식, 영문에는 아스키 코드에 따라 1바이트로, 한글에는 2바이트를 사용해서 인코딩. ‘EUC-KR’를 다시 확장한 인코딩 방식이며, 에디터에 따라 ‘ANSI’, ‘EUC-KR’ 등으로 표기됨.
- ‘UTF-8’: 유니코드 인코딩 방식의 하나. Python 3에서 .py 소스 파일에 대한 디폴트 인코딩 방식. 영문에는 아스키 코드에 따라 1바이트로, 한글은 초성, 중성, 종성을 각각 1 바이트로 저장(정확히는 ANSI 문자셋을 제외하면 2~4바이트로 표현). 다른 유니코드 방식인 ‘UTF-16’에 비해 영문이 많을 경우 파일 용량을 줄일 수 있으며, ‘ANSI’와의 하위호환성이 보장되기 때문에 가장 많이 사용됨.
2-2. 인코딩 확인법
이미 존재하는 텍스트 파일의 인코딩은 notepad++ 등의 에디터에서 가능하고, 반환 역시 가능하다. 가능하면 UTF-8을 사용하는 것이 좋다. 대부분의 에디터에서 최초 저장하는 텍스트 파일은 UTF-8을 사용한다. 만약 이미 존재하는 파일이고 인코딩 방식을 에디터가 인식했다면 그 인코딩 방식으로 변경된 사항을 저장한다. 문제는 Visual Studio인데 다음과 같은 방식이 적용된다.
- 새로 생성한 파일(처음으로 저장하는 파일)인 경우 CP949를 사용한다.
- 이미 존재하는 파일은 자동으로 인코딩을 감지하고 그 인코딩으로 저장한다.
- 따라서 Visual Studio로 Python 코드에 한글을 쓸 때 주의해야 한다.
2-3. 텍스트 파일 읽기
텍스트 파일은 rt 모드로 열어서 작업을 수행한다. 문서에 한글이 포함되어 있다면 문서의 인코딩 방식을 미리 알고 있어야 한다.(보통 사용하는 ‘CP949’와 ‘UTF-8’에서 영문은 구분할 필요가 없으나 한글이 포함되면 미리 알아야 함)
보통 텍스트 파일은 read(), readline(), readlines(), write() 등의 메서드로 읽고 쓰기를 수행한다.
# f.read()로 전체 파일을 읽어오기
f = open('SomeWord.txt', 'rt', encoding = 'utf-8') # Open file with 'UTF-8' 인코딩
text = f.read()
f.close()
lines = f.split('\n') # 라인 단위로 분해
# f.readline()으로 라인 단위 작업
f = open('SomeWord.txt', 'rt', encoding = 'utf-8') # Open file with 'UTF-8' 인코딩
while True:
line = f.readline() # read line-by-line using f.readline()
if not line: break
processing line ....
f.close() # Close file
# f.readlines()으로 모든 라인을 일괄 읽어와 작업
f = open('SomeWord.txt', 'rt', encoding = 'utf-8') # Open file with 'UTF-8' 인코딩
lines = f.readlines() # read all lines
f.close() # Close file
... processing lines
# 텍스트 출력
f = open('SomeWordOutput.txt', 'wt', encoding = 'UTF-8')
for line in lines:
f.write(line) # Use f.write(line) instead of f.writeline(line)
f.close() # Close file
2-4. CSV 파일 다루기
CSV 파일인 경우 Python 빌트인 패키지인 csv 패키지를 사용하면 편리하다.
filename = './text.csv';
f = open(filename, 'rt')
reader = csv.reader(f, delimiter = ',')
next(reader) # 헤더라인 skip... 필요한 경우 사용한다.
for line in reader:
print(line)
f.close()
위에서 line은 line = [‘first’, ‘second’, ‘third’] 등과 같이 문자열으로 분리해서 읽히게 된다. 만약 숫자만을 포함한 CSV 파일이라면 다음과 같이 float()를 통해 변환하면 된다.
filename = './text.csv';
f = open(filename, 'rt')
reader = csv.reader(f, delimiter = ',')
next(reader)
for line in reader:
for word in line:
print(float(word))
f.close()
2-5. 숫자로 이루어진 텍스트 파일
숫자만 있는 경우(엄밀하게 그럴 필요는 없지만), numpy를 이용하는 것이 편리하다. 예를 들어 숫자로 이루어진 테이블 형태의 파일을 쉽게 읽으려면 NumPy의 loadtxt()나 savetxt()를 사용하면 편리하다. 아래는 공백문자로 구분된 테이블 형태의 숫자 파일을 읽어들이고 저장한다.
import numpy as np
data = np.loadtxt('ttt.txt')
np.savetxt('ttt.out', data, "# test")
만약 header가 있는 경우 skiprows 인자를 지정할 수 있고, 디폴트로 공백문자인 구분자를 변경하려면 delimiter 인자를 지정한다. 예를 들어 첫번째 줄을 무시하고, 콤마로 구분된 파일을 읽을 때는 다음과 같다.
data = np.loadtxt(fname, skiprows = 1, delimiter = ',')
2-6. 비정형 데이터 파일 읽기
앞에서 소개한 load_txt(), load_csv() 등의 함수를 숫자 데이터가 행렬 형태여야 한다. 다음과 같이 정확하게 행렬 형태가 아닌 경우에는 직접 파일을 읽어야 한다.
def loadData(file, skiprow = 0):
f = open(file, 'rt', encoding = 'utf-8')
lines = f.readlines()
f.close()
data = []
for i in range(skiprow, len(lines)):
temp = lines[i].split()
for t in temp:
data.append(float(t))
return data
3. 바이너리 파일
이진 파일로 파일을 열면 텍스트 파일처럼 인코딩 작업이나 줄바꿈 문제에 대한 변환이 없이 항상 1바이트 크기의 배열인 bytes 객체로 읽고 쓰기를 수행한다. 다음은 이진 파일로 열어 파일을 복사한 예이다.
3-1. 파일 복사
f = open('ABBA.mp3', 'rb')
data = f.read() # bytes
f.close()
f = open('ABBA-copy.mp3', 'wb')
f.write(data)
f.close()
3-2. MP3 파일 곡명 확인
Working with FIle Objests에 소개된 코드를 발췌한 것으로 mp3 파일에서 곡명을 확인한 예이다. mp3 파일은 파일 마지막의 128바이트에 곡명, 장르 등등 여러 정보를 저장한다.
f = open('ABBA.mp3', 'rb')
f.seek(-128, 2) # 끝에서 128 바이트로 위치 이동
tagdata = f.read(128)
title = tagdata[3:33].decode()
title
f.close()
댓글남기기