파이썬 파일 입출력 및 예외 처리 패턴

데이터 영속성과 파일 관리의 기초

소프트웨어 개발 과정에서 프로그램이 종료되더라도 유지되어야 하는 데이터가 존재합니다. 이를 위해 가장 보편적으로 활용되는 방법은 파일을 매개로 데이터를 저장하는 것입니다. 운영 체제는 파일 시스템을 통해 데이터를 관리하며, 파이썬은 이러한 저수준 작업을 추상화한 내장 함수를 제공합니다.

파일 작업을 수행할 때는 open() 함수를 통해 핸들을 생성하고, 이후에 제공된 메서드를 활용하여 데이터를 읽고 쓰게 됩니다. 이때 파일을 어떻게 열 것인지 정의하는 '모드(mode)' 설정이 필수적입니다. 주요 모드별 기능은下表와 같습니다.

모드 문자 설명
'r' 읽기 전용 (기본값)
'w' 쓰기 전용 (파일이 없으면 생성, 있으면 내용을 초기화)
'x' 독점 쓰기 (파일이 이미 존재하면 오류 발생)
'a' 첨부 모드 (내용을 파일 끝에 추가)
'b' 이진 데이터 처리
't' 텍스트 데이터 처리 (기본값)
'+' 읽기 및 쓰기 동시 허용

텍스트 파일의 안전な 접근 방식

일반적인 텍스트 파일을 다룰 때는 인코딩 정보를 명시하는 것이 좋습니다. 시스템 기본 인코딩과 다를 경우 해독 오류가 발생할 수 있으므로 encoding='utf-8' 를 권장합니다. 또한, 파일이 존재하지 않거나 접근 권한 문제가 있을 경우 프로그램이 비정상적으로 종료되지 않도록 예외 처리를 적용해야 합니다.

자원 누수를 방지하기 위해서는 finally 블록을 사용하는 대신, 문맥 관리자 (Context Manager) 로 동작하는 with 문법을 사용하는 것이 현대적인 파이썬 코딩 관례입니다. 이는 코드 실행 영역을 벗어나는 시점에 리소스를 자동으로 해제해 줍니다.

def read_text_safely(target_path):
    """안전하게 텍스트 파일을 읽어옵니다."""
    try:
        with open(target_path, mode='r', encoding='utf-8') as file_handle:
            content = file_handle.read()
            print(f"파일 내용:\n{content}")
    except FileNotFoundError:
        print(f"오류: '{target_path}' 파일을 찾을 수 없습니다.")
    except UnicodeDecodeError:
        print("오류: 파일 인코딩 형식을 해석할 수 없습니다.")
    else:
        print("파일 읽기가 완료되었습니다.")

if __name__ == '__main__':
    read_text_safely('sample_note.txt')

대용량 파일을 처리할 때 메모리를 한 번에 모두 로드하는 것은 비효율적일 수 있습니다. 이 경우 줄 단위로 반복하거나 readlines() 를 사용하여 리스트로 변환할 수 있습니다.

def process_lines_log(file_name):
    import time
    
    # 전체 일회성 읽기
    with open(file_name, 'r', encoding='utf-8') as f:
        all_data = f.read()
    
    # 줄 단위 스트리밍 처리
    with open(file_name, 'r', encoding='utf-8') as f:
        line_count = 0
        for line in f:
            line_count += 1
            print(line.strip())
            time.sleep(0.1) # 디버깅용 지연
        print(f"총 {line_count} 줄 처리")
        
    # 리스트에 담아서 처리
    with open(file_name, 'r', encoding='utf-8') as f:
        lines_list = f.readlines()
    print(f"리스트 길이: {len(lines_list)}")

if __name__ == '__main__':
    process_lines_log('large_log.txt')

조건 기반 멀티 파일 작성 실습

생성된 데이터를 특정 기준에 따라 여러 파일로 분산 저장해야 하는 경우가 있습니다. 예를 들어, 생성된 숫자를 범위에 따라 다른 로그 파일로 기록한다고 가정해 봅시다. 여기서는 소수 판별 로직보다는 점수대를 기준으로 나누는 방식을 적용하여 로직의 유사성을 낮추고 가독성을 높였습니다.

from math import sqrt

def check_prime_number(num):
    if num <= 1: return False
    for i in range(2, int(sqrt(num)) + 1):
        if num % i == 0: return False
    return True

