[Python] IO (파일 입출력)


Study/Python  2019. 12. 23. 09:00

안녕하세요. 명월입니다.


이 글은 Python에서 IO(파일 읽기, 쓰기)를 다루는 방법에 대한 글입니다.


프로그램을 공부하는 데 있어서 제가 생각하는 중요한 리소스가 IO(파일 읽기, 쓰기)와 Socket 통신인 것 같습니다. 그 외에 리소스를 다루는 건 이 두 가지를 기본으로 확장된 개념으로 사용하지 않을까 싶네요.

특히 저의 경우는 주로 사용하는 언어는 Java, C#입니다만 C#에서 사용할 데이터를 만들 때는 스크립트 언어를 사용하는데 특히 Python을 자주 사용합니다. node.js(javascript)도 로컬에서 사용할 만한 스크립트 언어이기는 합니다만, Python이 손에 익어서 그런지 Python이 편합니다.


서버로 운영하는 언어로는 Python을 사용하기에는 아무래도 스크립트 언어이다 보니 성능에 많은 의문점이 생깁니다. Python도 빠르다고 하지만 제 개념으로는 컴파일 언어가 그래도 가장 빠르고 메모리가 적게 들지 않을까 싶습니다.

그래서 저는 반대로 로컬에서 간단하게 데이터를 만들어 내야 한다거나(json 나 xml데이터), 서버를 운영하는 shell 스크립트 관리, 웹서버나 응용 서버의 환경 설정 파일등을 쉽게 관리하기 위해서 Python을 자주 사용합니다.


그런 입장에서 이 IO는 Python에서 가장 중요하지 않을까 싶습니다.


파일은 기본적으로 입출력(Input / Ouput)이 기본입니다. 입력은 파일로부터 데이터를 읽어오는 것이고 출력은 데이터를 파일로 작성하는 것입니다.

# 파일 커넥션을 open한다.
handle = open("text.txt", "w");
# handle에 write 함수를 사용해서 hello world라는 문구를 입력하였다.
handle.write("hello world");
# 파일은 리소스 타입이기 때문에 사용 후에는 반드시 커넥션을 닫아야 한다.
# 커넥션을 닫지 않으면 다른 곳에서 해당 파일을 사용할 수 없다.
handle.close();

위에서 open 함수를 사용하여 파일 리소스를 가져옵니다. open의 파라미터는 사용할 파일명과 파일 옵션입니다.

파일 옵션의 타입은 아래의 표와 같습니다.

타입 설명
w 파일 쓰기 (파일이 없으면 생성하고, 존재하면 기존 파일은 지우고 새로 작성한다.)
a 파일 쓰기 (파일이 없으면 생성하고, 있으면 기존 내용에서 데이터를 추가한다.)
모드 설명
t 일반 텍스트 파일(생략가능)
b 바이너리 타입(텍스트 글자이외의 데이터를 만들 때 사용한다.)

위 타입과 모드에 따라서 w는 파일 쓰기를 설정하였습니다. 사실은 wt로 작성을 해야 하는데 t는 생략이 가능하니 w하나로 설정이 가능합니다.

파일 명의 경우는 모든 패스를 사용할 경우 절대 경로로 파일을 생성할 수 있고(d:\Python\blog\text.txt) 상대 경로로 생성할 수도 있습니다.(./text.txt 혹은 text.txt)

작성이 되었네요. 그럼 이번에는 해당 파일을 읽어 오도록 하겠습니다.

# 파일 커넥션을 open한다. 옵션을 r로 설정했다.
handle = open("text.txt", "r");
# read함수는 파일 전체를 한번에 읽어드린다.
data =handle.read();
# 커넥션을 닫는다.
handle.close();
print(data);

위에서 파일로부터 데이터를 읽어와서 화면 출력을 했는데 잘 되네요.

기본적인 파일 입출력은 위와 같은 형태입니다. Python에서는 파일을 읽고 쓰는데 또 커넥션을 열고 닫는데 좀 더 편리하게 사용하는 함수가 있습니다.

