파이썬 파일 입출력의 기초

파이썬에서 파일을 다루는 것은 데이터를 저장하고 불러오는 데 필수적인 과정입니다. 파일 작업은 크게 세 단계를 거칩니다. 파일을 열고, 해당 파일을 사용하며, 마지막으로 파일을 닫는 것입니다.

기본 파일 작업 흐름

# 1단계: 파일 열기 (읽기 모드, UTF-8 인코딩)
file_descriptor = open('example.txt', 'r', encoding='utf-8')

# 2단계: 파일 내용 사용 (예: 전체 내용 출력)
print(file_descriptor.read())

# 3단계: 파일 닫기
file_descriptor.close()

파일을 연 후에는 반드시 닫아주어 시스템 자원을 해제해야 합니다. 파이썬의 with 문을 사용하면 파일을 자동으로 닫아주므로 더욱 안전하고 편리합니다.

# with 문을 사용한 자동 파일 닫기
with open('example.txt', 'r', encoding='utf-8') as file_obj:
    content = file_obj.readlines()
    print(content)

파일 경로 지정 방식

파일 경로를 지정하는 방법은 여러 가지가 있습니다.

# 1. 원시(raw) 문자열 사용: '\'가 특수 문자로 해석되지 않음
file_path_raw = r'C:\Users\User\Documents\data.txt'
with open(file_path_raw, 'r') as f:
    pass

# 2. 이스케이프 문자 '\\' 사용
file_path_escaped = 'C:\\Users\\User\\Documents\\data.txt'
with open(file_path_escaped, 'r') as f:
    pass

# 3. Unix 스타일 '/' 슬래시 사용 (Windows에서도 호환 가능)
file_path_unix = 'C:/Users/User/Documents/data.txt'
with open(file_path_unix, 'r') as f:
    pass

파일 읽기 모드 (텍스트 vs. 이진)

파일을 읽을 때는 텍스트 모드('t')와 이진 모드('b')를 구분하여 사용합니다.

  • 'rt' (기본값): 텍스트 파일 읽기 모드. 문자열 단위로 데이터를 처리합니다.
  • 'rb': 이진 파일 읽기 모드. 바이트 단위로 데이터를 처리합니다. 이미지, 오디오 파일 등에서 사용됩니다.

read() 메서드를 이용한 데이터 읽기

read() 메서드는 파일 전체 내용을 한 번에 읽거나, 인자로 전달된 숫자만큼의 문자 또는 바이트를 읽습니다.

# 'rt' 모드: 인자는 문자(character) 수
with open('text_data.txt', 'r', encoding='utf-8') as f:
    print(f.read(5)) # 첫 5개 문자 읽기

# 'rb' 모드: 인자는 바이트(byte) 수
# (예시를 위해 'rb'로 열고 바이트로 읽는 척 하지만, 실제로는 text_data.txt는 텍스트 파일이므로 주의)
with open('text_data.txt', 'rb') as f:
    print(f.read(5)) # 첫 5개 바이트 읽기

예시 파일 (text_data.txt 내용):

안녕하세요
파이썬 파일
입출력입니다

read() 전체 읽기 예시:

with open('text_data.txt', 'r', encoding='utf-8') as f:
    all_content = f.read()
    print(all_content)

실행 결과:

안녕하세요
파이썬 파일
입출력입니다

readline() 메서드를 이용한 한 줄 읽기

readline()은 파일을 한 줄씩 읽어옵니다. 인자를 전달하면 해당 줄에서 지정된 문자(바이트) 수만큼 읽습니다.

with open('text_data.txt', 'r', encoding='utf-8') as f:
    first_line_partial = f.readline(4) # 첫 줄에서 4개 문자 읽기
    print(first_line_partial)

실행 결과:

안녕하

readlines() 메서드를 이용한 전체 줄 읽기

readlines()는 파일의 모든 줄을 읽어와 각각의 줄을 리스트의 요소로 반환합니다. 각 요소는 줄바꿈 문자(\n)를 포함합니다.

with open('text_data.txt', 'r', encoding='utf-8') as f:
    all_lines = f.readlines()
    print(all_lines)

실행 결과:

['안녕하세요\n', '파이썬 파일\n', '입출력입니다\n']

대용량 파일 효율적으로 읽기

대용량 파일은 read()로 한 번에 읽으면 메모리 문제를 일으킬 수 있습니다. 대신 파일을 작은 청크(chunk) 단위로 나누어 순차적으로 읽는 것이 효율적입니다.

def read_large_file_in_chunks(filepath, chunk_size_chars=1024):
    """
    대용량 텍스트 파일을 청크 단위로 읽어 내용을 반환합니다.
    """
    total_content = []
    try:
        with open(filepath, 'r', encoding='utf-8') as file_handle:
            while True:
                segment = file_handle.read(chunk_size_chars)
                if not segment:  # 더 이상 읽을 내용이 없으면 반복 종료
                    break
                total_content.append(segment)
    except FileNotFoundError:
        print(f"오류: '{filepath}' 파일을 찾을 수 없습니다.")
        return ""
    except Exception as e:
        print(f"파일 읽기 중 오류 발생: {e}")
        return ""
    return "".join(total_content)

