Python 입출력과 예외 처리: input, 파일, UTF-8, try-except, with 정리

Alchemist·2026년 9월 29일

KT AIVLE

목록 보기
34/130

프로그램은 혼자 동작하지 않는다.

사용자에게 값을 입력받고, 파일을 읽고 저장하며, 예상하지 못한 상황에서는 오류를 처리해야 한다.

이 글에서는 Python 3 기준으로 다음 내용을 정리한다.

  • input()과 print()
  • 파일 읽기와 쓰기
  • UTF-8과 문자열
  • JSON과 pickle
  • try-except
  • else, finally
  • raise
  • with

사용자 입력 input()

터미널에서 사용자에게 값을 입력받을 때 input()을 사용할 수 있다.

name = input(
    "이름을 입력하세요: "
)

print(
    f"안녕하세요, "
    f"{name}님."
)

input()의 반환값은 항상 문자열이다.

age = input("나이: ")

print(type(age))
<class 'str'>

숫자로 사용하려면 변환이 필요하다.

age = int(
    input("나이: ")
)

print(age + 1)

숫자가 아닌 값을 입력하면 ValueError가 발생할 수 있다.


출력 print()

name = "kim"
score = 92

print(name)
print(score)

여러 값:

print(
    name,
    score,
)

구분자 변경:

print(
    "python",
    "sql",
    "api",
    sep=" | ",
)

줄바꿈 대신 다른 문자열:

print(
    "loading",
    end="...",
)
print("done")

f-string 출력

user = "kim"
score = 92.345

print(
    f"{user}: "
    f"{score:.1f}점"
)

천 단위 쉼표:

price = 1234567

print(
    f"{price:,}원"
)

파일 쓰기

기본적인 파일 쓰기는 open()으로 할 수 있다.

file = open(
    "result.txt",
    "w",
    encoding="utf-8",
)

file.write(
    "Python 파일 저장 테스트"
)

file.close()

하지만 직접 close()를 호출하는 방식보다는 with 문을 사용하는 편이 안전하다.


with open()을 사용하는 이유

with open(
    "result.txt",
    "w",
    encoding="utf-8",
) as file:
    file.write(
        "Python 파일 저장 테스트"
    )

블록을 빠져나오면 파일이 자동으로 닫힌다.

예외가 발생하더라도 자원 정리가 보장되기 쉬워진다.


파일 모드

모드의미
"r"읽기
"w"새로 쓰기, 기존 내용 덮어쓰기
"a"기존 내용 뒤에 추가
"x"파일이 없을 때 새 파일 생성
"b"바이너리 모드
"t"텍스트 모드

텍스트 파일 읽기

전체 읽기:

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

print(content)

한 줄씩 읽기

큰 파일은 전체를 한꺼번에 읽지 않고 줄 단위로 처리하는 편이 좋다.

with open(
    "access.log",
    "r",
    encoding="utf-8",
) as file:
    for line in file:
        print(
            line.rstrip()
        )

로그 분석에서 자주 볼 수 있는 패턴이다.


pathlib로 파일 다루기

경로를 문자열로만 다루기보다 pathlib.Path를 활용하면 편한 경우가 많다.

from pathlib import Path

path = (
    Path("data")
    / "users.txt"
)

파일 읽기:

content = path.read_text(
    encoding="utf-8"
)

파일 쓰기:

path.write_text(
    "hello",
    encoding="utf-8",
)

UTF-8과 문자열

Python 3의 str은 Unicode 문자열이다.

message = "안녕하세요 👋"

print(type(message))
<class 'str'>

파일이나 네트워크 경계에서는 문자열을 어떤 바이트 형식으로 저장할지 결정해야 한다.

텍스트 파일을 읽고 쓸 때는 인코딩을 명시하는 습관이 좋다.

with open(
    "message.txt",
    "w",
    encoding="utf-8",
) as file:
    file.write(
        "안녕하세요"
    )

문자열과 바이트

