또르르's 개발 Story

[05] Python Exception/File/Log /data Handling 본문

부스트캠프 AI 테크 U stage/이론

[05] Python Exception/File/Log /data Handling

또르르21 2021. 1. 22. 22:38

오늘은 Python의 Exception handling, File handling, Log handling, Data handling에 대해서 정리해보겠습니다.

프로그램을 개발하게 되면 다양한 상황을 마주하게 되는데 위와 같은 handling 작업을 통해 문제들을 해결해나갈 수 있습니다.

 

 

1️⃣ Exception handling

 

프로그램을 사용하거나 개발하게 되면 뜻하지 않은 오류들을 마주하게 되는데요. 이상한 데이터 값을 삽입했다거나 모듈이 충돌하거나, 값이 의도치 않게 수정되거나하는 문제들을 겪으셨을 것입니다.

 

이러한 예외들을 처리해주는 방법이 Exception handling입니다. 프로그램을 사용하다가 발생하는 상황들에 대한 대처 방안(?)이라고 말할 수 있습니다.

 

Python에서는 Exception handler를 아래와 같이 사용합니다.

 

 

1) try ~ except 문법

try:

    예외 발생 가능 코드
    
except <Exception Type>:

    예외 발생시 대응하는 코드

try에는 일반 코드를 작성하고, except에는 Exception type과 예외가 발생하면 어떻게 대응할 건지에 대한 코드를 작성합니다.

 

여기서 Exception type이란 어떤 except가 발생했는지에 대한 구분이며, 아래 표와 같은 Exception들이 존재합니다. (이보다 훨씬 많습니다.)

하지만 Exception type을 모르겠다면 Exception을 적어도 됩니다. 그러면 모든 exception 상황들이 Exception에 출력이 됩니다.

 

즉, ZeroDivisionError 이런 식으로 적으면 코드 리뷰를 할 때 무슨 Exception을 주의해야 되는지 명시돼서 좋은 점이 있지만

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

아래와 같이 Exception만 적어도 나오게 됩니다. (e라는 별칭을 사용하고 print로 찍으면 무슨 exception이 발생했는지 알 수 있습니다.)

for i in range(10):

    try:
    
        print(10 / i)
        
    except Exception as e:
    
        print(e)
      
      
division by zero
10.0
5.0
3.3333333333333335
2.5
2.0
1.6666666666666667
1.4285714285714286
1.25
1.1111111111111112

 

이번에는 try ~ except에서 확장돼서 나온 구문에 대해 알아봅시다.

 

try ~ except ~ else는 예외가 발생되지 않았을 때도 동작하는 부분이 있습니다.

try:

    예외 발생 가능 코드
    
except <Exception Type>:

    예외 발생시 동작하는 코드
    
else:

    예외가 발생하지 않을 때 동작하는 코드

 

try ~ except ~ finally는 예외가 발생하더라도 finally부분은 무조건 실행되게 만들 수 있습니다.

try:

    예외 발생 가능 코드
    
except <Exception Type>:

    예외 발생시 동작하는 코드
    
finally:

    예외 발생 여부와 상관없이 실행됨

 

raise는 필요에 따라 강제로 Exception을 발생시킬 수 있습니다.

try ~ except는 for문에서 예외가 발생해도 다른 값들을 계속 순회하지만 raise는 Exception을 발생시켜 프로그램을 종료시킵니다. 

raise <Exception Type>(예외정보)
while True:

    value = input("변환할 정수 값을 입력해주세요")
    
    for digit in value:
    
        if digit not in "0123456789":
        
            raise ValueError("숫자값을 입력하지 않으셨습니다")
            
    print("정수값으로 변환된 숫자 -", int(value))
    
    
ValueError: 숫자값을 입력하지 않으셨습니다

 

assert는 특정 조건에 만족하지 않을 경우 예외를 발생시킵니다.

assert 예외조건
def get_binary_nmubmer(decimal_number):

    assert isinstance(decimal_number, int)
    
    return bin(decimal_number)

>>> print(get_binary_nmubmer(10))

0b1010

>>> print(get_binary_nmubmer(10.0))

