Skip to content
This page has been auto-translated and may contain errors.View in English

파일과 예외

docs.scrimba.com

실제로 작동하는 대부분의 프로그램은 파일 시스템에 접근합니다: 설정을 읽고, 결과를 쓰고, 데이터를 로드합니다. 그리고 무언가 잘못되면 Python은 예외를 발생시키는데, 이는 예상치 못한 일이 발생했음을 알리는 신호입니다. 이 장에서는 두 가지를 모두 다룹니다: 파일에서 데이터를 읽고 쓰기, 그리고 크래시 대신 오류를 우아하게 처리하는 코드 작성하기.

파일 I/O와 예외 처리는 프로그램을 신뢰성 있게 만드는 두 가지 메커니즘입니다. open()은 파일 시스템에 접근할 수 있게 해주고, with은 오류 발생 시에도 파일이 제대로 닫히도록 보장합니다. try/except는 특정 예외 유형을 잡아서 크래시 대신 복구할 수 있게 해줍니다. 함께 사용하면 이들은 무인으로 실행되는 모든 스크립트의 기초입니다.

파일과 예외는 스크립트가 지저분한 현실 세계와 만나는 곳입니다: 존재하지 않는 경로, 가득 찬 디스크, 파싱되지 않는 입력. open()은 파일 객체를 반환하고, with은 코드 내에서 예외가 발생하더라도 파일이 닫히도록 보장합니다. 예외는 Python이 실패를 알리는 방식입니다: 예외가 발생하면, 그것을 유발한 호출들을 거슬러 올라가며 이동하고, 처음 일치하는 except가 이를 처리합니다. 이 장이 강조하는 규율은 실제로 복구할 수 있는 특정 실패만 잡고 나머지는 드러나게 내버려두는 것이며, 모든 것을 삼켜서 조용히 실패하는 스크립트를 배포하는 것이 아닙니다.

파일 열기

open()은 파일을 열고 읽거나 쓸 수 있는 객체를 반환합니다. 경로와 파일로 수행하려는 작업(읽기, 쓰기 또는 추가)을 지정합니다. 완료되면 항상 파일을 닫으세요. with 문은 이를 자동으로 수행합니다.

open(path, mode)은 파일 객체를 반환합니다. 모드 문자열은 접근을 제어합니다: "r"은 읽기, "w"는 쓰기(먼저 파일을 지움), "a"는 끝에 추가합니다. 파일이 텍스트가 아닐 때 "b"를 추가하여 바이너리 모드를 사용합니다. 처음부터 설정할 가치가 있는 한 가지: 텍스트 파일에 encoding="utf-8"을 전달하세요. 이를 지정하지 않으면 Python은 머신의 기본값을 사용하므로 랩톱에서 잘 읽히는 파일이 다른 사람의 컴퓨터에서는 깨진 문자로 올 수 있습니다.

open()은 OS에 파일 핸들을 요청하고 이를 감싼 파일 객체를 반환합니다. 모드는 접근("r", "w", "a")과 텍스트인지 원본 바이트인지("b")를 모두 선택합니다. 사람들을 곤경에 빠뜨리는 매개변수는 encoding입니다: 디스크의 바이트가 어떻게 문자로 매핑되는지 결정합니다. 설정하지 않으면 Python은 머신의 로케일 기본값으로 돌아가는데, 이는 플랫폼마다 다르므로 한 머신에서 작성된 파일이 다른 머신에서 잘못 디코딩될 수 있습니다. 특정한 이유가 없는 한 항상 텍스트에 encoding="utf-8"을 전달하세요. 그러면 대부분의 놀라움이 사라집니다.

python
f = open("data.txt", "r")    # "r" = 읽기
content = f.read()
f.close()

"r"모드입니다:

모드의미
"r"읽기. 파일이 반드시 존재해야 함. 기본 모드.
"w"쓰기. 파일을 생성하거나 덮어씀.
"a"추가. 지우지 않고 끝에 추가.
"x"생성. 파일이 이미 존재하면 실패.
"r+"읽기와 쓰기.
"b"바이너리. 임의의 모드에 추가: "rb", "wb".

작업이 끝나면 항상 .close()를 호출하세요. 잊으면 파일이 열린 채로 남아 아직 쓰기를 기다리는 데이터가 손실될 수 있습니다. 이를 처리하는 안정적인 방법은 with 문입니다.

Juno파일 열기open(path, mode)는 파일 객체를 주고, 모드는 수행할 작업을 나타냅니다: "r"은 읽기, "w"는 쓰기(거기 있던 것을 지움), "a"는 끝에 추가. 저는 한 번 "w"를 실수로 사용하여 파일을 잃었는데, "a"를 의도했었으므로 입력하기 전에 모드를 그려 보세요.
Juno파일 열기open(path, mode)는 파일 객체를 반환합니다: "r"은 읽기, "w"는 덮어쓰기, "a"는 추가, 그리고 바이너리는 "b"입니다. 텍스트 파일에 encoding="utf-8"을 전달하여 모든 머신에서 동일하게 읽히도록 하고, 닫기는 with에 맡기세요.
Juno파일 열기open()은 OS 핸들 위의 파일 객체를 제공합니다. 모드는 접근과 텍스트 대 바이트를 설정합니다. 플랫폼 간에 잘 이동하지 않는 설정은 `encoding`입니다: 설정하지 않으면 머신의 로케일 기본값을 상속하므로 텍스트에는 `encoding="utf-8"`을 습관으로 만드세요. .close()를 손으로 사용할 때 `with`이 대신할 수 있으면 의존하지 마세요.

