#!/usr/bin/env python # coding: utf-8 # *** # *** # # 23. 예외처리 # *** # *** # *** # ## 1 파이썬 예외의 종류 # *** # - 구문 에러 (Syntax Error) # - 문법적 에러 # - 이클립스 등의 통합개발환경 도구에서는 자동으로 실행 전에 구문 에러를 체크 해 줌 # - 파이썬은 상대적으로 언어적 문법이 간단하기 때문에 구문 자체의 에러 발생 비율이 낮으며, 통합개발환경 도구를 사용하여 완벽하게 제거할 수 있음 # # - 논리 에러 (Logical Error) # - 논리적 에러 # - 문법적 에러가 없으므로 프로그램이 실행이 되며, 비정상적으로 종료되지 않는 버그 # - 논리 오류는 즉시 인식되지는 않지만 의도치 않은 또는 바라지 않은 결과나 다른 행동을 유발 # # - 예외 (Exception) # - 문법적 에러는 없으나 프로그램 실행 중 더 이상 진행 할 수 없는 상황 # - 즉, 논리 에러에 해당함 # - 프로그램 실력은 이러한 예외 처리를 얼마나 꼼꼼하게 잘 하는가에 달려 있음 # - 파이썬에서의 예외 (Exception) 종류 # - 에러 (Error) # - 대부분의 예외 # - 대부분의 예외들에 해당하는 클래스 이름은 Error 로 끝남 # - 경고 (Warning) # - 논리적인 오류로 판단하기에 애매한 상황 # - 일단은 무시하여도 좋음 # - 하지만, 미래에는 다른 판단을 하여 에러로 변경될 수 있음 # ### 1-1 예외 발생 예제 보기 # - 예외 발생 예제 1: 정의되지 않은 변수 사용하기 # - NameError # In[1]: 4 + spam*3 # - 예외 발생 예제 2: 0으로 숫자 나누기 # - ZeroDivisionError # In[3]: a = 10 b = 0 c = a / b # - [note] 예외가 발생하면 프로그램은 바로 종료된다. # In[4]: def division(): for n in range(0, 5): print(10.0 / n) division() # - 예외 발생 예제 3: 문자열과 숫자 더하기 # - TypeError # In[5]: '2' + 2 # - 예외 발생 예제 4: 참조 범위를 넘어서 인덱스 사용 # - IndexError # In[6]: l = [1, 2] print(l[2]) # - 예외 발생 예제 5: 등록되지 않은 키로 사전 검색 # - KeyError # In[7]: d = {"a": 1, "b": 2} print(d['c']) # - 예외 발생 예제 6: 있지도 않은 파일을 열려고 할 때 # - FileNotFoundError (Python3) # - IOError (Python2) # In[8]: a = open('aaa.txt') # ### 1-2 내장 예외의 종류 # - 예외 클래스의 계층 구조 # - python2: https://docs.python.org/2/library/exceptions.html # - python3: https://docs.python.org/3/library/exceptions.html # #
BaseException
#  +-- SystemExit
#  +-- KeyboardInterrupt
#  +-- GeneratorExit
#  +-- Exception
#       +-- StopIteration
#       +-- StopAsyncIteration
#       +-- ArithmeticError
#       |    +-- FloatingPointError
#       |    +-- OverflowError
#       |    +-- ZeroDivisionError
#       +-- AssertionError
#       +-- AttributeError
#       +-- BufferError
#       +-- EOFError
#       +-- 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
#       +-- SyntaxError
#       |    +-- IndentationError
#       |         +-- TabError
#       +-- SystemError
#       +-- TypeError
#       +-- ValueError
#       |    +-- UnicodeError
#       |         +-- UnicodeDecodeError
#       |         +-- UnicodeEncodeError
#       |         +-- UnicodeTranslateError
#       +-- Warning
#            +-- DeprecationWarning
#            +-- PendingDeprecationWarning
#            +-- RuntimeWarning
#            +-- SyntaxWarning
#            +-- UserWarning
#            +-- FutureWarning
#            +-- ImportWarning
#            +-- UnicodeWarning
#            +-- BytesWarning
#            +-- ResourceWarning
# 
# *** # ## 2 예외 처리 방법 # *** # ### 1-1 try/except/else/finally 절 사용하기 # - 예외가 발생할 수 있는 상황을 예상하여 예외 발생 상황을 전체 코드 흐름을 함께 제어할 수 있다. # - try/except/else/finally 절 # - 구문 # > try:
# >     (예외 발생 가능한) 일반적인 수행문들
# > except Exception:
# >     예외가 발생하였을 때 수행되는 문들
# > else:
# >     예외가 발생하지 않았을 때 수행되는 문들
# > finally:
# >     예외 발생 유무와 관계없이 무조건 수행되는 문들
# In[9]: try: print(1.0 / 0.0) except ZeroDivisionError: print('zero division error!!!') # - 예외 처리를 하면 예외 발생시 프로그램 종료가 되지 않는다. # - as 라는 구문을 통하여 예외 객체를 잡아서 처리할 수 있음. # In[23]: #python3.x def division(): for n in range(0, 5): try: print(10.0 / n) except ZeroDivisionError as e: print("ZeroDivisionError 발생") print("-----" + str(e)) print("-----" + e.__str__()) print("-----" + str(e.__class__)) division() # - else: 구문은 except: 구문없이 사용 못한다. # In[10]: def division(): for n in range(0, 5): try: print(10.0 / n) else: print("Success") division() # - 상황에 따라서는 에러와 함께 따라오는 정보를 함께 받을 수도 있다. # - 그러한 정보를 얻으려면 객체 자체를 print()에 넣어 출력하면 됨 # In[13]: #python3.x try: spam() except NameError as e: print('Error -', e) # - try 절 안에서 간접적으로 호출한 함수의 내부 예외는 그 함수를 호출한 쪽에서도 처리할 수 있다 # - 즉, 예외는 함수안에서 처리가 안되면 함수 밖으로 던져진다. # In[17]: #python3.x def zero_division(): x = 1 / 0 try: zero_division() except ZeroDivisionError as e: print('zero division error!!! -', e) # - 함수 안에서 예외 처리를 하면 함수를 호출한 쪽으로 해당 예외가 넘어오지 않는다. # In[18]: #python3.x def zero_division(): try: x = 1 / 0 except ZeroDivisionError as e: print('Inner - zero division error!!! -', e) try: zero_division() except ZeroDivisionError as e: print('Outer - zero division error!!! -', e) # - except 뒤에 아무런 예외도 기술하지 않으면 모든 예외에 대해 처리된다. # In[19]: try: spam() print(1.0 / 0.0) except: print('Error') # - 여러 예외들 각각에 대해 except 절을 다중으로 삽입할 수 있다. # - 하지만 해당 예외가 발생하면 그 다중으로 삽입된 except 중 하나만 처리된다. # In[21]: b = 0.0 name = 'aaa.txt' try: print(1.0 / b) spam() f = open(name, 'r') '2' + 2 except NameError: print('NameError !!!') except ZeroDivisionError: print('ZeroDivisionError !!!') except (TypeError, IOError): print('TypeError or IOError !!!') else: print('No Exception !!!') finally: print('Exit !!!') # - 파일에서 숫자를 읽어와서 읽은 숫자로 나누기를 하는 예제 # - 꼼꼼한 예외 처리 예제 # In[24]: get_ipython().run_line_magic('ls', 't.txt') # In[23]: #python3.x import os print(os.getcwd()) filename = 't.txt' try: f = open(filename, 'r') except IOError as e: print(e) else: a = float(f.readline()) try: answer = 1.0 / a except ZeroDivisionError as e: print(e) else: print(answer) finally: print("Finally!!!") f.close() # In[28]: get_ipython().run_line_magic('ls', 'a.txt') # In[29]: get_ipython().run_line_magic('cat', 'a.txt') # In[30]: #python3.x import os print(os.getcwd()) filename = 't.txt' default_filename = 'a.txt' try: f = open(filename, 'r') except IOError as e: print(e) f = open(default_filename, 'r') a = float(f.readline()) try: answer = 1.0 / a except ZeroDivisionError as e: print(e) else: print(answer) finally: print("Finally!!!") f.close() # ### 2-2 같은 부류의 예외 다 잡아내기 # - 예외 클래스들은 상속에 의한 계층 관계를 지니고 있기 때문에 이를 이용하면 여러 예외들을 한꺼번에 잡을 수 있다. # - 예를 들어, ArithmeticError의 하위 클래스로서 FloatingPointError, OverflowError, ZeroDivisionError가 존재하기 때문에 이들 하위 클래스 예외가 발생하였을 경우 ArithmeticError로서 잡아낼 수 있다. # In[31]: def dosomething(): a = 1/0 try: dosomething() except ArithmeticError: print("ArithmeticException occured") # - 예외가 임의의 except에 의해 잡히면 다른 except에 의해서는 잡히지 않는다. # In[20]: def dosomething(): a = 1/0 try: dosomething() except ZeroDivisionError: # ZeroDivisionError는 이곳에서 잡힌다. print("ZeroDivisionError occured") except ArithmeticError: # FloatingPointError, OverflowError는 이곳에서 잡힌다. print("ArithmeticException occured") # In[20]: def dosomething(): a = 1/0 try: dosomething() except ArithmeticError: print("ArithmeticException occured") except ZeroDivisionError: # 이곳에서 ZeroDivisionError는 잡히지 않는다. ==> 잘못된 코드 print("ZeroDivisionError occured" ) # *** # ## 3 예외 발생 # *** # ### 3-1 raise로 예외 발생하기 # - 예외를 특정 상황 조건에서 raise 키워드를 통해 발생시킬 수 있다. # - 아래 예는 시퀀스 형 클래스를 설계할 때 인덱싱을 구현하는 \_\_getitem\_\_ 메소드에서 인덱스가 범위를 넘을 때 IndexError를 발생시킨다. # In[37]: class SquareSeq: def __init__(self, n): self.n = n def __getitem__(self, k): if k >= self.n or k < 0 : raise IndexError # 첨자 범위를 벗어나면 IndexError 예외를 발생시킴 return k * k def __len__(self): return self.n s = SquareSeq(10) print(s[2], s[4]) for x in s: # IndexError가 발생하는 시점까지 반복한다 print(x,end=" ") print(s[20]) # 첨자 범위가 넘었다 # ### 3-2 사용자 클래스 예외 정의 및 발생시키기 # - 사용자 정의 예외 클래스를 구현하는 일반적인 방법은 Exception 클래스를 상속 받아 구현한다. # - Exception 클래스의 서브 클래스 중 하나를 상속 받아도 된다. # - 사용자 정의 예외 발생 방법 # - 내장 예외 발생 방법과 동일하게 raise [클래스의 인스턴스] 와 같이 해당 예외 클래스의 인스턴스를 던진다. # - 사용자 정의 예외를 잡는 방법 # - except [클래스 이름] 과 같이 해당 예외 클래스 이름을 사용한다. # - 아래 예에서 except Big이 잡는 예외는 Big과 Small 이다. # - 이유: Small은 Big의 하위 클래스이기 때문 # In[29]: class Big(Exception): pass class Small(Big): pass # In[30]: def dosomething1(): x = Big() raise x def dosomething2(): raise Small() for f in (dosomething1, dosomething2): try: f() except Big: print("Big Exception occurs!") # ### 3-3 예외값 전달하기 # - raise 키워드 뒤에 예외와 함께, 추가 메시지를 함께 던질 수 있다. # - python3에서는 지원하지 않음 # - python3에서는 예외의 생성자내에 메시지를 넣어서 예외 객체를 생성하여 던진다. # In[31]: #python3.x def f(): raise Exception('message!!!') try: f() except Exception as e: print(e) # - 생성자 안에 넣어준 에러 메시지는 except 키워드 사용시에 두 번째 인자로 해당 메시지를 받을 수 있다. # - python3에서는 지원하지 않음 # - python3에서는 예외 객체를 받아서.. # - 객체 자체를 print() 하거나 # - 해당 객체의 \_\_str\_\_()을 호출하여 메시지 문자열을 얻어서 print() 한다. # In[15]: #python3.x a = 10 b = 0 try: if b == 0: raise ArithmeticError('0으로 나누고 있습니다.') a / b except ArithmeticError as e: print(e) # In[28]: #python2.x a = 10 b = 0 try: if b == 0: raise ArithmeticError('0으로 나누고 있습니다.') a / b except ArithmeticError, v: print v #

참고 문헌: 파이썬(열혈강의)(개정판 VER.2), 이강성, FreeLec, 2005년 8월 29일