4 minute read

예외 처리 기법

파이썬 예외 처리에서 사용할 수 있는 키워드는 총 4개이다.

  1. try
  2. except
  3. finally
  4. raise


1. try - except

try블록 수행 중 오류가 발생하면 except 블록이 수행된다. try블록에서 오류가 발생하지 않는다면 except 블록은 수행되지 않는다.

try, except의 기본 형태

try:
    ...
except [발생오류 [as 오류변수]]:
    ...

except구문에서 ‘[]’기호인 대괄호는 안의 내용을 생략할 수 있다는 관례적인 표기법이다. 즉, except 뒤에 발생오류과, 오류변수 둘 다 작성하지 않아도 문법적으로 문제가 없다.


1. try, except만 작성 (모든 예외 처리하기)

try:
    ...
except:
    ...
  • 이 경우는 오류 종류에 상관없이 오류가 발생하면 except블록을 수행한다.

예시1

try:
    print (4/0)
except:
    print('에러 발생')
output:
에러 발생

예시2

try:
    a = [1, 2, 3]
    print(a[3])
except:
    print('에러 발생')
output:
에러 발생


2. 발생 오류만 포함한 except문 (특정 예외만 처리하기)

try:
    ...
except 발생오류:
    ...
  • 미리 정해 놓은 오류와 동일한 오류일 경우에만 except 블록 수행

예시 1

try:    
    a = [1, 2, 3]
    print(a[3])
except ZeroDivisionError:
    print("에러 발생")
output:
Traceback (most recent call last):
  File "c:\Users\JunsuPark\Desktop\Study\MachineLearning\Books\UnderLearning\Chap3\MNIST\exception.py", line 3, in <module>
    print(a[3])
IndexError: list index out of range
  • except의 코드가 실행되지 않고, 오류가 발생한 것을 볼 수 있다.

에시2

try:    
    a = [1, 2, 3]
    print(a[3])
except IndexError:
    print("인덱스 참조 에러 발생")
output:
인덱스 참조 에러 발생
  • 발생 오류를 수정하면 except코드가 실행됨을 확인할 수 있다.


3. 발생오류와 오류변수까지 포함한 except문 (예외의 에러 메시지 받아오기)

try:
    ...
except 발생오류 as 오류변수:
    ...

예시 1

try:    
    print(4 / 0)
except ZeroDivisionError as e:
    print(e)
output:
division by zero


2. try - finally

try문에는 finally절을 사용할 수 있다. finally절은 try문 수행 도중 예외 발생 여부에 상관없이 항상 수행된다. 보통 finally절은 사용한 리소스를 close 해야 할 때에 많이 사용한다.

try:
    f = open('foo.txt', 'w')
    # 무언가를 수행한다.

    (... 생략 ...)

finally:
    f.close()  # 중간에 오류가 발생하더라도 무조건 실행된다.
  • foo.txt 파일을 쓰기 모드로 연 후에 try문을 수행한 후 예외 발생 여부와 상관없이 finally절에서 f.close()로 열린 파일을 닫을 수 있다.


3. 여러 개의 오류 처리하기

try:
    ...
except 발생오류1:
   ... 
except 발생오류2:
   ...

예제

예시1

try:
    print(4 / 0)
    a = [1, 2]

except ZeroDivisionError as e:
    print(e)

except IndexError as e:
    print(e)
  • 코드 라인의 순서대로 처리 되므로, IndexError오류는 발생하지 않는 것을 확인할 수 있다.

예시2

try:
    a = [1,2]
    print(a[3])
    4/0
except (ZeroDivisionError, IndexError) as e:
    print(e)
output:
list index out of range
  • except를 2개로 넣는 것이 아니라 괄호를 통해 하나로 묶어줄 수 있다.
  • IndexError가 먼저 발생하기에, ZeroDivisionError는 발생하지 않고 IndexError오류 메시지만 출력되는 것을 확인할 수 있다.


4. try-else

try문 수행중 오류가 발생하면 except절이 수행되고, 오류가 없으면 else절이 수행될 수 있도록 할 수 있다.

try:
    ...
except [발생오류 [as 오류변수]]:
    ...
else:  # 오류가 없을 경우에만 수행된다.
    ...
try:
    age = int(input("나이를 입력하세요: "))
except:
    print("입력이 정확하지 않습니다.")
else:
    if age <= 18:
        print("미성년자는 출입금지입니다.")
    else:
        print("환영합니다.")
output:
(enter만 입력한 경우)
나이를 입력하세요:  
입력이 정확하지 않습니다.

(숫자를 입력한 경우)
나이를 입력하세요: 15
미성년자는 출입금지입니다.


5. 오류 회피하기

특정 오류가 발생할 경우 그냥 통과시켜야 할 때가 있다.

try:
    f = open("없는파일", "r")
except FileNotFoundError:
    print("없는 파일 접근")
    pass
output:
없는 파일 접근
  • pass는 null 연산으로, 실행되었을 때 아무일도 일어나지 않는다. 구문상 문이 필요하지만 코드를 실행할 필요가 없는 경우와 같이 placeholder로 유용하다.


6. raise를 통한 오류 발생시키기

raise 키워드를 통해 파이썬에서 오류를 강제로 발생시킬 수 있다.

  • 예를 들어 Bird 클래스를 상속받는 자식 클래스는 반드시 fly()메소드를 구현하도록 강제하고 싶은 경우에 사용할 수 있다.