with

with open(...)은 파일을 관리하며, 들여쓴 블록이 끝날 때 자동으로 닫습니다. 오류가 발생해도 닫힙니다. 항상 수동 open()/close() 대신 with open(...)을 사용하세요. 더 안전하고 표준입니다.

with는 Python의 컨텍스트 매니저 문법입니다: 블록이 시작할 때 "설정" 호출을 하고 끝날 때 "정리" 호출을 하는 작은 프로토콜입니다. 파일의 경우 설정은 열린 파일을 반환하고 정리는 이를 닫습니다. 이점은 블록 내에서 예외가 발생하더라도 정리가 실행되어서, 모든 파일 주위에 try/finally를 작성하지 않고도 이를 얻을 수 있다는 것입니다.

컨텍스트 매니저(와 함께 with를 놓을 수 있는 모든 객체)는 두 가지 행동을 정의합니다: 들어올 때 할 일과 나갈 때 할 일. with open(...) as f는 들어올 때 단계를 실행하고, 파일을 f에 바인딩하고, 나갈 때 단계를 등록합니다. 이는 Python이 블록이 끝날 때, 정상 또는 예외가 발생하여 끝났든 상관없이 실행합니다. 파일의 경우 나갈 때 단계는 close()입니다. 두 가지 실용적인 사항. 한 문에서 여러 파일을 열 수 있습니다, with open(a) as f, open(b) as g:, 둘 다 순서대로 닫힙니다. 그리고 나갈 때 단계는 예외를 삼키기로 선택할 수 있는데, 이것이 contextlib.suppress(FileNotFoundError) 같은 헬퍼가 작동하는 방식이므로, with 블록이 컨텍스트 매니저가 작성되었을 때 오류를 조용히 흡수할 수 있다는 것을 알고 있어야 합니다.

python
with open("data.txt", "r") as f:
    content = f.read()

# f는 여기서 닫혀 있음, 보장됨

with는 Python의 컨텍스트 매니저 문법입니다: 설정과 정리 코드를 실행하며, 여기서 파일을 열고 안정적으로 닫습니다. 내부적으로 어떻게 작동하는지 알 필요는 없습니다.

Junowith 문with open(...) as f는 파일을 열고, 들여쓴 블록 내에서 f로 작업하게 하고, 블록이 끝날 때 닫습니다. 뭔가 잘못되어도 닫힙니다. .close()를 직접 호출하는 대신 매번 이것을 사용하세요. 하나 덜 잊을 일입니다.
Junowith 문with는 블록이 열릴 때 설정을 실행하고 닫힐 때 정리를 실행하며, 예외 발생 시에도 정리가 실행됩니다. 파일의 경우 정리는 `close()`이므로, 수동 `open()`/`close()` 대신 `try`/`finally` 안전을 얻습니다.
Junowith 문 컨텍스트 매니저는 블록이 완료되든 예외가 발생하든 파일을 닫으므로, `with`은 손수 작성한 `try`/`finally`를 정리 대신 사용합니다. 하나의 `with`으로 여러 개를 한 번에 엽니다. 나갈 때 단계가 예외를 억제할 수 있다는 점을 명심하세요. `contextlib.suppress`가 정확히 이것이 무엇이고, `with` 블록이 오류를 삼킬 때 인식할 가치가 있습니다.

파일 읽기

세 가지 읽기 방법이 있습니다. .read()는 전체 파일을 하나의 문자열로 로드합니다. .readline()은 한 줄을 읽습니다. 파일 객체를 직접 반복하면 한 줄씩 읽는데, 이는 한 번에 모든 것을 메모리에 로드하지 않으므로 큰 파일에 가장 효율적입니다.

.read()는 전체 파일을 메모리에 하나의 문자열로 로드합니다. .readline()은 개행 포함하여 한 줄을 읽습니다. .readlines()는 모든 줄의 리스트를 반환합니다. 크기가 클 수 있는 것의 경우 파일 객체를 직접 for line in f로 반복합니다: 한 번에 한 줄을 가져오고 다음 줄 이전에 버려서 메모리가 파일 크기와 관계없이 평탄하게 유지됩니다.

.read()는 전체 파일을 하나의 문자열로 반환하므로 메모리 비용이 파일 크기로 확장됩니다: 설정에는 괜찮고, 다중 기가바이트 로그의 경우 문제입니다. 파일 객체를 반복하면, for line in f, 각 단계에서 한 줄을 읽고 다음 줄 이전에 버립니다. 메모리는 길이와 관계없이 평탄하게 유지됩니다. 이것은 크기를 알 수 없는 파일에 기본 설정할 패턴입니다. .readlines()는 편리해 보이지만 한 번에 모든 줄의 리스트를 생성하므로, .read()와 동일한 메모리 용량을 가지므로 루프 위에서 아무것도 얻지 못합니다. 경전 메모리 부족 버그를 방지하는 규칙: for line in f 루프를 먼저 사용하고, 파일이 작다는 것을 알 때만 .read()를 호출하세요.

python
with open("data.txt", "r") as f:
    content = f.read()          # 전체 파일을 하나의 문자열로

with open("data.txt", "r") as f:
    first_line = f.readline()   # 한 번에 한 줄

with open("data.txt", "r") as f:
    lines = f.readlines()       # 리스트의 줄, 각 줄이 "\n"으로 끝남

