스터디/파이썬 스터디 강의자료

[2팀/김가림, 최다예] 10주차 파이썬 스터디 - 예외처리

다예뻐 2023. 6. 1. 00:13

10차시 예외처리_과제.pdf
0.19MB
10차시 예외처리_과제답안.pdf
0.20MB

참고도서 : 데이터 과학을 위한 파이썬 프로그래밍, 최상철

 

예외의 개념과 사례

예외 : 프로그램을 개발하면서 예상치 못한 상황이 발생하는 것

ex) 사용자의 입력 오류

사용자가 한글 아이디 생성 (영문자, 숫자만 지원하는 프로그램) → 입력된 한글이 아무 문제 없이 데이터베이스에 저장되면 사용자가 해당 웹 사이트에 로그인하지 못할 수 있음

프로그래머의 실수로 인코딩에 문제가 발생하여 데이터베이스에 한글이 잘못 저장될 수 도 있기 때문

ex) MS 오피스의 자동 저장 기능

갑자기 컴퓨터가 종료되었을 때를 대비하여 작업물을 중간 자동 저장해주는 기능


예측 가능한 예외와 예측 불가능한 예외

예측 가능한 예외

발생 여부를 개발자가 사전에 인지할 수 있는 예외

예외를 예측하여 명시적으로 예외가 발생할 때는 어떻게 대응할지 대책을 마련할 수 있음

ex) 텍스트 박스에 사용자가 실수로 잘못된 값을 입력할 것 같다

사용자가 실제로 존재하지 않는 파일에 저장할 것 같다

예측 불가능한 예외

매우 많은 파일을 처리할 때 문제가 발생할 수 있음

ex) 5,000개의 파일이 한 디렉터리에 있고 그 파일명들을 넘겨받아 파일을 하나씩 읽어들이는 프로그램

어떤 파일은 실제로 안에 내용이 없을 수 있는데, 이런 경우에도 어떤 처리를 하지 못하고 넘어간다

는 파일을 읽고 있는데 다른 사용자에 의해 해당 파일이 삭제되어 읽어들이는 시점에 해당 파일이 사라지는 경우도 발생

ex) 인덱스의 범위를 넘어가는 리스트의 호출

정수를 0으로 나누는 것 등에 대한 문제

인터프리터가 자동으로 이것이 예외라고 사용자에게 알려줌

대부분은 이러한 예외가 발생하면서 프로그램이 종료되므로 적절한 조치를 해야 함

단순히 파일이 없다고 알려주는 것이 아니라 파일이 없기 때문에 추가로 해야 하는 조 치가 있다면 그것을 예외 처리 구문에 추가해야 함

ex) <처리 파일 X → 기록 → 다음 파일 처리> 작성

  • 예외 처리가 중요한 이유는 프로그램도 일종의 제품이기 때문
  • 제품의 완성도를 높이는 차원에서 중요한 개념

예외 처리 구문

try-except 문

try:
    예외 발생 가능 코드
except 예외 타입:
    예외 발생 시 실행되는 코드
for i in range(10):
    try:
        print(10 / i)
    except ZeroDivisionError:
        print("Not divided by 0")
Not divided by 0
10.0
5.0
3.3333333333333335
2.5
2.0
1.6666666666666667
1.4285714285714286
1.25
1.1111111111111112

try문이 for문 밖에 있으면 어떤 일이 발생할까?

→ 반복문 전체가 종료

try문 내부에서 예외가 발생하면 except문 영역에 있는 코드가 실행되고 tryexcept문이 종료된다. 이렇게 try문을 적당한곳에 삽입하여 예외 처리를 할수 있음

try-except-else 문

if-else 문과 비슷

해당 예외가 발생하지 않는 경우 수행할 코드를 else문에 작성하면 됨

try: 
    예외 발생 가능 코드
except 예외 타입:
    예외 발생 시 실행되는 코드
else: 
    예외가 발생하지 않을 때 실행되는 코드
for i in range(10):
    try:
        result = 10 / i
    except ZeroDivisionError:
        printC"Not divided by 0")
    else:
        print(10 / i)
Not divided by 0
10.0
5.0
3.3333333333333335
2.5
2.0
1.6666666666666667
1.4285714285714286
1.25
1.1111111111111112

에러가 발생하지 않는 경우의 수행문을 정의해 놓으면 에러가 발생하지 않는 경우에도 일어날 일을 사용자가 정확히 예측할 수 있다는 장점이 있음

