본문 바로가기
python

네카라쿠배 프론트엔드 취업완성 스쿨 2기 2차 테스트 4일차 학습

by 새우하이 2021. 6. 17.

에러 및 예외 처리

예외는 에러를 뜻한다. 예상치 못한 에러가 발생할 수도 있고 잘못된 코딩이나 잘못된 형변환등 미세한 실수 때문에 에러가 나기도 한다. 아무리 코드가 무결성하더라도 하드웨어 등에서 뜬금없이 나오는 오류까진 완벽하게 처리하기는 어렵기 때문이다.

문법적으로 에러가 없지만, 코드 실행(런타임)프로세스에서 발생하는 예외 처리도 중요하다.

SyntaxError : 잘못된 문법

print('test) 
# SyntaxError: EOL while scanning string literal

if True
    pass
# SyntaxError: invalid syntax

예제와 같이 '나 : 등 문법적으로 잘못된 경우 발생하는 에러이다.

NameError : 참조변수 없음

a = 10 
print(c)
# NameError: name 'c' is not defined

참조하려는 변수가 정의되지 않았을 때 발생하는 에러이다.

ZeroDivisionError : 0으로 나누기 에러

print(10/0)
#ZeroDivisionError: division by zero

0으로는 나누기가 불가능한데 0으로 나눠질 때 발생하는 에러이다.

IndexError : index 범위 오버

li = [1,2,3]
print(li[3])

# IndexError: list index out of range

리스트를 다룰 때 종종 발생하는 에러로 index범위 밖의 index에 접근하려 하면 발생한다.

KeyError : 없는 key를 조회했을 때 발생함

dic = {'name':'kim', 'age':33, 'city':'seoul'}
print(dic['hobby'])
#KeyError: 'hobby'

없는 key를 조회하려 할 때 발생한다. 하지만 이는 없는 키를 조회하면 None을 반환해주는 get메서드를 사용하면 방지할 수 있다.

AttributeError : 모듈, 클래스에 있는 잘못된 속성 사용시에 예외

import time
print(time.time()) # 잘 실행됨
print(time.test()) # 에러 발생

불러온 모듈에 대해 있는 속성은 사용이 가능하지만 test()같이 없는 attribute는 AttributeError 를 발생시킨다.

ValueError : 참조값이 없을 때 발생

x = [1,5,9]
x.remove(10)
x.index(10)
# ValueError: list.remove(x): x not in list

리스트에 포함되어있지 않은 요소를 제거하려고 할때나 접근하려고 할 때 발생함

FileNotFoundError

f = open('test.txt', 'r')
# FileNotFoundError: [Errno 2] No such file or directory: 'test.txt'

주로 외부 파일을 처리할 때 발생하는 에러로 없는 파일을 처리하려고 할 때 발생한다.

TypeError

x = [1,2]
y = (1,2)
z = 'test'

print(x+y)
print(y+z)
# TypeError: can only concatenate list (not "tuple") to list

튜플과 리스트는 결합이 안되고 문자열과 리스트, 문자열과 튜플도 결합할 수 없다. 형변환을 통해 처리가 가능하다.

EAFP

EAFP(It's Easier to Ask Forgiveness than Permission) 허락보다 용서구하는것이 쉽다

라는 코딩스타일로 항상 예외가 발생하지 않을 것으로 가정하고 먼저 코딩을한뒤 런타임 에러발생시 예외 처리 코딩을 권장하는 방식이다.

EAFP는 python에서 표준이다.

예외 처리 기본

try : 에러가 발생할 가능성이 있는 코드를 실행할 때 사용

except : 에러명 1

except : 에러명 2

else : 에러가 발생하지 않았을 경우 실행

finally : 항상 실행

name = ['kim','park','lee']

try:
    z = 'kim'
    x = name.index(z)
    print('{} Found it!'.format(z,x+1))
except ValueError:
    print(' Not found it! ')

이 상황에서는 리스트에 kim이라는 요소가 있기 때문에 에러가 발생하지 않겠지만 없는 요소를 찾으려고하면 ValueError 가 발생할 것을 예상할 수 있을 것이다. 그럴때 ValueError를 캐치하여 print로 관리자에게 알려줄 수 있다.

name = ['kim','park','lee']

try:
    z = 'cho'
    x = name.index(z)
    print('{} Found it!'.format(z,x+1))
except ValueError:
    print(' Not found it! ')

이 예제에서는 Not found it!이 출력될 것이다.

name = ['kim','park','lee']

try:
    z = 'kim'
    x = name.index(z)
    print('{} Found it!'.format(z,x+1))
except ValueError:
    print(' Not found it! ')
else:
    print("ok")

# kim Found it!
# ok

else는 try 에서 정상적으로 실행되었을 때 추가적으로 수행할 작업들에 대해 사용한다.

우리가 위에서 ValueError 처럼 예측가능한 에러를 예외처리하면 더욱 좋겠지만. 만약 어떤 에러가 발생할지 알 수 없는 경우에는

name = ['kim','park','lee']

try:
    z = 'choi'
    x = name.index(z)
    print('{} Found it!'.format(z,x+1))
except Exception:
    print(' Not found it! ')
else:
    print("ok")

그냥 except 만 사용해서 에러를 잡아낼 수 있다.

name = ['kim','park','lee']

try:
    z = 'choi'
    x = name.index(z)
    print('{} Found it!'.format(z,x+1))
except Exception:
    print(' Not found it! ')
else:
    print("ok")
finally:
    print('finally')

fianlly는 에러가 발생하던 발생하지 않던 무조건 실행시켜준다. 즉 try에서 외부프로그램에 연결하는 작업을 했다면 에러발생여부를 떠나 해당 연결을 끊어주는 작업을 해야할 것이다. 그런 무조건적인 수행을 위해서 finally를 사용한다.

예외처리는 하지 않지만, 무조건 수행되는 코딩패턴에서

try:
    <수행>
finally:
    <수행>

이런식의 패턴도 많이 사용한다.

try:
    z = 'choi'
    x = name.index(z)
    print('{} Found it!'.format(z,x+1))
except ValueError:
    print(' Not found it! ')
except IndexError:
    print('index Error')
except Exception:
    print('Error')
else:
    print("ok")
finally:
    print('finally')

여러개의 except를 사용하여 계층적으로 에러를 잡는것이 가능하다.

except Exception는 default로 모든에러를 잡기때문에 제일 아래에 위치하는게 계층적으로 에러를 찾아내기에 적절하다.

alias

name = ['kim','park','lee']

try:
    z = 'choi'
    x = name.index(z)
    print('{} Found it!'.format(z,x+1))
except ValueError as v:
    print(v)
else
    print("ok")
finally:
    print('finally')

as 키워드를 사용해서 에러 내용을 직접 타이핑하여 출력하지 않고 에러내용을 출력하는 것도 가능하다.

예외 발생 : raise

raise 키워드로 예외를 직접 발생시킬 수 있다.

try:
    a = 'park'
    if a == 'park':
        print('허가')
    else:
        raise ValueError
except ValueError:
    print('문제발생')
except Exception as f:
    print(f)
else:
    print('ok')

만약 a 가 park 이 아니라면 ValueError 를 발생시킨다. ValueError는 원래 이런상황에 발생하는 에러가 아니지만 ValueError를 일으키고 except ValueError로 해당 에러를 잡아서 관리자가 볼 수 있게 할 수 있다.

그리고 못잡을 수 있는 이외의 에러에대해 Exception으로 처리한다.

이대로 실행하면 허가와 ok가 출력되겠지만

a = 'kim'

으로 실행하면

문제발생이 출력될 것이다. 예외 클래스는 다른걸로 직접 지정해도 된다. IndexError로 raise시켜도 된다는 뜻.

외부 파일 처리

CSV : MIME - text/csv

MIME 타입이란 클라이언트에게 전소오딘 문서의 다양성을 알려주기 위한 메커니즘이다. 웹에서 파일의 확장자보다 문서와 함께 올바른 MIME타입을 전송하도록 서버가 정확하게 설정하는 것이 중요하다.

CSV는 text/csv MIME타입을 가지고 .csv 확장자를 가진다

import csv
with open('./resource/sample1.csv','r') as f:
#만약 에러가난다면 파이썬의 기본 인코딩방식인 UTF-8 이아닌 파일의 인코딩방식으로 불러오게 해야한다.
#with open('./resource/sample1.csv','r', encoding='euc-kr') as f:
    reader = csv.reader(f)
    print(reader) # <_csv.reader object at 0x1079723c0>
    print(type(reader)) # <class '_csv.reader'>
    print(dir(reader)) #생략

    for c in reader:
        print(c)

python의 csv 모듈을 import 시켜주고 외부파일을 열어준다. csv를 어떻게 일어오고 type등을 확인하기 위해 print해준다.

csv.reader() 함수는 csvfile의 줄을 이터레이트하는 reader 객체를 반환한다.csv파일에서 읽은 각 행(row)는 문자열 리스트로 반환된다.

파일의 상단에 불필요한 열 이름이 있다면 그 다음줄 부터 불러오게끔 하기 위해 next() 를 호출한다.

```python
      ...
        reader = csv.reader(f)
    next(reader) ## 추가
    print(reader)
        ...

csv.reader 에는 delimiter 라는 옵션을 사용하면 csv에서 구분하기위해 사용하는 기호를 직접 지정해줄 수 있다. 기본적으로 , (콤마)를 사용하지만 예를들어 | (파이프라인)이라면

import csv
with open('./resource/sample2.csv','r', encoding='euc-kr') as f:
    reader = csv.reader(f, delimiter='|') # delimiter 지정
    next(reader)
    print(reader)
    print(type(reader))
    print(dir(reader))

    for c in reader:
        print(c)

delimiter를 지정해줘서 그 문자열을 기준으로 split하여 읽어올 수 있다.

with open('./resource/sample1.csv','r', encoding='euc-kr') as f:
    reader = csv.DictReader(f)

    for c in reader:
        print(c)

csv의 DictReader 메서드를 사용하면

불러온 값들을 dictionary형태의 key와 value로 불러올 수 있다.

for c in reader:
        for k, v in c.items():
            print(k,v)
        print('==============')

이를 활용해서 출력내용을


출력 :

번호 9
이름 김은미
가입일시 2017-02-08 07:44:33
나이 51

===================

번호 10
이름 장혁철
가입일시 2017-12-01 13:01:11
나이 16


이렇게 보여주는것도 가능하다.

생성

w = [[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]]
with open('./resource/sample3.csv', 'w', newline='') as f:
    wt = csv.writer(f)

    for v in w:
        wt.writerow(v)

이중리스트를 하나 선언해두고

쓰기모드로 open한다. 여기서 newline=''이라는 옵션을 사용하는 이유는

파일을 한줄 씩 불러올때도 개행이 자동으로 되고 쓸 때도 개행이 되기때문에 개행이 두번씩 되는것을 막기 위해서다. newline=''을 지우고 확인해보면 무슨말인지 알 수 있다.

그리고 csv의 writer 메서드를 사용해서 작성을 시작하고 w 리스트를 writerow 메서드를 통해 리스트를 한 라인에 추가한다.

그리고 csv.write()메서드에서도 마찬가지로 기본적으로 , (콤마)를 구분문자로 사용하지만 변경하고 싶다면 delimiter 옵션을 지정하면된다.

wt = csv.writer(f, delimter='|')

굳이 for문을 사용하지 않고 한번에 쓰고싶다면

writerrows를 사용하면 된다.

w = [[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15],[16,17,18]]
with open('./resource/sample3.csv', 'w', newline='') as f:
    wt = csv.writer(f, delimiter='|')
    wt.writerows(w)

이미 검증이 끝난 데이터일 때는 이렇게 한번에 사용해도 된다.

하나하나 검증해야하는 경우 for문을 사용하는것이 더 알맞다. 경우에 따라 선택하면 된다.

XSL, XLSX 읽기 쓰기

exel을 처리하는 open source는 상당히 많다.

openpyxl, xlswriter, xlrd, xlwt, xlutils 등등이 있는데 주로

pandas를 주로 사용한다. (가장많이 사용하는openpyxl, xlrd를 pandas가 내부적으로 사용하기 때문이다.)

$ pip install xlrd
$ pip install openpyxl
$ pip install pandas

가상환경을 실행시켜놓은 상태로 위의 3개의 패키지를 설치한다.

엑셀을 불러오기 위해선

import pandas as pd # alias는 필수가 아님
pd.read_excel('경로/파일명.xlsx')

를 사용해서 불러온다.

그리고 read_excel의 몇가지 옵션을 살펴보자

  • sheetname

    sheetname='시트명' 혹은 숫자

    엑셀에서 sheet가 여러개일 때 시트명을 적거나 1~n 번까지의 시트번호를 적어주면된다.

  • header

    header = 숫자

    몇번째 열을 header로 지정할 것인지

  • skiprow

    skiprow = 숫자

    몇번째 행을 skip할 것인지. 해당 행은 불러오지 않는다.

그 다음에

import pandas as pd
xlsx = pd.read_excel('./resource/sample.xlsx')
print(xlsx.head()) 
print(xlsx.tail())
print(xlsx.shape) # 행, 열

head와 tail은 상위 혹은 하위의 5개 행을 보여준다.

shape를 사용하여 파일의 구조를 볼 수 있다. 행과 열을 표시해줌.

Excel or CSV 다시 쓰기

xlsx.to_excel('./resource/result.xlsx', index=False)
xlsx.to_csv('./resource/result.csv', index=False)

읽어왔던 excel 파일을 csv 로 다시 쓰거나 xlsx 파일로 다시 내보낼 수도 있다.

SQL이란

데이터베이스 시스템에서 자료를 처리하는 용도로 사용되는 구조적 데이터 질의 언어이다.

SQLite 연동

import sqlite3

print('sqlite3.version :', sqlite3.version)
print('sqlite3.sqlite_version: ', sqlite3.sqlite_version)

sqlite의 버전을 확인한다.

sqlite3.version 은 sqlite3의 버전을 확인하는것이고

sqlite3.sqlite_version 은 DB엔진 버전이다.

import datetime

now = datetime.datetime.now()
print('now : ', now)

datetime 모듈을 불러오고 datetime의 datetime클래스에 now라는 함수를 now에 담아 출력해보면 년월일시분초 가 출력된다. 이것을 format을 사용해서 보기 편하게 바꿔본다.

nowDatetime = now.strftime('%Y-%m-%d %H:%M:%S')
print(nowDatetime)

strftime 는 명시적인 포맷 문자열로 제어되는 날짜를 나타내는 문자열을 반환한다.

DB 생성 & Auto commit

conn = sqlite3.connect('./resource/database.db', isolation_level=None)

sqlite3의 connect로 db를 생성할 수 있다.

commit 이란 모든 작업을 정상적으로 처리하겠다고 확정하는 것이다.

이를 이해하기 위해서는 트랜잭션이란 것을 알아야한다.

트랜잭션이란 DB의 상태를 변화시키기 위해 작업의 단위를 뜻한다

SQL 표준에서 각 SQL 쿼리는 하나의 트랜잭션을 시작하게 되어있다. 그리고 이런 표준 트랜잭션은 반드시 명시적으로 commit 되거나 rollback 되어야 한다.

autocommit 이란 데이터 변경작업에 대한 SQL 자체가 바로 반영되는 것을 의미한다. python에서는 기본적으로 오토커밋이 꺼져있는 상태이지만

isolation_level=None을 지정하면 autocommit 모드가 된다.

Cursor

DB랑 connection 을 했으면 cursor로 가져온다.

c = conn.cursor()
print('Cursor Type: ', type(c))
#Cursor Type:  <class 'sqlite3.Cursor'>

테이블 생성(Data Type: TEXT, NUMERIC, INTEGER, REAL, BLOB)

c.execute("CREATE TABLE IF NOT EXISTS users(id INTEGER PRIMARY KEY,\
username text, email text phone text, website text, regdate text)")

cursor의 execute 내에 TABLE을 생성하는 SQL문을 작성한다. users라는 테이블은 id : int 의 기본키 를 가지고 나머지 username, email,phone 등등의 정보를 text필드로 가진 테이블이다.

c.execute("INSERT INTO users VALUES(1, 'kim', 'admin@admin.com', '010-0000-0000',\
    'kim.com', ?)", (nowDatetime,))

이번에는 필드에 데이터를 추가한다.

excute(SQL, a-data) 의 형식으로 사용하는데

SQL에서 ?를 사용하면 두번째 인자인 nowDatetime과 매치된다.

이 때 여러 개의 데이터를 한 번에 대량 적용을 원하면 excutemany()를 사용하면 된다.

c.execute("INSERT INTO users(id, username, email, phone, website, regdate) VALUES (?,?,?,?,?,?)", (2, 'park', 'park@admin.conm', '010-1111-1111', 'park.com', nowDatetime))

정석대로 insert를 하자면 필드명을 먼저 쓰고 ,values를 기입해주면 된다.

Many 삽입 (튜플, 리스트)

userList = (
    (3, 'lee', 'lee@naver.com', '010-1234-1234', 'lee.com', nowDatetime),
     (4, 'cho', 'cho@naver.com', '010-4321-1234', 'cho.com', nowDatetime),
      (5, 'yoo', 'yoo@naver.com', '010-0000-0000', 'yoo.com', nowDatetime),

)

이렇게 한번에 읽어왔거나 json으로 받아온 데이터셋은 executemany를 사용한다.

c.executemany('INSER INTO users(id, username, email, phone, website, regdate) \
    VALUES (?,?,?,?,?,?)', userList)

오타에 유의해야함.

테이블 데이터 삭제

conn.execute('DELETE FROM users')

테이블 삭제는 conn execute의 DELETE 쿼리를 사용하면된다.

print를 사용해서 몇개의 db가 삭제되었는지 확인하는 방법도 있다.

print("users db deleted: ", conn.execute('DELETE FROM users').rowcount)

지금까지는 isolation_level=None 이기 때문에 따로 커밋을 사용하지 않았지만 원래는

conn.commit()

을 통해 커밋을 직접해주거나

conn.rollback()

을 통해 롤백해야한다.

그리고 마지막으로

접속해제

conn.close()

로 접속을 해제한다.

SQL 테이블 조회

import sqlite3
conn = sqlite3.connect('./resource/database.db')

#커서 바인딩
c = conn.cursor()

#데이터 조회(전체)
c.execute("SELECT * FROM users")

이 예제를 실행해도 아무일도 일어나지 않는다. 현재 커서 위치를 확인해보자

# 1개 로우 선택
print('1 ->\n', c.fetchone())

#지정 로우 선택
print("3 -> \n", c.fetchmany(size=3))

#전체 로우 선택
print('All -> \n', c.fetchall())

fetchone은 하나의 row를 출력하고 fetchmany를 사용하면 지정 사이즈의 row를 출력해준다 fetchall()은모두 출력

fetch를 사용해서 cursor가 이동하면 그 다음 cursor의 내용이 출력되는 것이다.

import sqlite3
conn = sqlite3.connect('./resource/database.db')

#커서 바인딩
c = conn.cursor()

#데이터 조회(전체)
c.execute("SELECT * FROM user`s")

rows = c.fetchall()
print(rows)
for row in rows:
    print('retrieve1 > ', row)

row를 순회해보자.

fetchall로 모두 받아와서 print 해줄 수도 있다.

for row in c.fetchall():
    print('retrieve1 > ', row)

이렇게 fetchall을 바로 for문에 사용하는게 더 간편하다.

for row in c.execute("SELECT * FROM users ORDER BY id desc"):
    print('retrieve2 > ', row)

execute로 바로 실행해서 출력할 수도 있다. execute로 실행하고 fetch로 가져오는 과정을 생략하고 바로 쿼리를 실행할 수도 있다.

WHERE 검색

param1= (3,)
c.execute("SELECT * FROM users WHERE id=?", param1)
print('param1', c.fetchone())
print('param1', c.fetchall())

처음 param1의 3을 검색해서 출력하고 커서가 이동하는데 id 3은 하나밖에 없기 때문에 빈 값이 출력된다.

param2= 4
c.execute("SELECT * FROM users WHERE id='%s'" % param2)
print('param2', c.fetchone())
print('param2', c.fetchall())

?와 튜플을 사용한 형태가아닌 그냥 int값을 사용하고 싶다면

%s를 사용해서 int값을 바인딩할 수 있다.

c.execute("SELECT * FROM users WHERE id=:Id" , {"Id":5})
print('param2', c.fetchone())
print('param2', c.fetchall())

딕셔너리를 사용해서도 가능하다. %s 대신 키 값을 놔주고 두번째 인수로 딕셔너리를 위치시켜준다.

param4=(3,5)
c.execute("SELECT * FROM users WHERE id IN(?,?)", param4)
print(c.fetchall())

이번에는 IN 함수 (합집합) 을 사용해서 2개의 row를 가져올 수도 있다.

c.execute("SELECT * FROM users WHERE id IN('%d','%d')" % (3,4))
print(c.fetchall())

이렇게도 사용가능함.

c.execute("SELECT * FROM users WHERE id=:id1 OR id=:id2" ,{'id1':2, 'id2':5})
print(c.fetchall())

일단 SQL문 부터 알아봐야할 듯 ㅡㅡ

dump 출력

with conn:
    with open('./resource/dump.sql', 'w') as f:
        for line in conn.iterdump():
            f.write('%s\n' % line)
        print('dump print complete')

dump는 DB를 백업하기 위한 방법으로 어떤 DB의 형태를 그대로 생성할 수 있게 해주는 dump파일을 생성한다.

해당 dump파일을 열어보면

BEGIN TRANSACTION;
CREATE TABLE users(id INTEGER PRIMARY KEY,username text, email text, phone text, website text, regdate text);
INSERT INTO "users" VALUES(3,'lee','lee@naver.com','010-1234-1234','lee.com','2021-06-17 21:34:25');
INSERT INTO "users" VALUES(4,'cho','cho@naver.com','010-4321-1234','cho.com','2021-06-17 21:34:25');
INSERT INTO "users" VALUES(5,'yoo','yoo@naver.com','010-0000-0000','yoo.com','2021-06-17 21:34:25');
COMMIT;

이렇게 테이블 생성에 사용되는 쿼리들이 있다.

Table data 수정 및 삭제

테이블의 데이터를 수정할 때는 고유한 값을 통해 바꾸는 것이 좋다.

import sqlite3
conn = sqlite3.connect('./resource/database.db')

c = conn.cursor()

c.execute("UPDATE users SET username=? WHERE id=?", ("niceman",2))
conn.commit()

update 해라 user의 username을 niceman 으로 id가 2인것을

그리고 커밋해주면 값이 변경된다.


# c.execute("UPDATE users SET username=:name WHERE id=:id", {"name": 'goodman', 'id':5})

# c.execute("UPDATE users SET username='%s' WHERE id='%s'" % ("badboy", 1))

다른 방법으로도 변경가능하다.

c.execute("DELETE FROM users WHERE id=?", (2,))

c.execute("DELETE FROM users WHERE id=:id", {'id':5})

c.execute("DELETE FROM users WHERE id='%s'" % 4)

conn.commit()

삭제는 DELETE를 사용해서 한다.

전체 데이터 삭제는는 이전에 했던것처럼.

print(conn.execute("DELETE FROM users").rowcount,"삭제됨")

conn.commit()
conn.close()

그리고 커밋을 해주고 연결을 해제까지 해준다.

댓글