큰 파일의 경우 한 번에 모든 것을 로드하는 것보다 한 줄씩 읽는 것이 더 효율적입니다:

python
with open("big_file.txt", "r") as f:
    for line in f:              # 파일을 직접 반복, 메모리 효율적
        print(line.strip())     # strip()은 뒤의 개행을 제거

파일 객체를 직접 반복(for line in f)하는 것이 큰 파일을 읽는 가장 효율적인 방법입니다.

Juno파일 읽기.read()는 전체 파일을 하나의 문자열로 제공하고, .readline()은 단일 줄을 제공합니다. 파일이 클 수 있을 때 대신 `for line in f`로 루프하세요. 한 번에 한 줄씩 읽으므로 메모리에 모든 것을 로드하지 않습니다. 각 줄의 `.strip()`은 뒤의 개행을 지웁니다.
Juno파일 읽기.read()는 한 번에 모든 것을 로드하는데, 작은 파일에는 괜찮고 큰 파일에는 위험합니다. 기본값은 `for line in f`: 한 번에 한 줄씩 스트리밍하므로 크기에 관계없이 메모리가 평탄합니다. .readlines()는 깔끔해 보이지만 `.read()`만큼 메모리를 사용합니다.
Juno파일 읽기.read().readlines() 모두 전체 파일을 메모리에 가지므로 파일 크기로 확장합니다. `for line in f` 루프는 한 번에 한 줄씩 읽고 버려서 메모리를 평탄하게 유지합니다. 이것이 먼저 크기를 정하지 않은 파일에서 메모리 부족 놀라움을 방지합니다. 기본값으로 루프를 사용하고, 작다는 것을 알 때만 `.read()`를 사용하세요.

파일 쓰기

"w" 모드는 존재할 경우 파일 전체를 덮어씁니다. "a" 모드는 끝에 추가합니다. .write()는 자동으로 개행을 추가하지 않습니다. 각 줄 끝에 "\n"을 명시적으로 포함하세요. 여러 줄을 한 번에 쓰려면 "\n".join()으로 결합하세요.

.write(s)는 하나의 문자열을 쓰고, print()와 달리 아무것도, 공백도, 개행도 추가하지 않으므로 "\n"을 직접 넣습니다. .writelines(lines)는 리스트의 각 문자열을 구분 기호 없이 다시 쓰므로, 개행이 이미 문자열에 있어야 합니다. 모드를 조심하세요: "w"는 무언가를 쓰기 이전에 파일을 열자마자 지우므로, "a"를 의도했을 때 "w"를 사용하면 이전 내용이 버려집니다.

실제 버그를 유발하는 쓰기에 대한 두 가지. 첫째, 작성한 데이터가 반드시 즉시 디스크에 도달하지는 않습니다: OS는 버퍼에 보관하고 일괄로 씁니다. .close()(이는 with이 호출)이 모든 것이 디스크에 있는지 확인합니다. 장시간 실행되는 프로세스가 쓰고 닫지 않으면 출력의 끝이 버퍼에 남아 있을 수 있고 크래시 시 손실될 수 있으므로 파일을 닫거나 데이터가 안전해야 하는 지점에서 .flush()를 호출하세요. 둘째, 모드는 피해를 결정합니다: "w"는 열자마자 파일을 비우므로, 경고 없이 "w"를 원하지 않을 때 도달하면 이전 내용이 파괴됩니다. .writelines(lines)는 자체 구분 기호를 추가하지 않으므로 개행이 문자열에 이미 있어야 합니다.

python
with open("output.txt", "w") as f:
    f.write("Hello, world\n")

with open("output.txt", "a") as f:
    f.write("Another line\n")

"w"는 존재하면 파일 전체를 덮어씁니다. "a"는 끝에 추가합니다.

f.write()는 자동으로 개행을 추가하지 않으므로 "\n"을 명시적으로 포함하세요. 여러 줄을 한 번에 쓰려면:

python
lines = ["Line one", "Line two", "Line three"]

with open("output.txt", "w") as f:
    f.write("\n".join(lines) + "\n")
Juno파일 쓰기"w"는 파일을 생성하거나 덮어씁니다. "a"는 끝에 추가합니다. .write()는 개행을 추가하지 않으므로 각 줄에 `"\n"`을 직접 붙이세요. 일괄 줄의 경우 `"\n".join(lines)`이 먼저 이들을 합칩니다.
Juno파일 쓰기"w"는 열자마자 파일을 지우고, "a"는 추가하므로 잘못된 것을 선택하면 조용히 이전 내용을 버립니다. .write()는 아무것도 추가하지 않습니다. 개행도 없으므로 `"\n"`을 직접 공급하세요. .writelines()는 리스트를 구분 기호 없이 씁니다. 개행이 문자열에 이미 있어야 합니다.
Juno파일 쓰기"w"는 열자마자 파일을 비우므로, `"a"` 대신 이것을 사용하면 경고 없이 이전 내용이 파괴됩니다. 쓰기는 `.close()`(이는 `with`이 호출)이 플러시할 때까지 버퍼에 남아 있습니다. 프로세스가 쓰고 닫지 않으면 크래시 시 끝이 손실될 수 있습니다. .write().writelines()는 개행을 추가하지 않습니다. 그것은 당신 몫입니다.

예외