class Bird:
    def fly(self):
        raise NotImplementedError
class Eagle(Bird):
    pass

eagle = Eagle()
eagle.fly()
output:
Traceback (most recent call last):
  File "c:폴더위치/exception.py", line 9, in <module>
    eagle.fly()
AttributeError: 'Eagle' object has no attribute 'fly'
  • Eagle object는 fly attribute를 가지고 있지 않다고 에러 메시지가 발생한다.
  • Eagle은 Bird클래스를 상속받았는데, fly()메소드를 오버라이딩하여 구현하지 않았음으로 eagle객체에서 fly()메소드를 수행하는 순간 Brid 클래스의 fly()메소드가 수행되어 NotImplementedError가 발생한다.

NotImplementedError가 발생하지 않으려면 다음과 같이 Eagle클래스에 fly()메소드를 구현해야 한다.

class Bird:
    def flt(self):
        raise NotImplementedError
    
class Eagle(Bird):
    def fly(self):
        print("very fast")

eagle = Eagle()
eagle.fly()
output:
very fast


7.예외 만들기

파이썬 내장 클래스인 Exception 클래스를 상속받아 직접 예외를 만들 수 있다. 숫자가 3의 배수가 아닌 상황에서 사용될 custom exception을 클래스로 정의하고, raise NotThreeMultipleError를 통해 에러를 발생시켜 보자.

class NotThreeMultipleError(Exception):
    def __init__(self):
        super().__init__('3의 배수가 아닙니다.')

def three_multiple():
    try:
        x = int(input('3의 배수를 입력하세요: '))
        if (x % 3 != 0):
            raise NotThreeMultipleError
        print(x)
    except NotThreeMultipleError as e:
        print("예외가 발생했습니다.", e)


three_multiple()
output:
3의 배수를 입력하세요: 4
예외가 발생했습니다. 3의 배수가 아닙니다.

사용자 정의 exception은 언제 사용?

이에 관해서는 여러 주장이 많은데, 충분히 많은 built-in exception이 존재하기에 굳이 사용할 필요 없다고 하는 주장과 이에 상반되는 주장인 사용자 지정 exception이 사용자에게 도움이 된다는 주장도 있다.

이는 옳고 그름의 선택은 아니며, 사용자 정의 exception 사용의 적절한 균형을 찾는 것이 중요하다.

  • 무분별한 사용보다는, 다른 사람이 코드를 읽을 때 무엇이 어디에서 잘못되었는지 정확하게 보여줄 수 있도록 잘 설계된 custom exception을 사용하는 것이 좋다.
  • 그러나 대부분은 불필요하게 코드를 복잡하게 만드므로, 프로젝트의 규모나 남에게 코드가 어떻게 보일지 고민해보며 사용하자.
  • 하지만 사용자 지정 예외를 사용하면 프로젝트에 큰 도움이 될 수 있으므로 사용하는 것을 두려워하지 말자.


8. 파이썬의 exception hierachy

BaseException
 ├── BaseExceptionGroup
 ├── GeneratorExit
 ├── KeyboardInterrupt
 ├── SystemExit
 └── Exception
      ├── ArithmeticError
          ├── FloatingPointError
          ├── OverflowError
          └── ZeroDivisionError
      ├── AssertionError
      ├── AttributeError
      ├── BufferError
      ├── EOFError
      ├── ExceptionGroup [BaseExceptionGroup]
      ├── ImportError
          └── ModuleNotFoundError
      ├── LookupError
          ├── IndexError
          └── KeyError
      ├── MemoryError
      ├── NameError
          └── UnboundLocalError
      ├── OSError
          ├── BlockingIOError
          ├── ChildProcessError
          ├── ConnectionError
              ├── BrokenPipeError
              ├── ConnectionAbortedError
              ├── ConnectionRefusedError
              └── ConnectionResetError
          ├── FileExistsError
          ├── FileNotFoundError
          ├── InterruptedError
          ├── IsADirectoryError
          ├── NotADirectoryError
          ├── PermissionError
          ├── ProcessLookupError
          └── TimeoutError
      ├── ReferenceError
      ├── RuntimeError
          ├── NotImplementedError
          └── RecursionError
      ├── StopAsyncIteration
      ├── StopIteration
      ├── SyntaxError
          └── IndentationError
               └── TabError
      ├── SystemError
      ├── TypeError
      ├── ValueError
          └── UnicodeError
               ├── UnicodeDecodeError
               ├── UnicodeEncodeError
               └── UnicodeTranslateError
      └── Warning
           ├── BytesWarning
           ├── DeprecationWarning
           ├── EncodingWarning
           ├── FutureWarning
           ├── ImportWarning
           ├── PendingDeprecationWarning
           ├── ResourceWarning
           ├── RuntimeWarning
           ├── SyntaxWarning
           ├── UnicodeWarning
           └── UserWarning


출처

https://wikidocs.net/30
https://dojang.io/mod/page/view.php?id=2398
https://docs.python.org/3/library/exceptions.html
https://docs.python.org/3/reference/simple_stmts.html#the-pass-statement
사용자 정의 exception을 사용해야 하나?
https://dojang.io/mod/page/view.php?id=2401

Categories:

Updated: