본문 바로가기
SK Shieldus Rookies 19th/인프라 활용을 위한 파이썬

[SK shieldus Rookies 19기][Python] - 클래스, 모듈, 패키지, 오류 처리

by En_Geon 2024. 3. 18.

1. 클래스

똑같은 무언가를 계속 만들어 낼 수 있는 설계도면(과자 틀)

 

 

result = 0

def add(num):
    global result
    result += num  # 결괏값(result)에 입력값(num) 더하기
    return result  # 결괏값 리턴

print(add(3))
print(add(4))

 

  • 이전에 계산한 결괏값을 유지하기 위해서 result 전역 변수를 사용

 

한 프로그램에서 2대의 계산기가 필요한 상황이면 add 함수 하나로는 결괏값을 따로 유지할 수 없다.

 

result1 = 0
result2 = 0

def add1(num):  # 계산기1
    global result1
    result1 += num
    return result1

def add2(num):  # 계산기2
    global result2
    result2 += num
    return result2

print(add1(3))
print(add1(4))
print(add2(3))
print(add2(7))

 

  • 계산기 1의 결괏값이 계산기 2에 아무런 영향을 끼치지 않는다.

 

계산기가 더 많아지면 전역 변수와 함수를 계속 추가해야 한다. 이럴 때 클래스를 사용하면 간단하게 해결할 수 있다.

 

class Calculator:
    def __init__(self):
        self.result = 0

    def add(self, num):
        self.result += num
        return self.result

cal1 = Calculator()
cal2 = Calculator()

print(cal1.add(3))
print(cal1.add(4))
print(cal2.add(3))
print(cal2.add(7))

 

  • 계산기의 결괏값이 다른 계산기의 결괏값과 상관없이 독립적인 값을 유지
  • 계산기 대수가 늘어나도 객체를 생성하면 되므로 함수만 사용할 때보다 간단하게 작성할 수 있다.

 

1) 객체

클래스로 만든 피조물(과자 틀로 찍어 낸 과자)

 

클래스로 만든 객체에는 중요한 특징이 있다. 객체마다 고유한 성격을 가진다.

 

과자 틀로 만든 과자에 구멍을 뚫거나 조금 베어 먹더라도 다른 과자에는 아무런 영향이 없는 것과 마찬가지로 동일한 클래스로 만든 객체들은 서로 전혀 영향을 주지 않는다.

 

 

2) 사칙 연산 클래스 만들기

(1) 클래스 구조 만들기

 

class Cal :
    pass

 

(2) 객체의 연산할 숫자 지정

 

계산기 기능을 하슨 객체를 만들어야 하는데 이러한 객체를 만들려면 먼저 사칙 연산을 할 때 사용할 2개의 숫자를 객체에게 알려 줘야 한다.

 

class Calculator:           
    def setdata(self, first, second):	# 메서드의 매개변수
        self.first = first		# 메서드 수행문
        self.second = second

 

  • 클래스 안에 구현된 함수를 메서드(method)라고 한다.
  • 메서드는 일반 함수를 만들 때와 같지만 클래스에 포함되어 있다는 점만 다르다.

 

setdata 메서드는 매개변수로 self, first, second 3개의 입력값을 받는다. 객체를 이용해 클래스의 메서드를 호출하려면 "a.setdat(1, 2)"로 호출한다. 

 

첫 번째 매개변수 self는 특별한 의미를 가진다.

setdata 메서드의 매개변수는 3개인데 2개의 값만 전달하는 것처럼 보인다. 이때 첫 번째 매개변수 self에는 setada 메서드를 호출한 객체 a가 자동으로 전달되기 때문이다.

 

파이썬 메서드의 첫 번째 매개변수 이름은 self로 사용한다. 객체를 호출할 때 호출한 객체 자신이 전달되기 때문이다. 

 

수행문은 메서드의 매개변수 first, second에는 각각 값 1, 2가 전달되어  self.first = 1, self.second = 2가 되고, self는 a이므로 a.first = 1, a.second = 2가 된다.

 