AssertionError:

 

 

2️⃣ File handling

 

파일 핸들링은 Python에서 파일을 읽고 쓰고 실행할 수 있게 도와주는 핸들러입니다. 파일 핸들러는 파일뿐만 아니라 디렉토리까지 추가, 삭제, 변경이 가능합니다.

 

 

1) File handling

 

파일 핸들링을 말씀드리기 앞서 파일의 종류에 대해서 말씀드리겠습니다.

파일에는 text파일binary파일이 존재합니다.

 

  • Text 파일

    - 인간도 이해할 수 있는 형태인 문자열 형식으로 저장된 파일

    - 메모장으로 열면 내용 확인이 가능

    - HTML 파일, 메모장 txt 파일, 파이썬 코드 등

  • Binary 파일

    - 컴퓨터만 이해할 수 있는 형태인 이진법 형식으로 저장된 파일

    - 일반적으로 메모장으로 열면 내용이 깨져 보임

    - 엑셀 파일, 워드 파일 등

실제로 모든 text파일도 원래는 binary 파일이며, ASCII/Unicode 문자열 집합으로 저장되어 있기 때문에 사람이 읽을 수 있는 것이라고 합니다.

 

그렇다면 Python으로 File을 열고 닫으려면 어떤 함수를 사용해야 할까요?

 

Python에는 open키워드가 있어 파일을 열 수 있습니다.

반대로 close키워드를 사용해서 파일을 닫아줄 수 있습니다.

f = open("<파일이름>", "접근 모드")

f.close()

 

접근 모드(파일 열기 모드)는 아래와 같습니다.

접근 모드의 "w"와 "a"의 차이는 "w"는 기존의 파일이 삭제되고 새로 파일을 만들지만, "a"를 사용하면 기존의 파일에 이어서 작성합니다.

 

한글파일을 작성하실 때는 encoding="utf8"이라는 구문을 접근 모드 옆에 써주시면 됩니다.

f = open("count_log.txt", 'w', encoding="utf8")

 

또한, with 구문을 사용해서 file을 열 수 있습니다.

장점은 file의 open 아래를 들여 쓰기(indent)를 하기 때문에 들여 쓰기 부분을 벗어나면 자동으로 file close가 된다는 점입니다.

with open("i_have_a_dream.txt","r") as my_file:

     contents = my_file.read()
    
     print (type(contents), contents)

 

파일을 읽어오는 read() 구문은 파일 전체를 읽어오는데 readlines()를 사용하면 파일 전체를 list로 반환해서 돌려주고, readline()을 사용하면 한 줄씩 읽어올 수 있습니다.

 

  • read()

    파일 전체를 읽어옴 (string)

  • readlines()

    파일 전체를 읽어옴 (list)

  • readline()

    파일의 내용을 한 줄씩 읽어옴

 

 

2) Directory handling

 

폴더 핸들링은 Python의 os모듈을 사용해서 directory를 다룰 수 있습니다.

import os

os.mkdir("log")		# 현재 directory에 log라는 directory를 만듭니다.
os.path.exists("abc")		# 현재 path에 abc라는 폴더가 존재하는지
os.path.isfile("file.ipynb")	# 현재 path에 file.ipynb 파일이 있는지

 

최근에는 pathlib 모듈을 사용해서 path를 객체로 다루는 방식을 사용한다고 합니다. 

path의 객체화는 parents와 glob와 같은 함수를 사용할 수 있게 도와줍니다.

import pathlib

>>> cwd = pathlib.Path.cwd()		# 현재 path를 출력합니다.

>>> cwd

WindowsPath('D:/workspace')

>>> cwd.parent				# 현재 path의 parent로 갑니다.

WindowsPath('D:/')

>>> list(cwd.parents)

[WindowsPath('D:/')]

>>> list(cwd.glob("*"))		# 현재 path의 전체 파일을 list형태로 출력합니다.

