프로그래밍

파일 입출력

프로그램이 끝나면 값은 모두 사라집니다.
남겨 두려면 파일에 적어 두어야 합니다.

학습 목표

  1. 프로그램이 끝난 뒤에 값이 남는지를 기준으로 변수와 파일의 차이를 설명할 수 있다.
  2. 파일을 열고, 읽거나 쓰고, 닫는 세 단계를 with로 안전하게 쓸 수 있다.
  3. 읽어 온 줄에서 줄바꿈을 떼고 필요한 자료형으로 변환할 수 있다.
  4. 쓰기 모드와 추가 모드를 구별해 고를 수 있다.
  5. 쉼표로 값을 구분한 파일을 split과 join으로 읽고 써서 표 형태의 자료를 다룰 수 있다.

값은 프로그램과 함께 사라진다

변수에 저장한 값은 실행이 끝나는 순간 없어집니다.

scores = [90, 85, 72]
print(sum(scores))      # 247

# 프로그램이 끝나면 scores는 사라진다.
# 다시 실행하면 값을 「처음부터」 다시 저장해야 한다.

변수는 프로그램이 실행되는 동안에만 값을 저장합니다. 프로그램이 끝나면 변수가 쓰던 공간을 컴퓨터가 회수하므로, 다시 실행하면 값을 처음부터 다시 입력해야 합니다. 한 학급의 점수를 다룬다면 서른 명의 점수를 매번 다시 입력해야 한다는 뜻입니다.

파일(file)

저장 장치에 이름을 붙여 기록해 둔 자료입니다. 프로그램이 끝나도, 컴퓨터를 꺼도 남습니다. 그래서 다음 실행에서 지난 실행의 결과를 이어받을 수 있습니다.

변수 — 빠르지만 사라진다

실행 중에는 바로 꺼내 쓸 수 있습니다. 대신 끝나면 남지 않습니다.

파일 — 남지만 열어야 한다

끝나도 남습니다. 대신 쓰려면 열고 읽는 절차를 거쳐야 합니다.

스스로 확인

입력한 점수를 파일에 저장해 두면, 다음에 프로그램을 실행했을 때 무엇을 하지 않아도 될까요?

열고 쓰고 닫기

파일 다루기는 세 단계로 되어 있습니다.

공책을 쓸 때와 같습니다. 열고 → 적고 → 덮습니다.

f = open("메모.txt", "w", encoding="utf-8")   # ① 연다
f.write("첫 줄입니다\n")                       # ② 쓴다
f.close()                                     # ③ 닫는다
단계 코드 하는 일
① 열기 open(이름, 모드) 어떤 파일을 어떤 목적으로 쓸지 정한다
② 읽고 쓰기 f.read() f.write() 파일의 내용을 읽어 오거나 파일에 내용을 쓴다
③ 닫기 f.close() 파일 사용을 끝낸다. 이때 남은 내용까지 확실히 저장된다

모드 — 무엇을 하러 여는가

모드 이름 하는 일
"r" read 읽는다. 파일이 없으면 오류가 난다
"w" write 쓴다. 없으면 만들고, 있으면 내용을 지우고 처음부터 쓴다
"a" append (추가) 쓴다. 없으면 만들고, 있으면 기존 내용 뒤에 이어 쓴다
f = open("메모.txt", "r", encoding="utf-8")
text = f.read()         # 파일 「전체」를 문자열 하나로 읽는다
f.close()

print(text)             # 첫 줄입니다

닫기를 빠뜨리기 쉽습니다

close를 빠뜨리면 쓴 내용이 아직 파일에 저장되지 않은 채 남아 있을 수 있습니다. 중간에 오류가 나면 close까지 실행되지도 못합니다. 그래서 파이썬에는 닫기를 빠뜨릴 수 없게 해 주는 문법이 있습니다.

with open("메모.txt", "r", encoding="utf-8") as f:
    text = f.read()
# 들여쓰기가 끝나는 곳에서 「자동으로」 닫힌다

print(text)             # 첫 줄입니다

with

들여쓰기가 끝나는 곳에서 파일이 자동으로 닫힙니다. 안에서 오류가 나도 닫힙니다. close를 직접 적는 것도 틀리지 않지만, 빠뜨릴 수 없는 쪽이 더 안전합니다. 여기부터는 with로만 파일을 엽니다.

스스로 확인

파일을 "w"로 여는 프로그램을 실수로 두 번 실행하면 파일의 내용은 어떻게 될까요?

파일 읽기

텍스트 파일에서 읽어 온 값은 언제나 문자열입니다.