위 문장이 수행되면 a 객체의 객체변수 first가 생성되고 1 값이 저장된다.

 

class Calculator :    
    def __init__(self) :            # 변수 초기화 작업
        self.first = 0
        self.second = 0

    def setdata(self, first, second) :    
        self.first = first
        self.second = second

    def add(self) :
        return self.first + self.second
    
    def sub(self) :
        return self.first - self.second
    
    def mul(self) :
        return self.first * self.second
    
    def div(self) :
        if self.first == 0 or self.second == 0 :  # 나누는 수(제수)가 0인 경우 0을 반환하도록 수정
            return 0
        return self.first / self.second
    

cal1 = Calculator()     # 인스턴스가 생성될 때 클래스에서 정의한 생성자가 제일 먼저 호출, 실행됨

result = cal1.add()     # 생성자가 없으면 오류발생

cal1.setdata(10,20)

result = cal1.add()
print(result)           # 30

result = cal1.sub()
print(result)           # -10

result = cal1.mul()
print(result)           # 200

cal1.setdata(100, 0)    # 위 클래스의 div를 수정하면 0을 리턴한다

result = cal1.div()
print(result)           # 클래스를 수정을 했기 때문에 ZeroDivisionError 오류발생 하지 않음

 

계산기의 메서드를 만들면 위 코드와 같이 된다.

 

나누기에서는 나누는 수가 0이면 오류가 나는데 조건문을 통해 나누는 수가 0이면 값을 0을 리턴하는 것으로 수정했다.

나눠지는 수 first에 0이 들어가면 값은 0.0으로 float 형으로 나오기 때문에 first도 0을 리턴하기 위해서 수정했다.

 

 

3) 생성자

객체가 생성될 때 자동으로 호출되는 메서드를 의미

 

객체에 first, second와 같은 초깃값을 설정해야 할 필요가 있을 때 초깃값을 설정하기보다 생성자를 구현하는 것이 안전한 방법이다.

 

메서드 이름을 __init__로 했기 때문에 생성자로 인식되어 객체가 생성되는 시점에 자동으로 호출된다.

 

객체를 생성할 때 매개변수 first, second에 해당하는 값이 전달되지 않으면 오류가 발생하기 때문에 매개변수를 전달해야 한다.

 

 

4) 상속

'물려받다'는 뜻으로 '재산을 상속받다'라고 할 때의 상속과 같은 의미 

 

  • 클래스를 상속하기 위해서는 클래스 이름 뒤 괄호 안에 상속할 클래스 이름을 넣는다.
  • class 클래스_이름(상속할_클래스_이름)
  • 상속은 기존 클래스를 변경하지 않고 기능을 추가하거나 기존 기능을 변경할 때 사용

 

(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()

c = Clock()

while True:
    c.add_one_second()
    c.print()
    
    time.sleep(1)

 

  • 클래스를 활용해 시계 클래스를 만들었다.

 

(2) 시계 클래스 상속

 

import time

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

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

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

    def add_one_minute(self):
        self.minute += 1
        if self.minute >= 60:
            self.minute = 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) :		# 메서드 오버라이딩 부모 클래스의 메서드를 자식 클래스에서 재정의
        print(f'{self.hour}시 {self.minute}분')

c = MinuteClock()

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

 

  • 시계 클래스에서 상속을 통해 MiunteClock(Clock) 클래스를 만들었다.

 

5) 메서드 오버라이딩

부모 클래스(상속한 클래스)에 있는 메서드를 동일한 이름으로 다시 만드는 것

 

  • 메서드 오버라이딩하면 부모 클래스의 메서드 대신 오버라이딩한 메서드가 호출된다.

 

6) 클래스 변수

"클래스_이름.클래스변수"로 사용

 

 

 

2. 모듈

함수나 변수 또는 클래스를 모아 놓은 파이썬 파일

 

 