[WindowsPath('D:/workspace/ai-pnpp'), WindowsPath('D:/workspace/cs50_auto_grader'), WindowsPath('D:/workspace/data-academy'), WindowsPath('D:/workspace/DSME-AI-SmartYard'), WindowsPath('D:/workspace/introduction_to_python_TEAMLAB_MOOC'),

 

또한, shutil 모듈을 사용할 수 있는데 copy 메서드를 통해 파일을 복사할 수 있습니다.

import shutil

source = "i_have_a_dream.txt"

dest = os.path.join("abc", "test.txt")		# "abc\\test.txt"

# os.path.join을 사용하는 이유는 window와 mac에서 폴더 기준 구분이 다르기 때문에 이 명령어 사용

shutil.copy(source, dest)

 

마지막으로 pickle 모듈을 사용하면 객체를 영속화(persistence, 프로그램이 종료돼도 반영구적으로 저장)가 가능합니다.

프로그램에서 실행 중인 객체나 데이터를 pickle 모듈을 사용해서 저장하면 다른 프로그램에서 불러와서 사용이 가능합니다.

# 1번 program

import pickle

f = open("list.pickle", "wb")	# write binary로 저장

test = [1, 2, 3, 4, 5]

pickle.dump(test, f)		# dump를 하면 pickle에 저장

f.close()
# 2번 프로그램

import pickle

f = open("list.pickle", "rb")		# read binary형식으로 open합니다.

test_pickle = pickle.load(f)		# load를 하면 2번 프로그램에서 test 객체를 사용할 수 있습니다.

print(test_pickle)

f.close()

 

 

3️⃣ Log handling

 

로그 핸들링은 Python에서 프로그램이 실행되는 동안 일어나는 정보를 기록하는 것입니다.

로그를 기록해놓으면 나중에 분석을 통해 의미 있는 결과를 도출할 수 있는데요. 이 때문에 거의 모든 프로그램이 로그를 기록한다고 해도 과언이 아닙니다.

 

로그는 실행 시점에서 남겨야 하는 기록, 개발시점에서 남겨야 하는 기록이 있습니다.

실행 시점에서 남겨야 하는 기록은 사용자들의 패턴 분석 등에 사용되고,

개발시점에서 남겨야 하는 기록은 버그 검출 등에 사용됩니다.

 

 

1) logging 모듈

 

Python에서는 logging 모듈을 사용해서 로그를 남길 수 있습니다.

import logging

logging.debug("틀렸잖아!")

logging.info("확인해")

logging.warning("조심해!")

logging.error("에러났어!!!")

logging.critical ("망했다...")

 

logging 모듈에는 logging level이라는 것이 있는데요.

debug, info, warning, error, critical 다섯 가지의 level이 있고 남겨지는 로그 level을 설정할 수 있습니다.

로그의 우선순위는 아래와 같습니다.

DEBUG > INFO > WARNING > ERROR > CRITICAL

logging을 사용하면 getLogger()로 Logger을 선언해줍니다.

logging의 setLevel()을 사용하면 level을 지정할 수 있습니다. (default는 warning level)

=> Python 3.8 버전 이상이신 분들은 basicConfig()를 사용하셔야 합니다.

import logging

if __name__ == '__main__':

    logger = logging.getLogger("main") # Logger 선언
    
    #logging.basicConfig(level=logging.DEBUG) # Python 3.8버전부터
    
    logger.setLevel(logging.DEBUG) # 이전버전

    logger.debug("틀렸잖아!")
    
    logger.info("확인해")
    
    logger.warning("조심해!")
    
    logger.error("에러났어!!!")
    
    logger.critical("망했다...")
    

DEBUG:main:틀렸잖아!
INFO:main:확인해
WARNING:main:조심해!
ERROR:main:에러났어!!!
CRITICAL:main:망했다...

로그는 console에 출력하는 것보다 file로 많이 저장을 합니다.

이 경우에는 logging.FileHandler를 사용하면 로그가 파일에 저장이 됩니다.

import logging

if __name__ == '__main__':

    logger = logging.getLogger("main") # Logger 선언
    
    logger.setLevel(logging.DEBUG) # 이전버전
    
    steam_handler = logging.FileHandler("my.log", mode="a", encoding="utf8")	# filehandler로 my.log에 append 형태로 저장
    
    logger.addHandler(steam_handler)	#handler를 추가해줍니다.

