9-3 할 일 목록 관리 프로그램: 파일 입출력 적용

9-3 할 일 목록 관리 프로그램: 파일 입출력 적용

← 목차로 돌아가기

썸네일

지금까지 배운 변수, 리스트, 함수, 조건문, 반복문을 한데 모아 실제로 쓸 수 있는 프로그램을 만들어 볼 차례입니다. 이번 편의 주제는 할 일 목록(To-Do List) 관리 프로그램이고, 핵심은 “프로그램을 껐다 켜도 데이터가 사라지지 않게 만드는 것”입니다. open() 함수와 텍스트 파일을 이용해 할 일 목록을 저장하고 다시 불러오는 방법을 익혀보겠습니다.

핵심 요약

프로그램이 종료돼도 데이터가 남아 있으려면 파일에 저장해야 합니다. 이번 편에서는 open()with 문으로 텍스트 파일을 읽고 쓰는 방법, 할 일을 추가·조회·삭제하는 함수를 만드는 방법, 그리고 파일이 없거나 손상됐을 때를 대비한 예외 처리까지 다룹니다. 완성하고 나면 콘솔에서 동작하는 나만의 할 일 관리 프로그램을 가지게 됩니다.

왜 메모리만으로는 부족할까요?

파이썬 프로그램이 실행되는 동안 변수와 리스트는 컴퓨터의 메모리(RAM)에 올라가 있습니다. 메모리는 속도는 빠르지만 프로그램이 종료되는 순간 그 안의 내용을 모두 잃어버립니다. 반면 파일은 디스크에 저장되기 때문에 프로그램이 꺼져도, 심지어 컴퓨터를 재부팅해도 그대로 남아 있습니다.

할 일 목록처럼 “오늘 추가한 항목을 내일도 다시 보고 싶은” 데이터는 이 차이가 결정적입니다. 그래서 프로그램을 시작할 때 파일에서 데이터를 불러오고(load), 데이터가 바뀔 때마다 파일에 다시 써주는(save) 구조가 필요합니다.

프로그램 구조 설계하기

전체 프로그램은 크게 세 부분으로 나눌 수 있습니다.

  • 할 일을 담아두는 리스트 todos
  • 파일을 읽고 쓰는 함수 load_todos(), save_todos()
  • 할 일을 다루는 함수 add_todo(), view_todos(), remove_todo()와, 이들을 반복 호출하는 메뉴 루프

이렇게 기능별로 함수를 나눠두면 나중에 기능 하나를 수정하거나 추가할 때 다른 부분에 영향을 주지 않아 관리가 편해집니다.

open()with 문으로 파일 다루기

파이썬에서 파일을 열 때는 보통 with open(...) as file: 형태를 씁니다. with 문을 쓰면 블록이 끝나는 순간 파일이 자동으로 닫히기 때문에, 파일을 닫는 걸 깜빡해서 생기는 문제를 걱정하지 않아도 됩니다.

with open("todos.txt", "r", encoding="utf-8") as file:
    content = file.read()

두 번째 인자는 파일을 여는 모드입니다. "r"은 읽기, "w"는 쓰기(기존 내용을 덮어씀), "a"는 뒤에 이어붙이기를 뜻합니다. encoding="utf-8"은 한글이 깨지지 않도록 지정하는 부분이니 빼먹지 않는 게 좋습니다.

할 일 불러오기: load_todos()

프로그램을 처음 실행하면 아직 todos.txt 파일이 없을 수 있습니다. 이럴 때 open()을 그냥 읽기 모드로 호출하면 FileNotFoundError가 발생하는데, 이건 프로그램의 결함이 아니라 “아직 저장된 할 일이 없다”는 지극히 정상적인 상황입니다. 그래서 try-except로 감싸 자연스럽게 처리합니다.

TODO_FILE = "todos.txt"
todos = []

def load_todos():
    global todos
    try:
        with open(TODO_FILE, "r", encoding="utf-8") as file:
            todos = [line.strip() for line in file]
        print(f"{len(todos)}개의 할 일을 불러왔습니다.")
    except FileNotFoundError:
        print("저장된 할 일 목록이 없어 새로 시작합니다.")
        todos = []

이 코드는 파일을 한 줄씩 읽어 리스트로 만들되, strip()으로 각 줄 끝의 줄바꿈 문자(\n)를 제거합니다. 파일이 없으면 예외를 잡아 빈 리스트로 시작하도록 했습니다.

할 일 저장하기: save_todos()

저장은 불러오기보다 단순합니다. todos 리스트를 한 줄씩 파일에 쓰면 됩니다. 다만 할 일이 바뀔 때마다(추가·삭제 직후) 곧바로 저장해야 프로그램이 예기치 않게 종료돼도 데이터를 잃지 않습니다.

def save_todos():
    with open(TODO_FILE, "w", encoding="utf-8") as file:
        for todo in todos:
            file.write(todo + "\n")

쓰기 모드("w")는 파일을 열 때마다 기존 내용을 지우고 새로 씁니다. 그래서 매번 todos 리스트 전체를 다시 써주는 지금 방식이 딱 맞습니다.