1) 모듈 불러오기

 

  • "import 모듈_이름"으로 사용

 

지금까지 많이 사용해 봤던 것이다. 그런데 모듈 이름 없이 함수 이름만 쓰고 싶은 경우도 있다.

 

  • "from 모듈_이름 import 모듈_함수"

 

위와 같이 함수를 직접 import 하면 모듈 이름을 붙이지 않고 바로 해당 모듈의 함수를 쓸 수 있다.

하지만 모듈에 있는 함수를 사용하지 못하고 직접 import 한 함수만 사용할 수 있다.

 

다른 함수도 쓰고 싶을 때 다른 함수를 부르는 방법이 2개 있다.

 

  • "from 모듈_이름 import 모듈_함수1, 모듈_함수2"
  • "from 모듈_이름 import *"

 

쉼표로 구분하여 필요한 함수를 더 불러올 수 있고, *문자를 사용하면 된다. *는 모든 것이라는 뜻으로 모듈의 모든 함수를 불러와 사용하겠다는 뜻이 된다.

 

다른 파일일에서 모듈을 불러오는 것은 지금까지 했던 방법으로 불러오는 것이다. 이 방법은 파일이 동일한 디렉터리에 있어야한다.

 

다른 디렉터리에 있는 모듈을 불러오는 방법도 있다.

 

  • sys.path에 원하는 디렉터리를 추가하면 디렉터리 이동 없이 모듈을 불러와서 사용할 수 있다.
  • PC의 환경변수 이용

 

환경변수에 원하는 디렉터리에 추가하면 모듈을 불러올 수 있다.

 

 

2) if __name__ == "__main__" : 의미

 

def add(a, b): 
    return a+b

def sub(a, b): 
    return a-b

print(add(1, 4))
print(sub(4, 2))

 

 

이 코드를 실행하면 잘 출력된다. 그런데 함수를 사용하기 위해 import 할 때는 이상한 문제가 생긴다.

import 하는 순간 test.py 파일이 실행되면서 결괏값을 출력한다.

 

def add(a, b): 
    return a+b

def sub(a, b): 
    return a-b

if __name__ == "__main__":
    print(add(1, 4))
    print(sub(4, 2))

 

 

위 코드와 같이 if __name__ == "__main__" : 을 사용해 직접 test.py 파일을 실행했을 때는 __name__ = "__main__"이 참이 되어 if 문 다음 문장이 수행된다.

 

인터프리터나 다른 파일에서 이 모듈을 불러 사용할 때는 거짓이 되어 if 문 다음 문장이 수행되지 않는다.

 

(1) __name__ 변수

파이썬이 내부적으로 사용하는 특별한 변수

 

직접 test.py를 실행하면 __name__ 변수에 __main__ 값이 저장되고, 모듈에서  test를 import할 때는 __name__ 변수에 모듈 이름인 test가 저장된다.

 

 

3. 패키지

관련 있는 모듈의 집합

 

  • 패키지는 파이썬 모듈을 계층적(디렉터리 구조)으로 관리할 수 있게 해 준다.
  • 패키지는 디렉터리와 파이썬 모듈로 이루어진다.
  • 공동 작업이나 유지 보수 등 여러 면에서 유리

 

가상 game 패키지 예

 

 

1) 패키지 안의 함수 실행

 

(1) import

 

import game.sound.echo
game.sound.echo.echo_test()

 

 

(2) from import

 

from game.sound import echo
echo.echo_test()

 

 

(3) 함수 직접 import

 

from game.sound.echo import echo_test
echo_test()

 

 

(4) 불가능

 

import game
game.sound.echo.echo_test()

 

  • 위 코드와 같이 echo_test 함수를 사용할 수 없어 오류가 난다.
  • import game을 수행하면 game 디렉터리의 __init__.py에 정의한 것만 참조할 수 있다.
  • 만약 game 디렉터리의 __init__.py에 패키지 내의 모듈을 미리 import 했다면 위와 같이 사용할 수 있다.
  • import 할 때 가장 마지막 항목은 반드시 모듈 또는 패키지여야만 한다.

 