하지만 일반적으로 많이 사용하지 않는 코드이며 예외가 많이 발생하는 상황에서 간혹 사용할 수 있는 코드

ex) 서버의 상태가 불안해 연결 끊김 connection loss이 자주 발생한다면 연결이 되었을 때 처 리할 코드를 명확히 하는 것이 좋음

try-except-finally 문

은 try—except문 안에 있는 수행 코드가 아무런 문제없이 종료되었을 경우 최종으로 호출하는 코드

for문에서 사용하는 finally문과 용도가 비슷

try: 
    예외 발생 가능 코드
except 예외 타입:
    예외 발생 시 실행되는 코드
finally:
    예외 발생 여부와 상관없이 실행되는 코드
try:
    for i in range (1, 10):
        result =10 // i
        print(result)
except ZeroDivisionError:
    print("Not divided by 0")
finally:
    print("종료되었다.")
10
5
3
2
2
1
1
1
1
종료되었다.

try문이 for문 밖에 선언되어 있고, 두가 1부터 시작한다. 사실상 ZeroDivisionError가 발생할 수 없는 코드

이러한 코드를 작성하면 except문은 실행되지 않고 마지막으로 finally문만 실행됨

try-except-finally문도 for문에서 finally문을 사용하는 것과 동일하게 예외 발생 여부와 상관없이 반드시 실행되는 코드

raise문

try—except문과 달리 필요할 때 예외를 발생시키는 코드

ex)

while True문을 사용해 계속 반복문을 돌려야 하는 프로그램을 작성한다고 가정하자.

상대방 컴퓨터에 계속 무엇인가를 요청해야 하는 프로그램이고, 요청할 때마다 비용이 청구된다. 만약 상대방 컴퓨터가 고장나거나 문제가 생겨 사용하지 못하게 되는 상황이 발생한다면 어떻게 해야 할까?

프로그램을 작성할 때 상대방 컴퓨터에서 응답이 없다면 강제로 프로그램이 종료되도록 예외를 발생 시킬 수 있다

raise 예외 타입(예외 정보)
while True:
    value = input("변환할 정수값을 입력해 주세요: ")
    for digit in value:
        if digit not in "0123456789":
        raise ValueError("숫자값을 입력하지 않았습니다.")
    print("정수값으로 변환된 숫자 int(value))
변환할 정수값을 입력해 주세요: 10
정수값으로 변환된 숫자 -10
변환할 정수값을 입력해 주세요: ab
Traceback (most recent call last):
    File "raise.py’\\ line 5, in〈module〉
        raise ValueError("숫자값을 입력하지 않았습니다.")
ValueError: 숫자값을 입력하지 않았습니다.

while True문으로 반복문이 계속 돌아가는 상태에서 사용자에게 입력을 받음

하지만 사용자가 입력한 값이 숫자가 아닌 경우에는 숫자값을 입력하지 않았다고 출력하면서 프로그램을 종료하는 것을 목적으로 작성된 프로그램

이때, 에러의 종류는 ValueError로 사용자가 입력을 잘못했을 때 입력이 잘못된 것을 알려주면서 종료

assert 문

True 또는 False의 반환이 가능한 함수를 사용

False가 반환되면 예외가 발생

assert 예외 조건
def get_binary_number(decimal_number):
    assert isinstance(decimal_number, int)
    return bin(decimal_number)
print(get_binary_number(10))
print(get_binary_number("10"))
0bl010 
Traceback (most recent caU last):
    File "<C:/.../assert.py>", line 5, in <module>
        print(get_binary_number("10"))
    File "C:/.../assert.py", line 2, in get_binary_number
        assert isinstance(decimal_number, int)
AssertionError

1 행에서 get_binary_number() 함수를 통해 십진수를 입력받음

하지만 함수를 사용하는 사용자가 잘못된 인수argument, 예를 들어 문자열값을 입력할 수도 있음

이를 방지하기 위해 2행에서 assert문을 사용하였다. isinstance() 함수는 입력된 값이 다음에 나오는 클래스의 인스턴스인지를 확인하는 함수. 이 코드에서 decimal_number 변수가 정수형 인지는 4〜 5행에서 확인 가능 assert문은 코드를 작성할 때 잘못된 입력 여부를 사전에 확인하여 나중에 필요 없는 연산을 막아주고, 결국 다른 사람이 만든 코드를 사용하는 데 좋은 가이드가 됨


파일의 개념

file : 컴퓨터를 실행할 때 가장 기본이 되는 단위

프로그램을 실행하는 것은 늘 파일을 실행하는 것

But, 일반적으로 윈도우의 GUI 환경에서는 아이콘을 더블클릭하여 실행함

→ 실제로는 아이콘과 연결된 파일이 실행되는 구조

  • 아이콘 → 오른쪽 마우스 → [속성]

‘대상’ 항목에 실행되는 파일의 경로가 있음

이 경로를 그대로 복사하여 콘솔창에 붙여넣고 실행하면 해당 프로그램이 실행됨

모든 프로그램은 이렇게 파일이 있기 때문에 실행되는 것


파일의 종류

바이너리 파일(binary file)

  • 컴퓨터만 이해할 수 있는 이진 정보로 저장된 파일
  • 비트(bit) 형태로 저장되어 메모장으로 열면 내용이 보이지 않거나 내용을 확인할 수 없는 파일
  • 정보의 효율적 저장을 위해 파일 시스템 대부분은 이진 정보로 저장됨
  • ex) 엑셀, 워드