추가·조회·삭제 기능과 메뉴 루프 만들기

이제 나머지 기능을 함수로 만들고, 하나로 이어 붙일 차례입니다. 각 함수는 자기 역할만 하고, 데이터가 바뀌면 save_todos()를 호출해 즉시 파일에 반영합니다.

def add_todo(item):
    todos.append(item)
    save_todos()
    print(f"'{item}'을(를) 추가했습니다.")

def view_todos():
    if not todos:
        print("할 일 목록이 비어 있습니다.")
        return
    for i, todo in enumerate(todos, start=1):
        print(f"{i}. {todo}")

def remove_todo(index):
    if 1 <= index <= len(todos):
        removed = todos.pop(index - 1)
        save_todos()
        print(f"'{removed}'을(를) 삭제했습니다.")
    else:
        print("존재하지 않는 번호입니다.")

def main():
    load_todos()
    while True:
        print("\n1.추가  2.보기  3.삭제  4.종료")
        choice = input("메뉴 선택: ")
        if choice == "1":
            add_todo(input("할 일 입력: "))
        elif choice == "2":
            view_todos()
        elif choice == "3":
            try:
                remove_todo(int(input("삭제할 번호: ")))
            except ValueError:
                print("숫자를 입력해 주세요.")
        elif choice == "4":
            print("프로그램을 종료합니다.")
            break
        else:
            print("1~4 중에서 선택해 주세요.")

if __name__ == "__main__":
    main()

삭제 기능에서 int(input(...)) 부분을 try-except로 감싼 이유가 있습니다. 사용자가 숫자 대신 문자를 입력하면 int()ValueError를 일으키는데, 이걸 그대로 두면 프로그램이 통째로 멈춰버립니다. 예외를 잡아두면 잘못된 입력이 들어와도 프로그램은 계속 실행됩니다.

자주 발생하는 오류와 해결법

UnicodeDecodeError가 뜬다면 — 파일을 열 때 encoding="utf-8"을 빼먹었을 가능성이 큽니다. 저장할 때와 불러올 때 인코딩을 반드시 통일해야 합니다.

IndexError가 뜬다면 — 리스트 범위를 벗어난 인덱스에 접근한 경우입니다. remove_todo()처럼 사용자 입력값을 그대로 인덱스로 쓰기 전에 범위를 먼저 확인해야 합니다.

저장이 안 되는 것 같다면save_todos()를 호출하는 위치를 확인해 보세요. 할 일을 추가하거나 삭제한 직후 바로 호출하지 않으면, 메모리상의 todos와 파일 내용이 서로 어긋나게 됩니다.

자주 묻는 질문 (FAQ)

Q. 텍스트 파일 대신 다른 방식으로 저장할 수는 없나요?
가능합니다. 지금은 한 줄에 할 일 하나씩 저장하는 가장 단순한 방식을 썼지만, 완료 여부나 마감일처럼 정보가 많아지면 JSON 형식으로 저장하는 방법이 더 적합합니다. 이 부분은 다음 챕터들에서 다룰 예정입니다.

Q. 매번 저장하지 않고 종료할 때 한 번만 저장하면 안 되나요?
그렇게 만들 수도 있지만, 프로그램이 예기치 않게 강제 종료되면 그동안의 변경 사항이 모두 사라집니다. 변경될 때마다 저장하는 방식이 더 안전합니다.

Q. 파일이 이미 존재하는데 내용이 이상하게 깨져 있으면 어떻게 하나요?
지금 코드에서는 별도로 복구하지 않습니다. 실무에서는 파일을 열기 전 손상 여부를 검사하거나 백업 파일을 두는 방식으로 보완합니다.

Q. global 키워드는 왜 필요한가요?
load_todos() 함수 안에서 전역 변수 todos에 새 값을 대입하려면 global로 선언해야 합니다. 선언하지 않으면 파이썬은 함수 안에서만 쓰이는 지역 변수로 새로 만들어버립니다.

마무리하며

이번 편에서는 리스트에만 의존하던 프로그램에 파일 입출력을 더해, 데이터가 실제로 살아남는 프로그램을 만들어 봤습니다. open()with 문, 그리고 try-except로 예외를 처리하는 흐름은 앞으로 어떤 프로그램을 만들든 반복해서 쓰이는 기본기입니다. 다음 편에서는 함수와 예외 처리를 좀 더 본격적으로 활용해 간단한 계산기를 만들어 보겠습니다.

참고 자료

  1. Python 공식 튜토리얼 — 입력과 출력 / 파일 읽고 쓰기: docs.python.org
  2. Python 표준 라이브러리 — 내장 함수 open(): docs.python.org
  3. Python 공식 튜토리얼 — 오류와 예외 처리: docs.python.org

#파이썬파일입출력, #할일목록프로그램, #파이썬기초강좌, #텍스트파일저장불러오기, #초보자파이썬예제, #파이썬, #파일입출력, #할일목록, #예외처리, #콘솔프로그램

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다