또한, logging에는 특정한 format을 출력할 수 있는 Formatter도 지원합니다.

>>> formatter = logging.Formatter('%(asctime)s %(levelname)s %(process)d %(message)s')

2018-01-18 22:47:04,385 ERROR 4410 ERROR occurred
2018-01-18 22:47:22,458 ERROR 4439 ERROR occurred
2018-01-18 22:47:22,458 INFO 4439 HERE WE ARE
2018-01-18 22:47:24,680 ERROR 4443 ERROR occurred
2018-01-18 22:47:24,681 INFO 4443 HERE WE ARE
2018-01-18 22:47:24,970 ERROR 4445 ERROR occurred
2018-01-18 22:47:24,970 INFO 4445 HERE WE ARE

 

 

 

2) configparser

 

프로그램은 실행 설정(config)을 file에 저장하는데 Section, Key, Value 형태로 설정 파일을 설정합니다.

Python에서는 configparser를 사용하면 Dict Type형태로 호출해서 사용할 수 있습니다.

 

config file은 아래와 같이 존재합니다.

[SectionOne]

Status: Single

Name: Derek

Value: Yes

Age: 30

Single: True



[SectionTwo]

FavoriteColor = Green



[SectionThree]

FamilyName: Johnson

 

configparser을 사용하면 config파일을 Dict type으로 불러올 수 있습니다.

import configparser

config = configparser.ConfigParser()

config.sections()

config.read('example.cfg') #cfg 파일은 config파일의 확장자

config.sections()


>>> for key in config['SectionOne']:

        print(key)
        
status
name
value
age
single
    
    
>>> config['SectionOne']["status"]

'Single'

 

3) argparser

 

터미널이나 Console창에서 명령어를 입력하고 -을 추가해서 추가 명령어를 입력한 것을 보신 적 있으실 겁니다.

아래 명령어도 git에 커밋을 하는데 message를 "Update my code"로 하라는 명령입니다. 

git commit -m "Update my code"

이런 것처럼 추가적인 명령어(Command-Line Option)를 처리해주는 모듈이 argparse입니다.

argparse는 아래와 같이 사용합니다.

add_argument를 할 때 parameter는 (짧은 이름, 긴 이름, 표시명-코드에서 사용할 변수 이름, help 설명, arument type)을 넣어줍니다.

# argument.py

import argparse

parser = argparse.ArgumentParser(description='Sum two integers.')

parser.add_argument('-a', "--a_value", dest="a", help="A integers", type=int, required=True)

# 짧은 이름, 긴 이름, 표시명, help 설명, argument type으로 나뉨

parser.add_argument('-b', "--b_value", dest="b", help="B integers", type=int, required=True)

args = parser.parse_args()

print(args)

print(args.a)

print(args.b)

print(args.a + args.b)

위의 코드를 prompt창에서 실행시키면 a value와 b value를 입력하라고 뜹니다.

>>> python argument.py

usage: arugment.py [-h] -a A_VALUE -b B_VALUE
arugment.py: error: the following arguments are required: -a/--a_value, -b/--b_value

-h를 argument로 넣으면 optional aruments에 대해 보여줍니다. 

(따로 입력 안 해도 자동으로 -h가 만들어지는 게 신기했습니다.)

>>> python arugment.py -h

usage: arugment.py [-h] -a A_VALUE -b B_VALUE

Sum two integers.

optional arguments:
  -h, --help            show this help message and exit
  -a A_VALUE, --a_value A_VALUE
                        A integers
  -b B_VALUE, --b_value B_VALUE
                        B integers

-a에 5를 넣고 -b에 10을 넣으면 아래와 같이 출력이 됩니다.

>>> python argument.py -a 5 -b 10

Namespace(a=5, b=10)
5
10
15

 

 

4️⃣ data handling

 

Python은 정말 강력한 데이터 핸들링은 제공합니다.

Python에서는 데이터들을 원하는 방식으로 저장하고 수정할 수 있죠.

가장 많이 데이터를 저장하는 방식인 CSV, 웹(html), XML, JSON에서 어떻게 Python이 데이터를 가지고 오는지 정리해보았습니다.

 

 