Python이 처리할 수 없는 문제를 만나면 예외를 발생시킵니다: 무엇이 잘못되었고 어디에서 발생했는지 설명하는 오류. 이를 처리하지 않으면 프로그램이 크래시하고 추적을 인쇄합니다. 아래 표는 마주칠 가장 일반적인 예외를 보여줍니다.

예외는 객체이고, 예외는 가족 트리를 형성합니다: FileNotFoundError 같은 구체적인 것들은 더 광범위한 것(OSError)의 일종이고, 잡는 거의 모든 것은 Exception의 일종입니다. 이 트리가 잡기를 작동하게 합니다: 부모 유형을 잡으면 자식도 잡습니다. 하나가 발생하면 Python은 현재 줄을 버리고 그것을 유발한 호출들을 거슬러 올라가며 일치하는 except를 찾습니다. 없으면 프로그램이 멈추고 추적을 인쇄합니다: 오류가 발생한 곳에서 최상위까지 호출의 목록.

예외는 클래스 계층(유형의 가족 트리)을 형성하고, 이 트리는 이들을 잡을 때 전체 게임입니다: 부모 유형의 except는 아래의 모든 유형도 잡으므로 except Exception은 넓은 망이고 except FileNotFoundError는 좁은 망입니다. 기억할 만한 트리의 한 부분은 KeyboardInterrupt(사용자가 Ctrl-C를 누름)와 SystemExit(깔끔한 종료 요청)가 Exception 아래가 아니라 옆에 있다는 것입니다. 이것은 의도적입니다: 광범위한 except Exception이 이 둘을 통과하게 하므로 Ctrl-C는 여전히 프로그램을 멈추게 합니다. 이것은 정확히 except Exception을 사용하고 except:를 절대 사용하지 않는 이유인데, 이 둘도 잡아서 사용자를 나가려고 하는 프로그램 안에 갇히게 합니다.

마주칠 일반적인 예외:

예외발생하는 시기
FileNotFoundErroropen()이 파일을 찾을 수 없음
ValueError함수가 올바른 유형이지만 잘못된 내용의 값을 받음, 예: int("abc")
TypeError완전히 잘못된 유형, 예: "hello" + 5
KeyError딕셔너리 키가 존재하지 않음
IndexError리스트 인덱스가 범위를 벗어남
ZeroDivisionError0으로 나누기
AttributeError객체가 그 속성이나 메서드를 가지고 있지 않음
Juno예외 예외는 뭔가 잘못되었다고 Python이 말하는 것입니다. `FileNotFoundError`, `ValueError`, `KeyError`, `TypeError`를 계속 만날 것입니다. 처리되지 않으면 프로그램을 멈추고 추적을 인쇄하는데, 이것은 경각심을 주지만 정말로는 깨진 곳을 직접 가리키는 지도입니다.
Juno예외 예외는 가족 트리에 배열되고, 부모 유형을 잡으면 자식도 잡습니다. 하나가 발생하면 Python이 호출들을 거슬러 올라가며 일치하는 `except`를 찾고, 없으면 추적을 인쇄합니다. 일반적인 이름 `ValueError`, `KeyError`, `FileNotFoundError`를 아는 것이 올바른 것을 잡게 합니다.
Juno예외 유형 계층이 전체 지점입니다: `except Exception`은 넓은 망이고, 구체적인 유형은 좁은 망입니다. `KeyboardInterrupt`와 `SystemExit`는 `Exception` 아래가 아니라 옆에 있으므로 Ctrl-C는 여전히 `except Exception`에서 벗어납니다. 이것이 `except:` 대신 이것을 사용하는 이유인데, 이것이 사용자를 나가려고 하는 프로그램 안에 가둡니다.

try / except

실패할 수 있는 코드를 try 블록으로 감싸세요. 예외가 발생하면 일치하는 except 블록이 크래시 대신 처리합니다. 어떤 예외를 잡을지 구체적으로 지정하세요: except:로 모든 것을 잡으면 실제 버그를 숨깁니다.

try/except는 특정 예외 유형을 잡고 크래시 대신 핸들러를 실행합니다. 유형을 이름짓는 것이 중요합니다: 실제로 복구할 수 있는 것을 잡아서 관련 없는 버그가 충분히 드러나게 하세요. as e로 예외를 바인딩하면 메시지를 읽을 수 있습니다. 서로 다른 유형을 위해 여러 except 절을 나열하고 Python은 맞는 첫 번째를 사용합니다.

except SomeType은 발생한 예외가 그 유형이거나 그 하위 유형(트리 아래 더 구체적인 일종)일 때 일치합니다. 이것이 부모를 잡으면 모든 자식도 잡는 이유입니다. Python은 각 except를 위에서 아래로 시도하고 첫 번째 일치에서 멈추므로 특정 것들을 먼저 일반적 것들 마지막으로 정렬하세요: 먼저 놓인 광범위 절이 모든 것을 잡으면 아래 좁은 것들은 절대 실행되지 않습니다. try/except가 버그를 숨기지 않게 하는 규율: 처리할 수 있는 가장 좁은 유형을 잡고, try 본문을 실제로 발생할 수 있는 한 줄로 유지하고, 나머지는 전파하게 하세요. 20줄 주위에 try를 감싸면 한 예상된 실패를 19개의 예상치 못한 것들 위의 마스크로 전환합니다.

python
try:
    value = int("abc")
except ValueError:
    print("That's not a valid number")

어떤 예외를 잡을지 구체적으로 지정하세요. 모든 것을 except:로 잡으면 버그를 숨깁니다:

python
# 나쁨, 프로그래머 실수를 포함한 모든 것을 잡음
try:
    result = do_something()
except:
    pass

# 좋음, 예상하고 실제로 처리할 수 있는 것만 잡음
try:
    result = do_something()
except FileNotFoundError:
    print("File not found")
Junotry / except 실패할 수 있는 코드를 `try` 블록으로 놓고, 일치하는 `except`가 프로그램이 크래시하는 대신 실행됩니다. `ValueError` 같은 예상한 예외의 이름을 지정하세요. `except:`는 모든 것을 잡아서 실제 버그를 숨깁니다. 실제로 다룰 수 있는 것을 잡고 나머지는 드러나게 하세요.
Junotry / except `try`/`except`는 명명된 유형을 잡고 크래시 대신 복구합니다. `as e`는 메시지를 줍니다. 처리할 수 있는 구체적인 유형을 잡아서 관련 없는 버그가 여전히 드러나게 하세요. `try` 본문을 작게 유지하세요. 20줄을 감싸면 한 예상된 실패를 나머지를 위의 마스크로 전환합니다.
Junotry / except `except`는 유형과 모든 하위 유형과 일치하고, Python은 맞는 첫 번째 절을 택하므로 특정을 먼저 일반 것들 마지막으로 정렬하세요. 광범위한 것이 좁은 것을 위에 놓으면 좁은 절이 죽은 코드입니다. 가장 좁은 유형을 잡고, `try`를 실제로 발생할 수 있는 줄로 유지하고, 나머지는 전파하게 하세요. 넓은 `try`는 처리하려던 실패보다 훨씬 많이 숨깁니다.

여러 예외 잡기

서로 다른 오류 유형을 별도의 except 블록에서 처리하거나 튜플을 사용하여 한 블록에서 여러 유형을 잡을 수 있습니다. as e 부분은 오류 메시지에 접근할 수 있게 합니다.

여러 except 절을 서로 다른 유형에 대해 쌓고 Python이 위에서 아래로 확인하며 맞는 첫 번째를 택합니다. 여러 유형을 같은 방식으로 처리하려면 튜플로 그룹화하세요: except (ValueError, ZeroDivisionError). as e는 예외를 바인딩하므로 메시지를 읽을 수 있습니다. 순서가 중요합니다: 구체적인 유형을 일반 것들 이전에 놓으세요. 광범위한 것이 위에 있으면 아래의 것들은 절대 실행되지 않습니다.

순서가 함정입니다. Python은 유형이 일치하는 첫 번째 except를 택하고, 광범위한 유형은 좁은 친척과 일치하므로 except ValueError 위에 except Exception을 나열하면 ValueError 절은 절대 실행되지 않는 죽은 코드입니다. 특정을 먼저, 일반을 나중에. 같은 응답을 공유하는 유형을 except (A, B) as e로 그룹화하세요. 오류만 잡고 컨텍스트를 추가하여 전달할 때는 from으로 새것을 발생시키세요: raise ValueError("bad config") from e. 이것은 원본 오류를 새것 아래에 추적 보고에서 보이게 하므로 높은 수준의 의미와 낮은 수준의 원인을 보여줍니다. 일반적인 규칙: 컨텍스트를 추가하기 위해 다시 발생시키고, 숨기기 위해 절대 발생시키지 마세요.

python
try:
    data = int(user_input)
    result = 100 / data
except ValueError:
    print("Not a number")
except ZeroDivisionError:
    print("Can't divide by zero")

또는 튜플에서 여러 개를 잡으세요:

python
except (ValueError, ZeroDivisionError) as e:
    print(f"Input error: {e}")

as e는 예외 객체를 이름에 바인딩하므로 메시지를 검사할 수 있습니다.

Juno여러 예외 잡기 각 오류 유형에 자체 `except` 블록을 주고, Python이 위에서 아래로 실행하여 맞는 첫 번째를 실행합니다. 몇 개를 같은 방식으로 처리하려면 튜플에 나열하세요: `except (ValueError, ZeroDivisionError)`. `as e` 부분은 오류 객체를 주므로 메시지를 인쇄할 수 있습니다.
Juno여러 예외 잡기 여러 `except` 절은 위에서 아래로 실행하고 첫 번째 일치가 이기므로 구체적인 유형을 일반 것들 이전에 나열하세요. 광범위한 것이 좁은 것을 그림자 내면 나머지는 죽은 코드입니다. 튜플 `except (A, B)`는 여러 개를 한 블록으로 처리합니다. `as e`는 예외를 바인딩하므로 메시지를 읽을 수 있습니다.
Juno여러 예외 잡기 특정 절이 먼저 옵니다: 광범위한 것이 좁은 것 위에 있으면 좁은 절이 죽은 코드입니다. 공유된 응답을 `except (A, B) as e`로 그룹화하세요. 컨텍스트를 추가하기 위해 다시 발생시킬 때 `raise NewError("...") from e`를 사용하여 원본 원인이 새 오류 아래 추적에서 보이게 하세요. 사라지지 않습니다.

else와 finally

else는 예외가 발생하지 않은 경우에만 실행됩니다. finally는 예외 발생 여부와 관계없이 항상 실행됩니다. finally는 무엇이 일어나든 상관없이 발생해야 하는 정리에 유용합니다.

