#!/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일