파이썬에서 예외 처리와 파일 입출력을 효과적으로 활용하는 10가지 전략
파이썬 프로그래밍에서 예외 처리와 파일 작업은 시스템의 안정성과 유지보수성을 결정짓는 핵심 요소입니다. 본 문서에서는 실제 프로젝트 코드를 기반으로, 실무에 바로 적용 가능한 10가지 핵심 기법을 소개합니다.
- 구조화된 예외 처리: try-except-finally 패턴 예외 발생 시 프로그램이 중단되지 않도록 하기 위해, 명확한 흐름 제어를 위한 기본 구조를 사용해야 합니다. 아래는 정상적인 실행과 오류 발생 시 모두 처리할 수 있는 구조입니다:
try:
operation_result = risky_calculation()
except ValueError as error:
print(f"입력 값 오류: {error}")
except Exception as e:
print(f"예기치 않은 오류 발생: {e}")
else:
print("연산 성공! 결과:", operation_result)
finally:
cleanup_resources()
이 구조는 모든 상황에서 자원 해제 및 로깅을 보장합니다.
- with 문을 통한 자동 리소스 관리
파일, 네트워크 연결 등 외부 리소스는 반드시 닫혀야 합니다.
with문은 컨텍스트 매니저를 통해 자동으로 리소스를 해제합니다:
file_path = 'data.txt'
with open(file_path, 'r', encoding='utf-8') as file_stream:
content_lines = file_stream.readlines()
# 파일은 자동으로 닫힘 → 메모리 누수 방지
이 방법은 코드 가독성과 안정성을 동시에 확보합니다.
- 구체적인 예외 타입 처리하기
포괄적인
except:문보다는 특정 예외 유형을 명시적으로 처리하는 것이 바람직합니다:
try:
value = int(user_input)
except ValueError:
print("유효한 숫자를 입력하세요.")
except ZeroDivisionError:
print("0으로 나누려고 시도했습니다.")
이렇게 하면 오류 원인을 정확히 파악하고 적절한 대응이 가능합니다.
- 중첩된 예외 처리를 통한 복잡한 로직 처리 데이터 변환 과정에서 여러 단계의 실패 가능성이 있을 경우, 내부/외부 예외 처리를 분리하여 구현합니다:
try:
with open('input.csv', 'r') as f:
for line in f:
parts = line.strip().split(';')
total = 0
try:
for part in parts:
num = float(part)
total += num
print(f"합계: {total}")
except (ValueError, TypeError) as conv_error:
print(f"숫자 변환 실패: {parts} - {conv_error}")
except FileNotFoundError:
print("입력 파일을 찾을 수 없습니다.")
except PermissionError:
print("파일 접근 권한이 없습니다.")
외부 예외(파일 열기)와 내부 예외(데이터 변환)를 명확히 분리함으로써 디버깅 용이성 증가.
- else 블록을 이용한 정상 경로 처리
try블록에서 예외가 발생하지 않았을 때만 실행되는else블록은 정상적인 처리 로직을 명확하게 분리합니다:
user_age = input("나이를 입력하세요: ")
try:
age = int(user_age)
except ValueError:
print("나이는 숫자여야 합니다.")
else:
if age >= 18:
print("성인입니다.")
else:
print("미성년자입니다.")
이렇게 하면 오류 처리와 정상 흐름이 혼동되지 않습니다.
- finally 블록을 통한 필수 리소스 정리
finally블록은 예외 여부와 무관하게 항상 실행되므로, 파일 닫기, 데이터베이스 연결 종료 등의 작업에 적합합니다:
connection = None
try:
connection = connect_to_database()
execute_query(connection, "SELECT * FROM users")
except DatabaseError as db_err:
print(f"데이터베이스 오류: {db_err}")
finally:
if connection:
connection.close() # 반드시 실행됨
이러한 보장은 시스템의 장기적 안정성에 필수적입니다.
- 다양한 파일 읽기 모드 활용 파일 내용을 효율적으로 처리하기 위해 다양한 읽기 방식을 선택할 수 있습니다:
# 전체 읽기 (메모리에 한 번에 로드)
with open('large_file.txt', 'r') as f:
all_text = f.read()
# 줄 단위 처리 (메모리 효율적)
with open('log.txt', 'r') as f:
for line in f:
stripped_line = line.strip()
if stripped_line:
process_log_entry(stripped_line)
# CSV 형식 분리
with open('data.csv', 'r') as f:
headers = f.readline().strip().split(',')
data_rows = [line.strip().split(',') for line in f]
문맥에 맞는 방식 선택이 중요합니다.
- 파일 쓰기 모드의 적절한 사용 출력 목적에 따라 모드를 선택해야 합니다:
output_data = ['apple', 'banana', 'cherry']
# 기존 내용에 추가 (추가 모드)
with open('fruits.txt', 'a') as f:
for item in output_data:
f.write(item + '\n')
# 새 파일 생성 또는 덮어쓰기
with open('result.txt', 'w') as f:
f.write('\n'.join(output_data))
'a'(append) vs 'w'(write) 차이를 이해하고 사용해야 합니다.
- 파일 내용을 리스트로 로드하는 패턴 처리용 데이터를 리스트로 저장해 후속 연산에 활용하는 방법:
records = []
with open('employees.csv', 'r') as file:
header = file.readline().strip().split(',')
for line in file:
row = line.strip().split(',')
record_dict = dict(zip(header, row))
records.append(record_dict)
print(records[0]['name']) # 첫 번째 직원 이름 출력
이러한 구조는 데이터 분석 및 검색에 유리합니다.
- 사용자 입력의 안전한 처리 사용자로부터 입력받는 값은 항상 검증되어야 합니다. 다음은 간단한 입력 유효성 검사 예시입니다:
def get_number():
while True:
try:
user_input = input("정수를 입력하세요: ")
return int(user_input)
except ValueError:
print("잘못된 형식입니다. 다시 입력해 주세요.")
number = get_number()
result = 100 / number
print(f"결과: {result}")
이처럼 반복적인 입력 검사를 구현하면 프로그램의 신뢰성이 크게 향상됩니다.