2) __init___.py의 용도

해당 디렉터리가 패키지의 일부임을 알려주는 역할

 

  • 만약 패키지에 포함된 디렉터리에 __init__.py 파일이 없다면 패키지로 인식되지 않는다.
  • 패키지와 관련된 설정이나 초기화 코드를 포함할 수 있다.

 

4. 예외 처리

오류로 인한 비정상 종료를 막는 것

 

 

1) 오류가 발생하지 않도록 입력값을 검증 후 사용

 

num = int(input("숫자를 입력하세요."))

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

 

  • 0 이면 검증하고 작성자가 원하는 방식으로 프로그램을 종료한다.

 

2) try-except

오류가 발생했을 때 적절한 조치를 수행하는 코드 추가

 

try :
    수행할_구문
except [발생_오류 [as 오류_변수]]
    수행할_구문

 

  • 오류를 처리하기 위한 try-except 문의 기본 구조
  • try 블록 수행 중 오류가 발생하면 except 블록 수행
  • try 블록에서 오류가 발생하지 않으면 except 블록은 수행되지 않음

 

3) except

 

(1) try-except만 쓰는 방법

 

try :
   ...
except:
   ...

 

  • 오류의 종류에 상관없이 오류가 발생하면 excpet 블록 수행

 

(2) 발생 오류만 포함한 except 문

 

try :
    ....
except 발생_오류 :
    ....

 

  • except 문에 미리 정해 놓은 오류와 동일한 오류일 경우에만 except 블록 수행

 

(3) 발생 오류와 오류 변수까지 포함한 except 문

 

try :
    ....
except 발생_오류 as 오류_변수 :
    ....

 

  • 두 번째 경우에서 오류의 내용까지 알고 싶을 때 사용

 

(4) 예제

 

try :
    4 / 0
except ZeroDivisionError as e :
    print(e)

 

 

4) try-finally

finally절은 try 문 수행 도중 예외 발생 여부에 상관없이 항상 수행

 

  • finally 절은 사용한 리소스를 close 해야 할 때 많이 사용한다.
  • 파일 시스템을 사용하다 예외 발생 여부에 상관없이 상상 파일을 닫아 주려면 try-finally 문을 사용

 

try :
    f = open('new.txt', 'w')
    # 수행할 문장
finally :
    f.close() 		# 중간에 오류가 발생하더라도 무조건 실행

 

 

5) 오류 일부러 발생

 

class Bird:
    def fly(self):
        raise NotImplemented
   
class Eagle(Bird):
    pass

e = Eagle()
e.fly()

 

  • Eagle 클래스는 Bird 클래스를 상속
  • Eagle 클래스는 fly 메서드를 오버라이딩하여 구현하지 않았다.
  • eagle 객체의 fly 메서드를 수행하는 순간 Bird 클래스의 fly 메서드가 수행되어 NotImplementedError가 발생

 

class Bird:
    def fly(self):
        raise NotImplemented
   
class Eagle(Bird):
    def fly(self) :
        print("very fast")

e = Eagle()
e.fly()

 

  • 오류 수정

 

6) 사용자 정의 예외

 

try:
    no = int(input("숫자를 입력하세요."))
    if no == 0:
        raise MyInputError(no)
   
    print(f"10 / {no} = {10 / no}")


except MyInputError as e:
    print(e)

 

  • 10을 0으로 나누려고 할 때 오류 발생

 

class MyInputError(Exception):			
    def __init__(self, no) -> None:		
        self.no = no					

    def __str__(self) -> str:			# 예외 발생 시 반환할 문자열
        return f"{self.no}는 잘못된 입력입니다."

 

  • 사용자 정의 예외는 Exception 클래스를 상속받아 정의
  • 예외 발생 시 전달받아야 하는 값이 있는 경우 생성자를 활용

댓글