read는 파일 전체를 문자열 하나로 읽어 옵니다. 하지만 자료는 대개 한 줄에 하나씩 적혀 있으므로, 줄 단위로 다루는 쪽이 편합니다. 파일도 for로 한 줄씩 꺼낼 수 있어서 리스트를 반복하던 방식이 그대로 통합니다. 아래 코드가 읽는 점수.txt에는 90 · 85 · 72가 한 줄에 하나씩 적혀 있습니다.

with open("점수.txt", "r", encoding="utf-8") as f:
    for line in f:              # 파일도 for로 「한 줄씩」 꺼낼 수 있다
        print(line.strip())     # 90 / 85 / 72

줄바꿈이 딸려 옵니다

줄 끝의 줄바꿈 문자까지 함께 읽힙니다. 눈에 보이지 않아 알아채기 어렵지만, 대괄호로 감싸 출력해 보면 드러납니다. 앞뒤 공백을 떼는 strip이 줄바꿈도 함께 떼어 줍니다.

with open("점수.txt", "r", encoding="utf-8") as f:
    for line in f:
        print("[" + line + "]")     # [90
                                    # ]   줄 끝의 줄바꿈이 딸려 온다

print("---")

with open("점수.txt", "r", encoding="utf-8") as f:
    for line in f:
        print("[" + line.strip() + "]")     # [90]  [85]  [72]

하나 더 있습니다. 파일에서 읽은 90은 숫자가 아니라 문자열 "90"입니다. input으로 입력받은 값이 문자열인 것과 같은 이유로, 더하려면 숫자로 변환해야 합니다.

scores = []

with open("점수.txt", "r", encoding="utf-8") as f:
    for line in f:
        scores.append(int(line.strip()))    # 문자열로 읽히므로 「숫자로 변환한다」

print(scores)                   # [90, 85, 72]
print(sum(scores) / len(scores))    # 82.33333333333333

정리가 먼저, 변환이 나중입니다

int는 앞뒤의 줄바꿈과 공백을 무시하므로 int("90\n")도 90이 됩니다. 하지만 쉼표 같은 다른 글자가 섞이면 오류가 나며 프로그램이 멈춥니다. 읽은 줄에는 strip을 먼저 적용하는 습관을 들여 두면, 무엇이 남았는지 확인한 뒤 변환할 수 있습니다.

스스로 확인

점수 파일에서 읽은 줄을 total = total + line으로 더했더니 오류가 나며 멈췄습니다. 무엇을 빠뜨린 것일까요?

파일 쓰기

줄바꿈은 직접 넣어 주어야 합니다.

scores = [90, 85, 72]

with open("결과.txt", "w", encoding="utf-8") as f:
    for score in scores:
        f.write(str(score) + "\n")     # 숫자는 str로, 줄바꿈은 「직접」 넣는다
  • write는 문자열만 인자로 받는다. 숫자는 str로 변환해 넣는다.
  • print와 달리 줄을 저절로 바꾸지 않는다. 줄바꿈 문자를 넣지 않으면 한 줄에 모두 이어 붙는다.

"w"는 있던 내용을 지우고 처음부터 씁니다. 기록을 계속 덧붙여 가려면 "a"로 열어야 합니다.

with open("기록.txt", "w", encoding="utf-8") as f:
    f.write("첫째 줄\n")               # w — 있던 내용을 지우고 처음부터 쓴다

with open("기록.txt", "a", encoding="utf-8") as f:
    f.write("둘째 줄\n")               # a — 기존 내용 뒤에 「이어」 쓴다

with open("기록.txt", "r", encoding="utf-8") as f:
    print(f.read())                    # 첫째 줄 / 둘째 줄

"w"가 맞는 때

이번 실행의 결과로 파일을 새로 만들 때. 계산 결과표처럼 지난 것이 남으면 곤란한 경우입니다.

"a"가 맞는 때

실행할 때마다 한 줄씩 덧붙여 갈 때. 출석 기록처럼 지난 것이 남아야 하는 경우입니다.

지워진 것은 되돌릴 수 없습니다

"w"로 여는 순간 있던 내용은 바로 사라집니다. 휴지통에도 남지 않습니다. 중요한 파일 이름을 코드에 적을 때는 모드를 한 번 더 확인합니다.

스스로 확인

출석을 실행할 때마다 한 줄씩 남기려는데 "w"로 열었습니다. 파일에는 몇 줄이 남을까요?

표 형태의 자료와 CSV

쉼표로 값을 구분한 텍스트일 뿐입니다.