elsetry 본문이 깔끔하게 끝난 경우에만 실행되는 코드를 가집니다. 이를 try 밖으로 유지하면 의도가 명확합니다: 성공 경로 코드를 우연히 except 절 중 하나로 트리거하지 않습니다. finally는 정리 보장이며 성공 후, 잡은 예외 후, 잡지 않은 예외 후, 심지어 tryreturn 후에도 실행됩니다. 이것이 발생해야 하는 리소스를 놓아주는 올바른 집입니다.

elsetry 본문이 깔끔하게 끝난 경우에만 실행되어 성공 경로 코드를 try 밖으로 유지하므로 우연히 except 절을 트리거할 수 없습니다. finally는 무조건 실행됩니다: 성공 후, 잡은 예외 후, 잡지 않은 예외 후, 심지어 tryreturn 후에도. 이 마지막 경우는 알 가치가 있는 함정을 숨깁니다: finally 내의 returntryreturn을 무시하고, 더 나쁘게는 전파하는 예외를 조용히 삼켜서 오류가 흔적 없이 사라집니다. finally를 정리에만 유지하세요. 절대 거기에서 return하지 마세요. 파일의 경우 with이 이미 이 보장을 주므로 주로 컨텍스트 매니저가 없는 리소스를 잡을 때 finally를 사용하세요.

python
try:
    with open("data.txt") as f:
        content = f.read()
except FileNotFoundError:
    print("File not found, using defaults")
    content = ""
else:
    print("File loaded successfully")
finally:
    print("Done attempting to load file")   # 항상 실행됨

finally는 파일용 with을 이미 사용하고 있어도 정리(연결 닫기, 락 해제)에 가장 유용합니다.

Junoelse와 finally `else`는 `try`가 아무것도 발생하지 않은 경우에만 실행되며, 성공 경로의 나머지 부분을 깔끔하게 놓는 곳입니다. `finally`는 매번 실행되며, 예외 여부와 관계없이, 무엇이 일어나든 상관없이 발생해야 하는 정리의 부분입니다. 파일의 경우 `with`이 이미 정리를 포함하므로 기대하는 것보다 `finally`를 덜 사용할 것입니다.
Junoelse와 finally `else`는 깨끗한 경로에만 실행되어 성공 코드를 `try` 밖으로 유지하므로 자체 `except`를 트리거할 수 없습니다. `finally`는 무조건 실행되며, `return` 너머도 포함하므로 정리 보장입니다. 파일의 경우 `with`이 이미 이를 수행하므로 `finally`를 컨텍스트 매니저가 없는 리소스에 저장하세요.
Junoelse와 finally `else`는 `try` 밖으로 유지되어 `except` 절을 발화할 수 없는 깨끗한 경로 코드입니다. `finally`는 무엇이든 실행되지만 절대 거기에서 `return`하지 마세요: 이것은 전파하는 예외를 무시하고 오류가 조용히 사라집니다. `finally`를 정리에만 유지하고 주로 `with`이 없을 때만 사용하세요.

raise

raise로 예외를 직접 발생시킬 수 있습니다. 이것은 함수가 조용히 잘못된 값을 반환하는 대신 명확하게 문제를 발신자에게 알리는 방법입니다.

raise ExceptionType("message")는 자신의 코드에서 예외를 발생시키는데, 이것은 함수가 잘못된 입력 대신 다루지 않는 방법을 보고하는 것입니다. except 블록 내에서 raise(인수 없음)는 잡은 예외를 다시 발생시키는데, 로그를 남기거나 반응하고 여전히 올라가게 하려고 할 때 유용합니다. 의도적으로 발생시키면 함수의 실패 모드가 계약의 일부가 되어 발신자가 이름으로 이들을 잡고 처리할 수 있습니다.

raise SomeError("message")는 의도적으로 실패를 보고하는데, 함수가 사용할 수 없는 입력을 받을 때 올바른 이동입니다: 큰 소리로 거부하면 나중에 미스터리로 나타나는 잘못된 답을 반환하는 것을 이깁니다. except 내에서 raise는 원본 추적을 유지하면서 잡은 예외를 다시 발생시켜서 로그를 남기고 다시 발생시킬 때 어디에서 왔는지 지우지 않습니다. 낮은 수준의 오류를 도메인 하나로 변환하려고 할 때 from을 사용하세요: raise ConfigError("bad settings") from e는 원본을 보고서에서 새것 아래에 유지하므로 독자는 의미와 원인 둘 다 봅니다. 엄지손가락의 규칙: 컨텍스트를 추가하기 위해 다시 발생시키고, 이를 숨기기 위해 절대 발생시키지 마세요.

python
def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

이것은 함수가 기대하는 것을 명시하고 문제를 발신자에게 명확하게 알립니다.

Junoraise `raise ExceptionType("message")`는 자신의 코드가 큰 소리로 문제를 깃발 세우고 잘못된 값으로 계속하는 대신 합니다. 함수가 "이것을 다룰 수 없습니다"라고 말하는 방법이므로 발신자가 다룹니다. 명확하고 빠른 것이 나중에 드러나는 잘못된 답을 이깁니다.
Junoraise `raise ExceptionType("message")`는 코드에서 실패를 보고하는데, 함수의 오류 경우를 발신자가 사용하는 방식의 일부로 만듭니다. `except` 내의 `raise`는 다시 발생시키고 원본 추적을 유지하므로 로그를 남기고 여전히 올라가게 할 수 있습니다.
Junoraise 입력을 받을 수 없을 때 의도적으로 발생시키세요: 큰 소리로 거부하면 나중에 미스터리로 나타나는 잘못된 답을 이깁니다. `raise`는 원본 추적을 유지하면서 다시 발생시킵니다. 낮은 수준의 오류를 도메인 하나로 변환하려면 `raise DomainError("...") from e`를 사용하여 원인이 여전히 보이게 하세요. 컨텍스트를 추가하기 위해 다시 발생시키고 이를 묻기 위해 절대 발생시키지 마세요.