텍스트 파일(text file)

  • 사람이 이해할 수 있는 문자열로 저장된 파일
  • 메모장으로 내용 확인 가능
  • ex) 파이썬, HTML 코드 파일
  • 컴퓨터가 처리하기 위해 바이너리 형태로 저장된 파일임
  • 컴퓨터가 이해할 수 있는 형태로 변경하여 저장됨 ex) 아스키코드, 유니코드
  • → 모든 문자열값도 전부 이진수로 변경하여 저장

💡 인코딩 : 텍스트를 변환 표준 체계에 맞게 저장하는 것


파일 읽기

>>> f = open("파일명", "파일 열기 모드")
>>> f.close()

파일 읽기

>>> f = open ("dream.txt", "r")   # 파일 객체
>>> contents = f.read()            # 해당 파일의 텍스트를 contents 변수에 문자열로 저장
>>> print(contents)                # ‘dream.txt’ 파일에 있는 텍스트를 화면에 출력
>>> f.close()                      # 파일 종료
I have a dream a song to sing
to help me cope with anything
if you see the wonder of a fairy tale
you can take the future even
if you fail I believe in angels
something good in everything

때때로 텍스트 파일을 다룰 때 이미 수정하고 있는 파일을 다른 프로그램에서 동시에 호출하면 에러가 발생

하나의 파이썬 프로그램이 하나의 파일을 사용할 때 사용을 완료하면 반드시 해당 파일을 종료해야 함

with문과 함께 사용하기

>>> with open ("dream.txt ", "r") as my_file:
        contents = my_file.read()
        print(type(contents), contents)
<class 'str’> I have a dream a song to sing
to help me cope with anything
if you see the wonder of a fairy tale
you can take the future even
if you fail I believe in angels
something good in everything

with문은 들여쓰기를 하는 동안에는 open() 함수가 유지되고, 들여쓰기가 끝나면 open() 함수도 종료되는 방식

close() 함수를 명시적으로 쓰지 않아도 파일의 사용을 종료할 수 있음

해당 파일 객체는 =로 할당하는 방식이 아니라 as문을 사용하여 변수명에 할당

한 줄씩 읽어 리스트형으로 반환하기

>>> with open("dream.txt","r") as my_file:
        content_list = my_file.readlines()   # 파일 전체를 리스트로 반환
        print(type(content_list))            # 자료형 확인
        print(content_list)                  # 리스트값 출력