def distribute_prime_logs():
    output_files = [
        ('small_primes.dat', lambda x: x < 100),
        ('medium_primes.dat', lambda x: 100 <= x < 1000),
        ('large_primes.dat', lambda x: x >= 1000)
    ]
    
    handles = []
    try:
        # 파일 핸들 초기화
        for fname, _ in output_files:
            handles.append(open(fname, 'w', encoding='utf-8'))
        
        for num in range(1, 10000):
            if check_prime_number(num):
                if num < 100:
                    handles[0].write(f"{num}\n")
                elif num < 1000:
                    handles[1].write(f"{num}\n")
                else:
                    handles[2].write(f"{num}\n")
                    
    except IOError as err:
        print(f"입출력 오류 발생: {err}")
    finally:
        for h in handles:
            h.close()
        print("분산 저장 작업 완료")

if __name__ == '__main__':
    distribute_prime_logs()

비정형 데이터의 이진 및 직렬화 처리

이미지나 실행 파일 같은 이진 데이터는 텍스트 모드가 아닌 'rb'(Read Binary) 와 'wb'(Write Binary) 모드를 사용해야 합니다. 이를 통해 원본 파일의 구조가 손상되지 않고 복사될 수 있습니다.

def duplicate_binary_source(source_name, target_name):
    try:
        with open(source_name, 'rb') as src_stream:
            binary_data = src_stream.read()
        
        with open(target_name, 'wb') as dest_stream:
            dest_stream.write(binary_data)
        print("복사 성공")
    except (FileNotFoundError, IOError) as e:
        print(f"작업 실패: {e}")

# 예제 실행
duplicate_binary_source('image_original.jpg', 'image_copy.jpg')

JSON 포맷을 활용한 데이터 교환

프로그램 내부의 복잡한 자료구조 (사전, 리스트 등) 를 파일이나 네트워크를 통해 주고받을 때는 JSON(JavaScript Object Notation) 표준 형식이 널리 사용됩니다. Python 의 json 모듈은 파이썬 객체와 JSON 문자열 간의 변환을 담당합니다.

데이터 타입의 대응 관계는 다음과 같습니다:

  • Python dict <-> JSON object
  • Python list / tuple <-> JSON array
  • Python str <-> JSON string
  • Python int / float <-> JSON number
  • Python True / False <-> JSON true / false
  • Python None <-> JSON null
import json

def save_and_load_inventory():
    # 직렬화 대상 데이터 모델
    stock_info = {
        "warehouse": "Seoul_Main",
        "items": [
            {"sku": "IP-001", "qty": 50},
            {"sku": "MD-002", "qty": 120}
        ],
        "active": True
    }
    
    # 파일에 저장 (Dump)
    try:
        with open('inventory.json', 'w', encoding='utf-8') as f:
            json.dump(stock_info, f, ensure_ascii=False, indent=4)
            
        # 파일에서 복원 (Load)
        with open('inventory.json', 'r', encoding='utf-8') as f:
            loaded_data = json.load(f)
            
        print(f"재고 확인: {loaded_data['warehouse']} - 총 항목: {len(loaded_data['items'])}")
    except Exception as e:
        print(f"저장/불러오기 실패: {e}")

if __name__ == '__main__':
    save_and_load_inventory()

마지막으로 외부 API 에서 제공하는 데이터를 활용할 때는 HTTP 클라이언트 라이브러리 (예: requests) 와 병행하여 사용합니다. 서버로부터 반환된 JSON 문자열을 json.loads() 를 통해 파싱하여 필요한 필드를 추출할 수 있습니다. 보안상 실제 API Key 는 환경 변수 등으로 관리하는 것이 원칙입니다.

import requests
import json

def fetch_news_headlines(api_endpoint, api_key):
    params = {'key': api_key, 'count': 5}
    try:
        response = requests.get(api_endpoint, params=params)
        response.raise_for_status()
        
        data = response.json()
        
        if 'articles' in data:
            for item in data['articles']:
                print(f"- {item['title'][:30]}...")
        else:
            print("유효한 뉴스 데이터가 아닙니다.")
            
    except requests.exceptions.RequestException as req_err:
        print(f"네트워크 통신 오류: {req_err}")

# 사용 예시 (실제 Key 는 필요 시 발급받음)
# fetch_news_headlines('https://api.example.com/news', 'YOUR_API_KEY')

파이썬에서는 json 외에도 pickleshelve 모듈을 사용할 수 있으나, 이들은 파이썬 자체 프로토콜을 기반으로 하므로 타 언어나 플랫폼 간 호환성이 제한됩니다. 따라서 공개 인터페이스나 상호 운용이 필요한 상황에서는 반드시 JSON 또는 YAML과 같은 표준 포맷을 선택해야 합니다.

태그: python file-io Exception-Handling JSON serialization

7월 12일 17:14에 게시됨