사용자 정의 예외 클래스

더 큰 프로그램의 경우 Exception을 상속하여 자신의 예외 유형을 정의할 수 있습니다. 이것은 발신자가 다른 종류의 오류와 별도로 자신의 특정 오류를 잡을 수 있게 합니다.

사용자 정의 예외는 Exception을 상속하는 자신의 클래스입니다(새로운 예외 종류가 되어 같은 가족 트리에 맞습니다). 보상은 발신자가 정확하게 잡을 수 있는 이름이며, 내장 오류와 분리됩니다. 관련된 실패 그룹의 경우 한 기본 클래스를 만들고 각 구체적인 경우에 서브클래싱하세요: 발신자가 전체 그룹을 처리하기 위해 기본을 잡거나 하나를 단독으로 잡으려면 서브클래스를 잡으세요.

BaseException 아닌 Exception을 서브클래싱하세요(Ctrl-C와 종료도 포함하는 루트인데, 오류를 이것과 그룹화하지 않아야 합니다). 내장과 재사용하는 대신 자신의 유형을 정의하는 이유는 가족 트리입니다: PaymentError 같은 기본을 만들고 각 모드(InsufficientFundsError, CardDeclinedError)에 서브클래싱하고, 발신자가 전체 카테고리를 처리하기 위해 PaymentError를 잡거나 한 경우에 서브클래스를 잡을 수 있으며, 메시지 문자열을 파싱하지 않습니다. 클래스에 관련 필드(계정, 금액)를 가진 __init__을 주세요. 그러면 핸들러가 텍스트를 스크래핑하는 대신 코드에서 무엇이 일어났는지 검사할 수 있어서, 오류가 프로그램이 행동할 수 있는 것이 되고, 인쇄만 하는 것이 아닙니다.

python
class InsufficientFundsError(Exception):
    pass

class BankAccount:
    def __init__(self, balance):
        self.balance = balance

    def withdraw(self, amount):
        if amount > self.balance:
            raise InsufficientFundsError(
                f"Cannot withdraw {amount}, balance is {self.balance}"
            )
        self.balance -= amount
python
try:
    account.withdraw(1000)
except InsufficientFundsError as e:
    print(f"Transaction declined: {e}")
Juno사용자 정의 예외 클래스 `Exception`을 상속하여 자신의 오류 유형을 만드세요. 빈 클래스 본문만으로도 충분히 시작합니다. 발신자가 내장 오류와 별도로 그 자체의 이름을 잡을 수 있게 합니다. 프로그램의 문제가 그 자체로 서 있습니다.
Juno사용자 정의 예외 클래스 사용자 정의 예외는 `Exception`을 상속하는 클래스이며, 발신자가 정확하게 잡을 수 있는 이름을 줍니다. 관련된 실패의 경우 한 기본 클래스를 만들고 경우마다 서브클래싱하세요: 전체 그룹을 처리하기 위해 기본을 잡거나, 하나를 위해 서브클래스를 잡으세요. 메시지 문자열 너머 세부 정보를 운반하기 위해 필드를 추가할 수 있습니다.
Juno사용자 정의 예외 클래스 `Exception`을 서브클래싱하세요. `BaseException` 아닙니다. Ctrl-C와 함께 오류가 그룹화되지 않습니다. 자신의 것을 정의하는 이유는 트리입니다: `PaymentError` 기본이 특정 서브클래스를 가지면 발신자가 카테고리나 한 경우를 메시지 텍스트를 스크래핑하지 않고 처리할 수 있습니다. 관련 필드를 `__init__`에 놓으세요. 그러면 핸들러가 데이터로 행동하지만, 문자열이 아닙니다.

JSON

JSON은 모든 것이 말하는 형식입니다: API, 설정 파일, 데이터 내보내기. Python의 json 모듈은 이를 직접 처리합니다. json.load()는 파일에서 JSON을 읽어 Python 딕셔너리 또는 리스트로 변환합니다. json.dump()는 딕셔너리 또는 리스트를 다시 파일로 JSON으로 씁니다.

쌍은 접하는 것에 의해 분할됩니다. json.load() / json.dump()는 파일 객체와 함께 작동하고, json.loads() / json.dumps()(들은 s 포함)는 메모리의 문자열과 함께 작동합니다. 인간이 출력을 읽을 때 dump/dumpsindent=2를 전달하세요. 아니면 한 줄로 나옵니다. 잘못된 JSON은 json.JSONDecodeError를 발생시키는데, 이것은 ValueError의 일종이므로 ValueError를 잡는 핸들러가 이미 이를 포함합니다.

네 이름은 두 작업을 두 소스로 교차합니다: 파일용 load/dump, 문자열용 loads/dumps(들은 s는 "string"). 생산에서 사람들을 잡는 세부는 JSON이 나타낼 수 없는 것입니다. 날짜, set, 또는 Decimal의 개념이 없으므로 json.dumps(datetime.now())TypeError를 발생시킵니다. default=를 전달하여 처리하세요. 이것은 dump/dumps이 알 수 없는 모든 값에 대해 호출하는 함수이며, JSON이 보관할 수 있는 것(일반적으로 str(value))을 반환해야 합니다. 다른 규칙이 보관할 가치가 있습니다: 숫자는 float로 왕복하므로 정확성을 위해 Decimal로 작성한 값은 float로 돌아오고 이를 잃습니다. 절대 돈 또는 정확한 소수가 필요한 것을 평문 JSON 숫자로 저장하지 마세요(Numbers 장 참조).