1) CSV

 

CSV는 Comma separate Values의 약자로서 Comma(,)로 구분된 데이터 형식입니다.

즉, 엑셀 양식의 데이터를 프로그램에 상관없이 쓰기 위해 만들어진 데이터 형식이라고 합니다.

 

Python에서 CSV 파일을 읽기 위해서는 file open으로 열어 split(', ')로 구분해주어도 되지만

Python에는 CSV 모듈을 지원합니다.

import csv

csv 모듈의 reader()은 아래와 같이 사용할 수 있습니다.

특히, quotechar에 큰따옴표(")가 들어가 있는데 큰따옴표(")로 구성된 문장이 나올 경우 delimiter를 skip 할 수 있습니다.

reader = csv.reader(f, delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL)

 

2) 웹(HTML)

 

은 World Wide web의 약자로서 우리가 하루에 가장 많은 시간을 보내는 곳이기도 합니다.

 

HTML은 웹 상의 정보를 구조적으로 표현한 언어이기 때문에 <Tag>를 사용해서 제목, 단락, 링크 등의 요소를 구분합니다.

기본적으로 HTML의 모양은 아래와 같습니다.

<!doctype html>
<html>
  <head>
    <title>Hello HTML</title>
  </head>
  <body>
    <p>Hello World!</p>
  </body>
</html>

 

Python에서 웹을 분석하는 기법은 여러 가지가 있지만 가장 많이 사용하는 것이 정규식(regular expression)입니다.

정규식에 대해서는 [4-1] 참조해주세요.

 

[04-1] Python 정규 표현식(Regular Expression)

오늘 과제를 하면서 정규 표현식(Regular Expression)을 써야 하는 일이 생겼습니다. 그래서 정리해보았습니다. 1️⃣ 정규표현식 (Regular Expression) 정규표현식 regex는 특정한 규칙을 가진 문자열 패턴

dororo21.tistory.com

 

Python에서 웹 정보를 가지고 오기 위해서는 uurllib.request를 사용하게 됩니다.

import urllib.request

url = "https://bit.ly/3rxQFS4"

html = urllib.request.urlopen(url)	# url의 HTML 소스코드를 받아옵니다.

html_contents = str(html.read())

html을 받아오게 되면 regex를 사용해 원하는 패턴의 데이터를 찾을 수 있습니다.

import re

import urllib.request

url ="https://bit.ly/3rxQFS4"

html = urllib.request.urlopen(url)

html_contents = str(html.read())

id_results = re.findall(r"([A-Za-z0-9]+\*\*\*)", html_contents)

#findall 전체 찾기, 패턴대로 데이터 찾기

>>> for result in id_results:

        print (result)
 

codo***
outb7***
dubba4***
...

아래의 코드는 구글에 등록되어있는 특허정보들을 받아오는 코드입니다.

import urllib.request # urllib 모듈 호출

import re

url = "http://www.google.com/googlebooks/uspto-patents-grants-text.html"	#url 값 입력

html = urllib.request.urlopen(url)				# url 열기

html_contents = str(html.read().decode("utf8"))			# html 파일 읽고, 문자열로 변환

url_list = re.findall(r"(http)(.+)(zip)", html_contents)	# http로 시작해서 zip으로 끝나는 모든 문자열 list에 반환

for url in url_list:

    print("".join(url)) 					# 출력된 Tuple 형태 데이터 str으로 join
    
    
    
http://storage.googleapis.com/patents/grant_full_text/2015/ipg150106.zip
http://storage.googleapis.com/patents/grant_full_text/2015/ipg150113.zip
http://storage.googleapis.com/patents/grant_full_text/2015/ipg150120.zip
http://storage.googleapis.com/patents/grant_full_text/2015/ipg150127.zip
http://storage.googleapis.com/patents/grant_full_text/2015/ipg150203.zip
....

아래 코드는 네이버 finance의 삼성전자 종목을 불러오는 코드입니다.