# 파일에 글을 쓰는 함수를 만들었다.
def write_text(msg):
  # with는 자동으로 커넥션은 close해준다. 타입을 a를 사용했으니 호출 될 때마다 내용이 추가가 됩니다.
  with open("text.txt", "a") as handle:
    # print함수를 사용해서 file 파라미터에 파일 리소스를 넣고, end는 print가 종료가 될때 가장 마지막에 자동으로 붙는 문자열입니다.참고로 "\n"는 줄바꿈입니다.
    # end를 설정하지 않으면 print는 기본적으로 \n를 포함합니다.
    print(msg,file=handle,end="\n");

# 함수 호출하기.
write_text("Good morning!!");
write_text("I will write a file");
write_text("Thank you");

파일 커넥션을 open, close로 제어하는 것도 괜찮지만 중간에 에러가 발생하게 되면 close를 호출하지 못하게 됩니다. try~finally를 사용해서 커넥션을 제어할 수 있지만 좀 더 편한 키워드인 with as를 써서 커넥션을 관리할 수 있습니다.

print 함수를 사용해서 파일에 글을 남길 수도 있습니다. 「handle.write(msg + "\n");」의 형태를 print로 사용한 것입니다.

with open("text.txt", "r") as handle:
  # 한 줄씩 읽기 위해 while 루프를 사용했다.
  while True:
    # 한 줄씩 읽어온다.
    line = handle.readline();
    # line에 값이 없다면 루프는 멈춘다.
    if not line:
      break;
    # 화면 출력
    print(line);

결과에 보면 두 줄씩 내려온 것을 확인할 수 있습니다. 이유는 데이터에 있는 줄바꿈이 있기 때문에 print를 호출하면 줄바꿈이 두번 일어나기 때문입니다.

with open("text.txt", "r") as handle:
  # 리스트 형식으로 가져 올 수도 있습니다.
  lines = handle.readlines();
  
print(lines);

이번에는 텍스트 파일이 아닌 이미지를 사용해 보겠습니다.

# 한번에 읽어 오는 크기
chunk = 100;
image = None;
# 모드에 b를 추가했다. 바이너리 데이터이기 때문이다.
with open("nowonbuntistory.png", "rb") as handle:
  # 파일을 끝까지 다 읽을 때까지 루프를 돈다.
  while True:
    binary = handle.read(chunk);
    # 파일을 끝까지 읽으면 루프를 멈춘다.
    if not binary:
      break;
    # 처음에는 바이너리 데이터를 저장한다.
    if image is None:
      image = binary;
    else:
      # 계속 추가한다.
      image += binary;
  
print(image);

바이너리 데이터를 출력했습니다. 바이너리 데이터는 인간이 그냥 읽을 수 없습니다.(매트릭스의 네오라면 가능할 듯...)


먼저 이미지 파일의 경우는 데이터가 큰 경우가 많기 때문에 read함수에 한 번에 읽어 올수 있는 양을 정해서 읽어오도록 했습니다. 이 방법은 text도 가능합니다.

이렇게 해야 불필요한 메모리 낭비를 줄일 수 있습니다. 요즘은 기본이 메모리 8GB인데 이미지 하나 쯤이야...


이번에는 읽어 온 것을 파일명을 바꾸어서 다시 출력해 보겠습니다.

chunk = 100;
image = None;
with open("nowonbuntistory.png", "rb") as handle:
  while True:
    binary = handle.read(chunk);
    if not binary:
      break;
    if image is None:
      image = binary;
    else:
      image += binary;
# 바이너리의 사이즈를 구한다.
size = len(image);
# offset은 현재 읽고 있는 위치입니다.
offset = 0;
with open("nowonbuntistory_copy.png", "wb") as handle:
  while True:
    # offset은 파일 사이즈를 넘어설 수 없기 때문에 여기서 루프를 멈춘다.
    if offset > size:
      break;
    #image의 구간을 100b씩 짤라서 작성한다.
    handle.write(image[offset:offset+chunk])
    offset += chunk;

파일이 그대로 복사가 된 것을 확인 할 수 있습니다.


여기까지 Python에서 IO(파일 읽기, 쓰기)를 다루는 방법에 관한 설명이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.