파일에서 JSON 읽기:

python
import json

with open("config.json", "r") as f:
    config = json.load(f)    # JSON을 Python dict/list로 파싱

print(config["setting"])

파일에 JSON 쓰기:

python
import json

data = {"name": "민준", "score": 87, "active": True}

with open("output.json", "w") as f:
    json.dump(data, f, indent=2)    # indent= 는 사람이 읽을 수 있게 함

JSON에서 Python 유형으로의 매핑:

JSONPython
object {}dict
array []list
string ""str
numberint 또는 float
true / falseTrue / False
nullNone

파일에 접하지 않고 JSON 문자열과 Python 객체 사이를 변환하려면:

python
import json

# 문자열에서 Python으로
data = json.loads('{"name": "민준", "score": 87}')

# Python에서 문자열로
text = json.dumps({"name": "민준", "score": 87}, indent=2)

json.load()는 파일 객체에서 읽습니다. json.loads()(들은 "s" 포함)는 문자열에서 읽습니다.

JunoJSON `json.load(f)`는 파일의 JSON을 Python dicts와 lists로 바꾸고, `json.dump(data, f)`는 이들을 다시 씁니다. `s` 버전들, `json.loads`와 `json.dumps`, 파일 대신 문자열에 동일하게 수행합니다. 출력을 읽을 수 있게 원할 때 `indent=2`를 추가하세요.
JunoJSON `load`/`dump`는 파일을 처리하고, `loads`/`dumps`는 문자열을 처리합니다. 들은 `s`는 문자열입니다. `indent=2`는 사람이 읽을 수 있게 만듭니다. 나쁜 JSON은 `json.JSONDecodeError`를 발생시키는데, 이것은 `ValueError`이므로 `ValueError`를 잡으면 파싱 실패가 이미 포함됩니다.
JunoJSON 두 작업을 두 소스로 교차합니다: 파일용 `load`/`dump`, 문자열용 `loads`/`dumps`. JSON은 날짜, set, 또는 `Decimal`이 없으므로 이들을 덤프하면 `default=`를 전달하지 않으면 `TypeError`를 발생시킵니다. 그리고 숫자는 `float`로 왕복하므로 정확한 소수가 필요한 평문 JSON 숫자로 돈을 절대 저장하지 마세요. 반환 시 정확성을 잃습니다.

실제 사용

작은 게임을 위한 저장/로드 패턴: 상태를 JSON으로 씁니다. 다음 실행에서 다시 로드하고, 저장 파일이 아직 없으면 기본값으로 돌아갑니다:

python
import json

SAVE_FILE = "save_game.json"

def save_game(player_data: dict) -> None:
    with open(SAVE_FILE, "w") as f:
        json.dump(player_data, f, indent=2)
    print("Game saved.")

def load_game() -> dict:
    try:
        with open(SAVE_FILE, "r") as f:
            return json.load(f)
    except FileNotFoundError:
        print("No save file found, starting fresh.")
        return {"name": "Player", "score": 0, "level": 1}

state = load_game()
state["score"] += 50
save_game(state)

설정 파일을 로드하고 결과를 저장하며, 각 실패 모드에 대한 특정 예외 처리:

python
import json

def load_config(path: str) -> dict:
    try:
        with open(path, "r") as f:
            return json.load(f)
    except FileNotFoundError:
        raise FileNotFoundError(f"Config file not found: {path}")
    except json.JSONDecodeError as e:
        raise ValueError(f"Invalid JSON in {path}: {e}")

def save_results(results: list[dict], path: str) -> None:
    with open(path, "w") as f:
        json.dump(results, f, indent=2)
    print(f"Saved {len(results)} result(s) to {path}")

config = load_config("experiment.json")
results = [{"epoch": 1, "loss": 0.82}, {"epoch": 2, "loss": 0.61}]
save_results(results, "results.json")

타임스탬프 항목을 파일에 추가하는 구조화된 로그 작성자이며, 예상치 못한 실패를 잡고 로그하는 최상위 핸들러:

python
import json
from datetime import datetime

LOG_FILE = "run.log"

def log(level: str, message: str) -> None:
    ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    entry = f"[{ts}] [{level.upper():7}] {message}\n"
    with open(LOG_FILE, "a") as f:
        f.write(entry)

def process(config_path: str) -> None:
    log("info", f"Starting job, config: {config_path}")
    try:
        with open(config_path) as f:
            config = json.load(f)
        log("info", f"Loaded config: {config}")
    except FileNotFoundError:
        log("error", f"Config not found: {config_path}")
        raise
    except json.JSONDecodeError as e:
        log("error", f"Bad JSON in config: {e}")
        raise

try:
    process("config.json")
except Exception as e:
    log("critical", f"Job failed: {e}")

로그를 남긴 후 다시 발생시키면 원본 추적이 발신자에게 유지됩니다. 최상위 except Exception은 빠져나간 것을 모두 잡고, 중요로 로그하고, 프로세스가 깔끔하게 종료되게 합니다.