<class 'list'〉
['I have a dream a song to sing \\n', 'to help me cope with anything \\n', 'if you
see the wonder of a fairy tale \\n', 'you can take the future even \\n', 'if you
fail. I believe in angels \\n', 'something good in everything \\n']

파일 전체의 텍스트를 문자열로 반환하는 read() 함수 대신, readlines() 함수를 사용하여 한 줄씩 내용을 읽어와 문자열 형태로 저장 가능

한 줄의 기준은 \n 으로 나뉘어지고 리스트로 반환될 때 for문 등 다양한 형태로 활용 가능

실행할 때마다 한 줄씩 읽어 오기

>>> with open("dream.txt", "r") as my_file:
        i = 0
        while 1:                           # 코드가 항상 수행되도록
            line = my_file.readline()      # 한 줄씩 파일 읽기
            if not line:
                break
            print(str(i) + "===" + line.replace("\\n","")  # 한 줄씩 값 출력
            i = i + 1
0 === I have a dream a song to sing
1 === to help me cope with anything
2 === if you see the wonder of a fairy tale
3 === you can take the future even
4 === if you fail I believe in angels
5 === something good in everything

파일의 내용을 찾다가 중간에 멈춰야 할 필요가 있는 대용량 데이터에 사용되는 코드

파일에 저장된 글자의 통계 정보 출력하기

파일에 저장된 모든 텍스트를 읽어온 후 차례대로 글자 수, 단어 수, 줄 수를 출력하는 프로그램

>>> with open("dream.txt", "r") as my_file:
        contents = my_life.read()
        word_list = contents.split(" ")     # 빈칸 기준으로 단어를 분리하여 리스트
        line_list = contents.split("\\n")    # 한 줄씩 분리하여 리스트
>>> 
>>> print("총 글자의 수:", len(contents))
>>> print("총 단어의 수:", len(word_list))
>>> print("총 줄의 수:", len(line_list))
총 글자의 수 188
총 단어의 수: 35
총 줄의 수: 7

파일 쓰기

인코딩 : 텍스트 파일을 저장하기 위해서는 저장할 때 사용하는 표준을 지정

  • 인코딩 옵션으로 인코딩 표준을 지정할 수 있음
  • 일반적으로 utf8을 많이 사용, 파일을 윈도우에서만 사용한다면 cp949도 사용
  • 운영체제나 파일의 사용 환경에 따라 다르게 설정
>>> f = open("count_log.txt", 'w', encoding = "utf8")
>>> for i in range(l,ll):
        data ="%d번째 줄이다.\\n"%i
        f.write(data)
>>> f.close()

‘count_log.txt’라는 파일을 w’ 옵션으로 읽어온 후

1부터 10까지의 숫자를 넣어 ‘1 번째 줄이다.’부터 ‘10번째 줄이다.’까지 문자열을 만듦

f .write(data) 코드로 count_log.txt 파일에 차례대로 기록

모든 내용을 기록한 후에 f.close() 코드로 해당 파일을 닫음

파일 열기 모드 a로 새로운 글 추가하기

w는 늘 새로운 파일을 생성

ex) 기존의 count_log.txt 파일이 있음에도 다시 한번 w로 파일을 부르면 기존 파일이 삭제되고 새로운 파일이 생겨 새로운 내용만 기록 됨

기존 파일에 계속 추가해야 하는 작업이 있을 수 → 추가 모드 a를 사용

>>> with open("count_log.txt", 'a', encoding = "utf8") as f:
        for i in range(1,11):
            data = "%d번째 줄이다.\\n" % i
            f.write(data)

기존에 countjog.txt 파일이 있다는 가정에서 ‘a’ 모드로 파일을 엶

코드를 수행할 때마다 해당 파일에 ‘1 번째 줄이다.’부터 ‘10번째 줄이다.’라는 텍스트가 계속 추가 됨

디렉터리 만들기

파이썬으로는 파일만 다루는 것이 아니라 디렉터리도 함께 다룰 수 있음

os 모듈을 사용하면 디렉터리를 쉽게 만들 수 있으며 디렉터리를 생성하는 코드는 log

>>> import os
>>> os.mkdir("log")

프로그램 대부분이 새로 실행되므로 기존에 해당 디렉터리가 있는지 확인하는 코드가 필요

>>> import os
>>> os.mkdir("log")
>>>
>>> if not os.path.isdir("log")   # 기존 디렉터리의 존재 여부 확인
        os.mkdir("log")
Traceback (most recent call last):
File "mkdir2.py", line 2, in <modulo
os.mkdir("log")
FileExistsError: [WinError 183] 파일이 이미 있으므로 만들 수 없습니다: 'log'

이전 코드에서 log 폴더를 생성했으므로 FileExistsError가 결과로 출력

이전에 생성된 log 폴더를 지우면 log 폴더가 생성됨

로그 파일 만들기

실제 프로그램에서 사용할 수 있는 로그 파일을 만들어보자

로그 파일 : 프로그램이 동작하는 동안 여러 가지 중간 기록을 저장하는 역할을 하는 파일

ex) 웹 프로그램의 경우 외부 접속자의 접속 기록이나 접속 시간 등을 저장하는 파일