자료는 대개 표 형태입니다. 이름과 점수처럼 한 줄에 여러 값이 들어갑니다. 이럴 때 흔히 쓰는 것이 값 사이를 쉼표로 구분한 파일입니다.

CSV(comma-separated values)

한 줄이 한 행, 쉼표가 열 구분인 텍스트 파일입니다. 표 계산 프로그램으로 열 수도 있지만, 내용을 살펴보면 평범한 문자열입니다 — 그래서 문자열 메서드만으로 다룰 수 있습니다.

아래 코드가 읽는 성적.csv에는 학생 A,90,65와 학생 B,85,80 두 줄이 적혀 있습니다.

with open("성적.csv", "r", encoding="utf-8") as f:
    for line in f:
        values = line.strip().split(",")    # 줄바꿈을 떼고 쉼표를 기준으로 나눈다
        print(values)                       # ['학생 A', '90', '65']
                                            # ['학생 B', '85', '80']

읽기는 나누기, 쓰기는 잇기

줄바꿈을 떼고 쉼표를 기준으로 나누면 리스트가 되고, 쉼표로 이어 붙이고 줄바꿈을 더하면 한 줄이 됩니다. 문자열을 나누는 split과 잇는 join 한 쌍이 그대로 쓰입니다.

rows = [["학생 A", 90, 65],
        ["학생 B", 85, 80]]

with open("성적출력.csv", "w", encoding="utf-8") as f:
    for row in rows:
        texts = []
        for v in row:
            texts.append(str(v))            # 숫자가 섞여 있으므로 문자열로 변환해 추가하고
        f.write(",".join(texts) + "\n")     # 쉼표로 이어 한 줄씩

encoding을 적어 두는 이유

글자를 어떤 규칙으로 저장할지 정하는 것을 인코딩(encoding)이라고 합니다. 쓸 때와 읽을 때의 규칙이 다르면 한글이 깨져 보입니다. encoding="utf-8"을 양쪽에 똑같이 적어 두면 이 문제를 피할 수 있습니다.

파일에서 읽고 → 함수로 계산하고 → 파일에 쓰면 하나의 프로그램이 됩니다. 사용자가 한 글자도 입력하지 않아도 끝까지 실행되는 프로그램입니다.

def average(scores):
    return round(sum(scores) / len(scores), 1)

rows = []
with open("성적.csv", "r", encoding="utf-8") as f:
    for line in f:
        rows.append(line.strip().split(","))

with open("평균.csv", "w", encoding="utf-8") as f:
    for row in rows:
        scores = []
        for v in row[1:]:               # 이름 뒤부터가 점수다
            scores.append(int(v))
        f.write(f"{row[0]},{average(scores)}\n")

with open("평균.csv", "r", encoding="utf-8") as f:
    print(f.read())     # 학생 A,77.5 / 학생 B,82.5

한 가지 조심할 것

값 안에 쉼표가 들어 있으면 이 방법으로는 잘못 나뉩니다. 실제 자료를 다룰 때는 그런 줄이 있는지 먼저 확인해야 합니다. 여기서는 값에 쉼표가 없다고 보고 다룹니다.

스스로 확인

첫 줄에 「이름,국어,영어」 같은 제목이 들어 있는 파일이라면, 계산 전에 무엇을 해 주어야 할까요?

핵심 정리

변수는 사라지고 파일은 남는다

변수는 프로그램이 실행되는 동안에만 값을 저장한다. 다음 실행에서 이어받게 하려면 파일에 적어 둔다.

열고 → 다루고 → 닫는다

with open(...) as f:를 쓰면 들여쓰기가 끝나는 곳에서 파일이 자동으로 닫힌다.

텍스트 파일에서 읽은 값은 문자열이다

줄 끝에 줄바꿈이 딸려 오므로 strip으로 떼고, 계산하려면 int·float로 변환한다.

"w"와 "a"

"w"는 기존 내용을 지우고 처음부터, "a"는 기존 내용 뒤에 이어 쓴다. write는 줄을 저절로 바꾸지 않는다.

CSV는 특별한 파일이 아니다

한 줄이 한 행, 쉼표가 열 구분인 평범한 텍스트다. 읽기는 나누기, 쓰기는 잇기로 다룬다. 한글을 주고받을 때는 encoding을 양쪽에 같게 적는다.

확인 퀴즈

배운 내용을 확인해 봅시다.

1. 내용이 들어 있는 파일을 "w"로 열면?

2. 한 줄에 하나씩 점수가 적힌 파일에서 line을 그대로 int에 넣기 전에 할 일은?

3. 다음 중 틀린 설명은?