# 함수 사용 예시
large_file_path = 'large_document.txt'
# 예시 파일을 생성합니다 (실제 사용 시에는 기존 파일을 사용)
with open(large_file_path, 'w', encoding='utf-8') as f:
    for i in range(1000):
        f.write(f"This is line {i+1} in the large document.\n")

document_data = read_large_file_in_chunks(large_file_path, 256)
# print(document_data[:500]) # 처음 500자만 출력하여 확인
print("대용량 파일 읽기 완료.")

파일 쓰기: 덮어쓰기 ('wt')

'w' 모드는 파일이 존재하면 기존 내용을 모두 지우고 새 내용을 작성합니다. 파일이 없으면 새로 생성합니다.

target_file = 'output.txt'

# 파일 덮어쓰기
with open(target_file, 'w', encoding='utf-8') as f:
    f.write('새로운 내용입니다.\n')
    f.write('이전 내용은 사라졌습니다.\n')

# 내용 확인
with open(target_file, 'r', encoding='utf-8') as f:
    print(f.read())

실행 결과:

새로운 내용입니다.
이전 내용은 사라졌습니다.

파일 쓰기: 내용 추가 ('at')

'a' 모드는 파일 끝에 내용을 추가합니다. 파일이 없으면 새로 생성합니다.

target_file = 'output.txt'

# 기존 내용이 있다고 가정하고 추가
with open(target_file, 'a', encoding='utf-8') as f:
    f.write('이 줄은 추가되었습니다.\n')
    f.write('또 다른 줄이 추가됩니다.\n')

# 내용 확인
with open(target_file, 'r', encoding='utf-8') as f:
    print(f.read())

실행 결과 (이전 'output.txt' 내용에 이어서):

새로운 내용입니다.
이전 내용은 사라졌습니다.
이 줄은 추가되었습니다.
또 다른 줄이 추가됩니다.

이진 파일 입출력 ('rb', 'wb', 'ab')

텍스트 파일이 아닌 이미지, 동영상, 오디오 파일 등은 이진 모드로 다루어야 합니다.

  • 'rb': 이진 파일 읽기
  • 'wb': 이진 파일 덮어쓰기 (쓰기)
  • 'ab': 이진 파일 내용 추가 (쓰기)

이진 파일 복사 예시:

import os

source_binary_file = 'input.bin'
destination_binary_file = 'copied.bin'