>>> import os
>>>
>>> if not os.path.isdir("log"):
        os.mkdir("log")
>>>
>>> if not os.path.exists("log/count_log.txt"):
        f = open("log/cont_log.txt", 'w', encoding = "utf8")
        f.write("기록이 시작된다.\\n")
        f.close()
>>>
>>> with open("log/count_log.txt", 'a', encoding = "utf8") as f:
        import random, datetime
        for i in range(1,11):
            stamp = str(datetime.datetime.now())
            value = random.random() * 1000000
            log_line = stamp + "\\t" + str(value) + "값이 생성되었다." + "\\n"
            f.wirte(log_line)

log 디렉터리가 존재하지 않을 경우 새롭게 디렉터리를 만듦

는 기존에 한 번도 로그 기록이 없었다면 ‘w’ 모드로 count_log.txt 파일을 생성하고 기록의 시작을 알리는 문구를 저장

예시를 만들기 위해 임의로 시간 기록과 함께 임의의 숫자를 문구 안에 계속 기록하여 저장

딱 10번의 기록을 시행하지만 실제로는 해당 코드가 호출할 때마다 시간과 함께 임의의 숫자가 계속 기록됨


pickle 모듈

파이썬 프로그램을 실행할 때 생성되는 여러 변수와 객체는 순간적으로 메모리에 로딩되었다가 프로그램이 종료되면 사라짐

때로는 이렇게 사용되는 객체를 저장시켜 놓고 필요할 때 다시 불러야 하는 경우가 있음

  • 영속화 : 필요한 객체를 파일로 저장시켜 다시 사용할 수 있도록 하는 것

파이썬은 pickle 모듈을 제공하여 메모리에 로딩된 객체를 영속화할 수 있도록 지원

리스트에 들어간 데이터나 클래스의 오브젝트 등을 파일로 저장시켜 나중에 다시 사용할 수 있도록

저장해야 하는 정보가 많아지고 계산 결과를 저장하였다가 나중에 다시 불러 사용할 경우에도 요긴하게 쓸 수 있는 기능

>>> import pickle
>>>
>>> f = open("list.pickle, "wb")
>>> test = [1,2,3,4,5]
>>> pickle.dump(test,f)   # (저장할 객체, 저장될 파일) -> 해당 객체가 해당 파일에 저장
>>> f.close()

파일을 생성할 때는 ‘w’가 아닌 ‘wb’로 열어야 하는데. 여기서 B는 바이너 리 binary를 뜻하는 약자로 텍스트 파일이 아닌 바이너 리 파일로 저장된다는 의미

>>> f = open("list.pickle", "rb")
>>> test_pickle = pickle.load(f)
>>> print(test_pickle)
[1, 2, 3, 4, 5]
>> f.close

저장된 picle 파일을 불러오는 프로세스

pickle 객체를 불러올 때 해당 객체가 어떤 객체인지에 대한 정보가 전혀 없어 상당히 혼란스러울 때도 있지만, 해당 코드를 개발한 개발자라면 코드의 인터페이스를 정확히 알고 있으므로 어려움 없이 사용할 수 있음

는 앞에서 리스트 객체를 list.pickle 파일에 저장했기 때문에 해당 파일을 불러 사용할 때도 동일하게 리스트 객체가 반환

>>> class Mutltiply(object):
        def __init__(self, multiplier):
        self.multiplier = multiplier
        def multiply(self, number):
        return number * self.multiplier
>>> muliply =Mutltiply(5)
>>> muliply.multiply(10)
50

사용자가 직접 생성한 클래스 객체도 저장

곱셈을 처리하는 클래스를 생성

클래스는 처음 객체를 생성할 때 초깃값을 생성하고, multiply() 함수를 부를 때마다 ‘초깃값 * number’의 값을 호출하는 클래스 (곱셈 클래스)

>>> import pickle
>>>
>>> f = open("multiply_object.pickle", "wb")
>>> pickle.dump(muliply, f)
>>> f.closeO
>>>
>>> f = open("multiply_object.pickle", "rb")
>>> multiply_pickle = pickle.load(f)
>>> multiply_pickle.multiply(5)
25

매우 복잡한 연산도 따로 저장하여 사용할 때가 존재

이러한 저장 모듈을 효율적으로 사용하기 위해 다음 코드처럼 pickle 모듈을 사용