문자열:

text = "Python"

인코딩:

data = text.encode(
    "utf-8"
)

print(data)
b'Python'

디코딩:

restored = data.decode(
    "utf-8"
)

네트워크 통신과 바이너리 파일을 다룰 때 str과 bytes의 차이를 이해해야 한다.


JSON 파일 저장

웹 서비스와 데이터 처리에서는 JSON을 자주 사용한다.

import json

user = {
    "name": "kim",
    "age": 30,
}

쓰기:

with open(
    "user.json",
    "w",
    encoding="utf-8",
) as file:
    json.dump(
        user,
        file,
        ensure_ascii=False,
        indent=4,
    )

읽기:

with open(
    "user.json",
    "r",
    encoding="utf-8",
) as file:
    user = json.load(file)

ensure_ascii=False를 사용하면 한글이 읽기 좋은 형태로 저장된다.


pickle

Python 객체를 그대로 직렬화하고 복원할 때 pickle을 사용할 수 있다.

import pickle

data = {
    "name": "kim",
    "skills": [
        "python",
        "sql",
    ],
}

쓰기:

with open(
    "user.pkl",
    "wb",
) as file:
    pickle.dump(
        data,
        file,
    )

읽기:

with open(
    "user.pkl",
    "rb",
) as file:
    restored = (
        pickle.load(file)
    )

신뢰할 수 없는 pickle 파일은 열지 말자

pickle은 단순 데이터 포맷이 아니다.

역직렬화 과정에서 코드 실행으로 이어질 수 있으므로 인터넷에서 받은 파일이나 사용자 업로드처럼 출처를 신뢰할 수 없는 pickle 데이터를 pickle.load()로 열면 안 된다.

외부 시스템과 데이터를 교환할 때는 JSON 같은 형식을 우선 고려하는 편이 안전하다.


문법 오류와 실행 중 예외

문법 자체가 잘못된 경우:

if True
    print("hello")

SyntaxError가 발생한다.

문법은 맞지만 실행 중 문제가 생길 수도 있다.

number = int("hello")

이 경우 ValueError가 발생한다.

이런 실행 중 예외 상황을 try-except로 처리할 수 있다.


try-except

try:
    age = int(
        input("나이: ")
    )
except ValueError:
    print(
        "숫자를 입력해야 합니다."
    )

오류 가능성이 있는 코드는 try에 두고, 처리 가능한 예외는 except에서 지정한다.


예외 객체 확인하기

try:
    number = int("abc")
except ValueError as error:
    print(error)

예외 객체에는 문제를 파악하는 데 도움이 되는 정보가 들어 있다.


여러 예외 처리

try:
    value = input(
        "숫자: "
    )

    number = int(value)

    print(
        100 / number
    )

except ValueError:
    print(
        "정수를 입력해 주세요."
    )

except ZeroDivisionError:
    print(
        "0으로 나눌 수 없습니다."
    )

처리할 수 있는 구체적인 예외를 지정하는 것이 좋다.


여러 예외를 한 번에 처리

try:
    ...
except (
    ValueError,
    TypeError,
) as error:
    print(error)

else

예외가 발생하지 않았을 때만 실행된다.

try:
    number = int(
        input("숫자: ")
    )
except ValueError:
    print("잘못된 입력")
else:
    print(
        f"정상 입력: "
        f"{number}"
    )

성공 경로를 try 블록 밖으로 분리할 수 있다.


finally

예외 발생 여부와 관계없이 실행해야 할 코드를 둔다.

try:
    print("작업 시작")
except Exception:
    print("오류 처리")
finally:
    print("정리 작업")

파일 같은 자원은 보통 with를 사용하는 편이 더 간결하다.


raise

프로그램이 직접 예외를 발생시켜야 할 때 사용한다.

def withdraw(
    balance,
    amount,
):
    if amount <= 0:
        raise ValueError(
            "출금 금액은 "
            "0보다 커야 합니다."
        )

    if amount > balance:
        raise ValueError(
            "잔액이 부족합니다."
        )

    return (
        balance
        - amount
    )