# 예시를 위한 더미 이진 파일 생성
with open(source_binary_file, 'wb') as f:
    f.write(b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09' * 100) # 1000 바이트의 더미 데이터

print(f"'{source_binary_file}' 복사 중...")

try:
    with open(source_binary_file, 'rb') as src_file:
        with open(destination_binary_file, 'wb') as dest_file:
            chunk_size = 4096 # 4KB씩 읽고 쓰기
            while True:
                binary_data = src_file.read(chunk_size)
                if not binary_data: # 더 이상 읽을 데이터가 없으면 종료
                    break
                dest_file.write(binary_data)
    print(f"'{source_binary_file}'이(가) '{destination_binary_file}'(으)로 성공적으로 복사되었습니다.")
except FileNotFoundError:
    print(f"오류: 원본 파일 '{source_binary_file}'을(를) 찾을 수 없습니다.")
except Exception as e:
    print(f"파일 복사 중 오류 발생: {e}")

# 복사된 파일 크기 확인 (선택 사항)
if os.path.exists(source_binary_file) and os.path.exists(destination_binary_file):
    src_size = os.path.getsize(source_binary_file)
    dest_size = os.path.getsize(destination_binary_file)
    print(f"원본 파일 크기: {src_size} 바이트")
    print(f"복사본 파일 크기: {dest_size} 바이트")

파일 커서(포인터) 제어: seek()tell()

파일 내에서 읽기/쓰기 위치(커서)를 변경하거나 현재 위치를 확인할 수 있습니다.

  • seek(offset, whence): 커서 위치를 변경합니다.
    • offset: 이동할 바이트 수입니다.
    • whence: 기준 위치를 지정합니다.
      • 0 (os.SEEK_SET): 파일의 시작 (기본값)
      • 1 (os.SEEK_CUR): 현재 커서 위치
      • 2 (os.SEEK_END): 파일의 끝

    참고: 텍스트 모드('t')에서는 whence1 또는 2일 때 offset 값은 0이어야 합니다. 즉, 텍스트 모드에서는 파일 시작부터의 절대 위치로만 이동할 수 있으며, 인코딩 문제로 인해 상대적 이동은 지원되지 않습니다. 이진 모드('b')에서는 모든 whence 값과 offset 값을 자유롭게 사용할 수 있습니다.

  • tell(): 현재 커서 위치를 바이트 단위로 반환합니다.

예시: 로그 파일 실시간 모니터링 (tail -f와 유사)

import time
import os

log_file = 'application.log'

# 예시 로그 파일 생성
with open(log_file, 'w', encoding='utf-8') as f:
    f.write("INFO: Application started.\n")
    f.write("DEBUG: Processing user input.\n")

print(f"'{log_file}' 파일 실시간 모니터링 시작... (Ctrl+C로 종료)")

try:
    with open(log_file, 'rb') as f_bin: # 이진 모드로 열어야 seek(0, 2)가 제대로 작동
        f_bin.seek(0, os.SEEK_END) # 파일의 끝으로 이동
        while True:
            new_data = f_bin.read()
            if new_data:
                decoded_data = new_data.decode('utf-8', errors='ignore')
                if 'ERROR' in decoded_data or 'CRITICAL' in decoded_data:
                    print(f"[알림] 오류 감지: {decoded_data.strip()}")
                else:
                    print(f"새 로그: {decoded_data.strip()}")
            else:
                # 새로운 로그를 시뮬레이션
                with open(log_file, 'a', encoding='utf-8') as f_append:
                    if time.time() % 10 < 2: # 대략 10초에 한 번 ERROR 추가
                        f_append.write(f"ERROR: Something went wrong at {time.ctime()}.\n")
                    elif time.time() % 5 < 1: # 대략 5초에 한 번 INFO 추가
                        f_append.write(f"INFO: Task completed successfully at {time.ctime()}.\n")
                time.sleep(1) # 1초마다 새로운 데이터 확인
except KeyboardInterrupt:
    print("\n모니터링이 중단되었습니다.")
except Exception as e:
    print(f"오류 발생: {e}")

# 생성된 예시 파일 정리
# if os.path.exists(log_file):
#     os.remove(log_file)

OS 모듈을 이용한 파일 시스템 관리

파이썬의 os 모듈은 운영체제와 상호작용하여 파일 및 디렉터리 작업을 수행하는 다양한 함수를 제공합니다. (표준 라이브러리 문서)

import os
from pprint import pprint

print("현재 작업 디렉터리:", os.getcwd())

# 디렉터리 목록 가져오기
print("\n현재 디렉터리 내용:")
current_dir_contents = os.listdir('.')
pprint(current_dir_contents)

# 디렉터리 변경 (예시로 상위 디렉터리로 이동 후 다시 돌아옴)
# os.chdir('..')
# print("\n상위 디렉터리로 이동 후:", os.getcwd())
# os.chdir(os.path.dirname(os.path.abspath(__file__))) # 스크립트 위치로 다시 이동
# print("스크립트 디렉터리로 돌아옴:", os.getcwd())

# 새 디렉터리 생성 (이미 존재하면 오류 발생)
new_directory = 'my_new_folder'
if not os.path.exists(new_directory):
    os.mkdir(new_directory)
    print(f"\n'{new_directory}' 디렉터리 생성.")
else:
    print(f"\n'{new_directory}' 디렉터리가 이미 존재합니다.")

# 파일 생성 및 삭제
temp_file = os.path.join(new_directory, 'temp_data.txt')
with open(temp_file, 'w') as f:
    f.write("임시 데이터.")
print(f"'{temp_file}' 파일 생성.")

if os.path.exists(temp_file):
    os.remove(temp_file)
    print(f"'{temp_file}' 파일 삭제.")

# 디렉터리 삭제 (디렉터리가 비어있어야 함)
if os.path.exists(new_directory) and not os.listdir(new_directory):
    os.rmdir(new_directory)
    print(f"비어있는 '{new_directory}' 디렉터리 삭제.")
elif os.path.exists(new_directory):
    print(f"'{new_directory}' 디렉터리가 비어있지 않아 삭제할 수 없습니다.")

# 파일 또는 디렉터리 이름 변경/이동
old_name = 'old_file.txt'
new_name = 'renamed_file.txt'
another_folder = 'another_place'

with open(old_name, 'w') as f:
    f.write("이름을 변경할 파일.")

print(f"\n'{old_name}' 파일 생성.")
os.rename(old_name, new_name)
print(f"'{old_name}'을(를) '{new_name}'으로 이름 변경.")

if not os.path.exists(another_folder):
    os.mkdir(another_folder)
    print(f"'{another_folder}' 디렉터리 생성.")

moved_path = os.path.join(another_folder, 'moved_file.txt')
with open('file_to_move.txt', 'w') as f:
    f.write("이동할 파일.")
print("'file_to_move.txt' 파일 생성.")

os.rename('file_to_move.txt', moved_path)
print(f"'file_to_move.txt'을(를) '{moved_path}'(으)로 이동.")

# 정리 (선택 사항)
if os.path.exists(new_name): os.remove(new_name)
if os.path.exists(moved_path): os.remove(moved_path)
if os.path.exists(another_folder) and not os.listdir(another_folder): os.rmdir(another_folder)

태그: python 파일입출력 파일처리 os모듈 with문

7월 20일 04:50에 게시됨