소스코드의 <dl class="blind"> ~~~~ </dl> 부분을 검출하기 위해 (\<dl class=\"blind\"\>)([\s\S]+?)(\<\/dl\>) 정규식을 사용합니다.

import urllib.request

import re

url = "http://finance.naver.com/item/main.nhn?code=005930"

html = urllib.request.urlopen(url)

html_contents = str(html.read().decode("ms949"))

stock_results = re.findall("(\<dl class=\"blind\"\>)([\s\S]+?)(\<\/dl\>)", html_contents)

samsung_stock = stock_results[0] # 두 개 tuple 값중 첫번째 패턴

samsung_index = samsung_stock[1] # 세 개의 tuple 값중 두 번째 값

# 하나의 괄호가 tuple index가 됨

index_list= re.findall("(\<dd\>)([\s\S]+?)(\<\/dd\>)", samsung_index)

>>> for index in index_list:

        print (index[1]) # 세 개의 tuple 값중 두 번째 값
        
        
2021년 01월 221610분 기준 장마감
종목명 삼성전자
종목코드 005930 코스피
현재가 86,800 전일대비 하락 1,300 마이너스 1.48 퍼센트
전일가 88,100
시가 89,000
고가 89,700
상한가 114,500
저가 86,800
하한가 61,700
거래량 30,430,330
거래대금 2,679,425백만

 

 

3) XML

 

XML은 eXtensible Markup Language의 약자로서 TAG를 사용해서 표시하는 언어입니다.

<TAG>와 <TAG> 사이에 값이 표시되고, 구조적인 정보를 표현할 수 있습니다.

 

XML의 기본적인 구조입니다.

<?xml version="1.0"?>
  <books>
    <book>
      <author>Carson</author>
      <price format="dollar">31.95</price>
      <pubdate>05/01/2001</pubdate>
    </book>
    <pubinfo>
      <publisher>MSPress</publisher>
      <state>WA</state>
    </pubinfo>
  </books> 

 

Python에서 XML의 데이터를 읽기 위해서는 여러 가지 방법(정규식 등)이 있습니다.

하지만 가장 많이 쓰이는 parser는 beautifulsoup이며, 이 parser에 대해서 정리해보겠습니다.

 

beautifulsoup를 사용하기 위해서는 conda(또는 pip)에서 lxml과 beautifulsoup를 설치해야 합니다.

conda install lxml

conda install -c anaconda beautifulsoup4=4.5.1 

모듈은 bs4에서 BeautifulSoup을 가지고 올 수 있습니다.

from bs4 import BeautifulSoup

BeautifulSoup에는 정규식과 마찬가지로 find_all를 사용해서 해당 패턴을 모두 반환할 수 있습니다.

from bs4 import BeautifulSoup

with open("books.xml", "r", encoding="utf8") as books_file:

    books_xml = books_file.read() 		# File을 String으로 읽어오기
    
soup = BeautifulSoup(books_xml, "lxml") 	# lxml Parser를 사용해서 데이터 분석

for book_info in soup.find_all("author"):	# author가 들어간 모든 element 추출

    print (book_info)
    
    print (book_info.get_text())		# get_text()는 반환된 패턴의 값 반환

 

 

4) JSON

 

JSON은 JavaScript Object Notation의 약자로서 웹에서 데이터를 주고받을 때 사용하는 데이터 객체입니다.

 

JSON 코드는 Python의 Dict type과 유사합니다.

www.flickr.com/photos/xmodulo/26106186415

 

Python에서는 json 모듈을 사용해서 json을 손쉽게 dict으로 변환할 수 있습니다.

import json

with open("json_example.json", "r", encoding="utf8") as f:

    contents = f.read()
    
    json_data = json.loads(contents)	# loads를 사용해서 쉽게 가지고 올 수 있습니다.
    
    print(json_data["employees"])

반대로 dict 형태를 json으로 변환하기 위해서는 dump() 메서드를 사용하면 됩니다.

import json

dict_data = {'Name': 'Zara', 'Age': 7, 'Class': 'First'}

with open("data.json","w") as f:

    json.dump(dict_data, f)

 

JSON은 웹에서 데이터를 주고받을 때 정말 많이 사용되기 때문에 알아두면 쓸모가 많습니다.

 


Comments