호출:

try:
    balance = withdraw(
        10000,
        15000,
    )
except ValueError as error:
    print(error)

사용자 정의 예외

도메인 의미를 더 명확하게 표현할 수 있다.

class InsufficientBalanceError(
    Exception
):
    pass
def withdraw(
    balance,
    amount,
):
    if amount > balance:
        raise (
            InsufficientBalanceError(
                "잔액이 부족합니다."
            )
        )

    return (
        balance
        - amount
    )

예외를 다시 발생시키기

로그만 남기고 상위 코드가 계속 처리하도록 할 수도 있다.

try:
    ...
except ValueError:
    print(
        "입력 처리 실패"
    )
    raise

raise만 작성하면 현재 예외를 다시 발생시킨다.


파일 처리에서 만나는 예외

from pathlib import Path

path = Path(
    "config.json"
)

try:
    content = path.read_text(
        encoding="utf-8"
    )
except FileNotFoundError:
    print(
        "설정 파일이 없습니다."
    )

파일 작업에서 자주 만나는 예외:

  • FileNotFoundError
  • PermissionError
  • UnicodeDecodeError
  • OSError

EAFP 스타일

Python에서는 먼저 모든 조건을 검사하기보다 일단 실행하고 문제가 생기면 예외를 처리하는 스타일을 자주 볼 수 있다.

예:

try:
    name = user["name"]
except KeyError:
    name = "unknown"

반대로 먼저 검사할 수도 있다.

if "name" in user:
    name = user["name"]
else:
    name = "unknown"

상황에 따라 더 읽기 쉬운 방식을 선택하면 된다.


실전 예제: JSON 설정 읽기

import json
from pathlib import Path


CONFIG_PATH = Path(
    "config.json"
)


def load_config():
    try:
        content = (
            CONFIG_PATH
            .read_text(
                encoding="utf-8"
            )
        )

        return json.loads(
            content
        )

    except FileNotFoundError:
        print(
            "config.json 파일이 "
            "없습니다."
        )
        return {}

    except (
        json.JSONDecodeError
    ) as error:
        print(
            f"JSON 형식 오류: "
            f"{error}"
        )
        return {}

사용:

config = load_config()

api_url = config.get(
    "api_url",
    "http://localhost:8000",
)

print(api_url)

이 예제에는 파일 읽기, UTF-8, JSON 파싱, 예외 처리, 기본값 사용이 함께 들어 있다.


피하고 싶은 예외 처리 패턴

오류를 조용히 무시하기

try:
    ...
except:
    pass

문제가 발생했다는 사실 자체가 사라져 디버깅이 어려워진다.


지나치게 넓은 try

try:
    # 서로 다른 작업 수십 줄
    ...
except ValueError:
    ...

어디서 문제가 발생했는지 파악하기 어려울 수 있다.

가능하면 예외 가능성이 있는 범위를 좁게 잡는 편이 좋다.


핵심 정리

  • Python 3에서 사용자 입력은 input()으로 받고 결과는 문자열이다.
  • 파일 작업에는 with open(..., encoding="utf-8") 패턴이 안전하고 읽기 쉽다.
  • Python 3의 str은 Unicode 문자열이며 파일과 네트워크 경계에서는 인코딩을 이해해야 한다.
  • pickle은 편리하지만 신뢰할 수 없는 데이터를 역직렬화하면 위험할 수 있다.
  • try-except는 실행 중 발생할 수 있는 예외를 처리한다.
  • else는 예외가 없을 때, finally는 예외 여부와 관계없이 실행된다.
  • raise를 사용하면 프로그램 규칙 위반을 명시적인 예외로 표현할 수 있다.
  • 예외는 가능한 한 구체적으로 처리하고 오류를 조용히 삼키지 않는 것이 좋다.
profile
html_programming_language

0개의 댓글