파이썬 에러와 예외 공식 문서를 참조하면 되겠지만,
딱 필요한 정보만 압축해서 정리해 놓으려고 한다(쓰다보면 길어질지도?).
조건문만 사용해서 모든 상황을 통제할 수 있다면 좋겠는데..
나도 모르는 내 실수나 손쓸 수 없는 예외적인 상황에 대한 완벽한 대처는 참 어려운 법이다.
파이썬에서는 문법적 에러와 런타임 에러가 있는데, 전자는 실행 시 무조건 에러가 발생하기 때문에 코딩 중에 바로 대처할 수 있다.
그러나 문법적으로 문제가 없어도 외부적인 요인에 의해 발생할 수 있는 예외상황에 대해서는 try-except
예외처리를 해줘야 한다.
예외는 항상 치명적이지는 않다. 하지만 보다 안정적인 코딩을 위해서 예외처리는 필수이다. 대표적인 예외는 ZeroDivisionError 인데, 이것은 정수나 실수를 0으로 나누는 논리적 예외상황에서 발생한다. 나누는 수로 0이 항상 입력되는 것은 아니기 때문에 이러한 예외상황이 언제 발생할 지는 알 수 없다. 이 문제를 조건문으로 처리할 수도 있겠지만, 0이 입력되는 경우는 의미 있는 고려대상이 아니기 때문에 예외처리하는 것이 옳다. 다음은 ZeroDivisionError 를 테스트하는 divide_by_zero.py 의 내용이다.
위 코드를 실행하면 다음과 같은 결과가 출력된다.
===== start =====
divide 10 by 10!!!
>> 1
divide 10 by 0!!!
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 3, in divide_by_zero
ZeroDivisionError: division by zero
===== end =====
보면 알겠지만 10을 10으로 나눈 첫 시도에는 문제 없이 1이 출력되었는데, 0으로 나눠지는 두 번째 시도에서는 ‘ZeroDivisionError: division by zero’ 예외가 발생했다.
위에서 언급했듯, try-except
도구를 사용하면 성공적으로 예외처리를 할 수 있다.
코드를 수정해 보자.
출력은 다음과 같다.
===== start =====
divide 10 by 10!!!
>> 1
divide 10 by 0!!!
[division by zero] Oops! "0" for div was no valid number. Try again...
===== end =====
이제 div=0
가 들어와도 예외상황에 발목잡히지 않고 이후 코드가 진행된다!
만약 division 의 결과값이 특정 변수에 담겨야 한다면,
except
처리문 내에 예외 시 변수의 기본값을 세팅함으로써 매끄럽게 처리할 수도 있을 것이다.
raise 문은 강제적으로 예외를 일으킨다. 사전에 클래스로 정의된 예외상황은 아니지만, 작성자가 원하는 조건에서 예외가 발생하기를 원할 때 사용한다.
일으킨 예외를 처리하고 싶다면 다음과 같이 한다.
An exception flew by!
Traceback (most recent call last):
File "<input>", line 2, in <module>
NameError: HiThere
이러한 출력이 나타나면서 예외가 처리되고 다음 로직이 실행된다.
모든 예외 타입들은 Exception 클래스의 자식들이므로,
Exception
혹은 그 자식 클래스를 상속받음으로써 사용자 정의 예외를 만들 수 있다.
일반적으로 Exception 의 상속은 간단하게 유지하며 에러에 관한 구체적인 정보들을 추출하기 위한 용도에서 이루어진다. 흔히 사용되는 방식은 모듈에서 정의되는 예외들의 베이스 클래스를 정의한 후, 각기 다른 에러 조건마다 특정한 예외 클래스를 서브 클래스로 만드는 것이다.
자세한 내용은 공식 문서 를 참조하자.
else
구문은 모든 except
문 이후에 작성되며, try
진행중 예외가 발생하지 않은 경우 실행된다.
지정한 모든 예외가 발생하지 않는 상황을 보장하기 때문에, 정상 로직은 try
문보다는 else
문에 작성하는 것이 좋다.
finally 구문은 무조건 실행된다. 예외가 발생하든 발생하지 않든 항상 실행되어야만 하는 코드를 이곳에 작성하면 된다. 일반적으로 열었던 파일을 닫을 때 많이 사용된다.
===== start =====
[START] divide 10 by 10!!!
[NORMAL] result is 1.
[FIN] finising division process.
[START] divide 10 by 10!!!
[ERROR][unsupported operand type(s) for /: 'int' and 'str'] Please insert a number. not a string.
[FIN] finising division process.
[START] divide 10 by 0!!!
[ERROR][division by zero] Oops! "0" for div was no valid number. Try again...
[FIN] finising division process.
===== end =====
중간에 div
로 문자열이 입력된 경우를 추가했다.
이 때는 TypeError 가 발생한다.
어쨌든 예외가 발생하면 각각의 except
구문에 해당하는 라인이 실행되고, 그렇지 않은 경우 else
구문이 실행되는 것을 볼 수 있다.
finally
구문은 예외 발생에 관계없이 무조건 실행된다.
마지막으로 finally
가 필요한 file read/write 상황을 살펴보자.
myFile.txt 를 try 문 내에서 open
한 뒤, 예외가 발생하면 except
문이, 아니라면 else
문이 실행되도록 작성한 코드이다.
예상되는 예외는 해당 파일을 경로 내에서 찾을 수 없는
FileNotFoundError 이다.
예외 발생 여부와 상관 없이 finally
문 내의 fo.close()
라인으로 인해 파일의 무기한 open
이 방지된다. (메모리 누수 방지)
file handling 에 최적화된 with 구문을 사용하면 위의 방식보다 훨씬 간단한 코드를 작성할 수도 있다.
파이썬 context manager 에 의해 관리되는 with
문은,
구문이 종료되는 __exit__
상황에서 열렸던 파일이 닫힌다.
그러므로 작성자가 with
구문을 활용하면 파일 open
에 의한 메모리 누수를 염려를 하지 않아도 된다.
자세한 내용은 context manager 를 다룬 포스트 에서 확인할 수 있다.
부가적인 예외 처리에 대한 부분을 추가하려면, with
구문을 try-except
구문으로 감싸면 된다.
끝~!
Written on June 16th, 2020 by namu