method & error

JoongHyun's Blog·2023년 11월 21일
0

추상 클래스

파이썬은 추상 클래스(abstract class)라는 기능을 제공합니다. 추상 클래스는 메서드의 목록만 가진 클래스이며 상속받는 클래스에서 메서드 구현을 강제하기 위해 사용합니다.

먼저 추상 클래스를 만들려면 import로 abc 모듈을 가져와야 합니다( abc는 abstract base class의 약자입니다). 그리고 클래스의 ( )(괄호) 안에 metaclass=ABCMeta를 지정하고, 메서드를 만들 때 위에 @abstractmethod를 붙여서 추상 메서드로 지정합니다.

from abc import *
 
class StudentBase(metaclass=ABCMeta):
    @abstractmethod
    def study(self):
        pass
 
    @abstractmethod
    def go_to_school(self):
        pass
 
class Student(StudentBase):
    def study(self):
        print('공부하기')
 
james = Student()
james.study()

이렇게 하면 에러가 뜹니다.
왜냐하면 추상 클래스 StudentBase에서는 추상 메서드로 study와 go_to_school을 정의했습니다. 하지만 StudentBase를 상속받은 Student에서는 study 메서드만 구현하고, go_to_school 메서드는 구현하지 않았으므로 에러가 발생합니다.

따라서 추상 클래스를 상속받았다면 @abstractmethod가 붙은 추상 메서드를 모두 구현해야 합니다. 다음과 같이 Student에서 go_to_school 메서드도 구현해줍니다.

from abc import *
 
class StudentBase(metaclass=ABCMeta):
    @abstractmethod
    def study(self):
        pass
 
    @abstractmethod
    def go_to_school(self):
        pass
 
class Student(StudentBase):
    def study(self):
        print('공부하기')
    def go_to_school(self):
        print('학교가기')
        return super().go_to_school()
 
james = Student()
james.study()
x = james.go_to_school()
print(x)

### output
공부하기
학교가기
None

추상 메서드를 빈 메서드로 만드는 이유

그리고 또 한 가지 중요한 점이 있는데 추상 클래스는 인스턴스로 만들 수가 없다는 점입니다. 다음과 같이 추상 클래스 StudentBase로 인스턴스를 만들면 에러가 발생합니다.

그래서 지금까지 추상 메서드를 만들 때 pass만 넣어서 빈 메서드로 만든 것입니다. 왜냐하면 추상 클래스는 인스턴스를 만들 수 없으니 추상 메서드도 호출할 일이 없기 때문입니다.

    @abstractmethod
    def study(self):
        pass    # 추상 메서드는 호출할 일이 없으므로 빈 메서드로 만듦
 
    @abstractmethod
    def go_to_school(self):
        pass    # 추상 메서드는 호출할 일이 없으므로 빈 메서드로 만듦

추상 클래스는 인스턴스로 만들 때는 사용하지 않으며 오로지 상속에만 사용합니다. 그리고 파생 클래스에서 반드시 구현해야 할 메서드를 정해 줄 때 사용합니다.

이번에는 인스턴스를 통하지 않고 클래스에서 바로 호출할 수 있는 정적 메서드와 클래스 메서드에 대해 알아보겠습니다.

정적 메서드

@staticmethod

매개변수에 self를 지정하지 않는다.

class Name:
	@staticmethod
    def method1(param1,param2):
    	pass

@staticmethod처럼 앞에 @이 붙은 것을 데코레이터라고 하며 메서드(함수)에 추가 기능을 구현할때 사용한다.

보통 정적 메서드는 인스턴스 속성, 인스턴스 메서드가 필요없는 경우 사용한다.즉 정적 메서드는 메서드의 실행이 외부 상태에 영향을 끼치지 않는 pure 함수를 만들때 사용한다.

순수 함수는 부수 효과(side effect)가 없고 입력 값이 같으면 언제나 같은 출력 값을 반환합니다. 즉, 정적 메서드는 인스턴스의 상태를 변화시키지 않는 메서드를 만들 때 사용

>>> a = {1, 2, 3, 4}
>>> a.update({5})    # 인스턴스 메서드
>>> a
{1, 2, 3, 4, 5}
>>> set.union({1, 2, 3, 4}, {5})    # 정적(클래스) 메서드
{1, 2, 3, 4, 5}

classmethod

class Person:
    count = 0    # 클래스 속성
 
    def __init__(self):
        Person.count += 1    # 인스턴스가 만들어질 때
                             # 클래스 속성 count에 1을 더함
 
    @classmethod
    def print_count(cls): # 이 함수의 주소
        print('cls:',id(cls)) # cls의 주소 계속 공통으로 유지.
        print('{0}명 생성되었습니다.'.format(cls.count))    # cls로 클래스 속성에 접근
 
james = Person()
maria = Person()
 
#클래스에 직접 연결

print('james id.func',id(james.print_count()))
print('maria id.func',id(maria.print_count()))
print(id(james)) # 둘이 다름
print(id(maria)) # 둘이 다름

Person.print_count()    # 2명 생성되었습니다. # 이렇게 해도 유지.
print(id(Person.print_count()))
print(id(Person())) # 이것도 다름

이제 @classmethod를 붙여서 클래스 메서드를 만듭니다. 클래스 메서드는 첫 번째 매개변수가 cls인데 여기에는 현재 클래스가 들어옵니다. 따라서 cls.count처럼 cls로 클래스 속성 count에 접근할 수 있습니다.
위 결과 새로운 인스턴스를 아무리 만들어도 Person.print_count() 주소값은 불변이다.
다만 인스턴스를 새로 만들때마다, Person.count값은 업데이트되며 주소값도 바뀐다.


try & except

y,x = 3,0
try:
    z = y/ x
except ZeroDivisionError as e:    # 숫자를 0으로 나눠서 에러가 발생했을 때 실행됨
    print('숫자를 0으로 나눌 수 없습니다.',e)
except IndexError as e:           # 범위를 벗어난 인덱스에 접근하여 에러가 발생했을 때 실행됨
    print('잘못된 인덱스입니다.',e) # e가 어떤 에러인지 표시
else:                        # try의 코드에서 예외가 발생하지 않았을 때 실행됨
    print(z)
------------------------------------------------------------
# 정리 
try:
    실행할 코드
except:
    예외가 발생했을 때 처리하는 코드
else:
    예외가 발생하지 않았을 때 실행할 코드
finally:
    예외 발생 여부와 상관없이 항상 실행할 코드

raise

예외를 발생시킬 때는 raise에 예외를 지정하고 에러 메시지를 넣습니다(에러 메시지는 생략할 수 있음).

def three_multiple():
    x = int(input('3의 배수를 입력하세요: '))
    if x % 3 != 0:                                 # x가 3의 배수가 아니면
        raise Exception('3의 배수가 아닙니다.')    # 예외를 발생시킴
    print(x)                                       # 현재 함수 안에는 except가 없으므로
                                                   # 예외를 상위 코드 블록으로 넘김
 
try:
    three_multiple()
except Exception as e:                             # 하위 코드 블록에서 예외가 발생해도 실행됨
    print('예외가 발생했습니다.', e)

three_multiple 함수는 안에 try except가 없는 상태에서 raise로 예외를 발생시켰습니다. 이렇게 되면 함수 바깥에 있는 except에서 예외가 처리됩니다. 즉, 예외가 발생하더라도 현재 코드 블록에서 처리해줄 except가 없다면 except가 나올 때까지 계속 상위 코드 블록으로 올라갑니다.

만약 함수 바깥에도 처리해줄 except가 없다면 코드 실행은 중지되고 에러가 표시됩니다. 다음은 파이썬 셸에서 직접 three_multiple 함수를 호출했으므로 except가 없는 상태입니다.

따라서 함수안에 except를 처리해주는것이 좋습니다.

def three_multiple():
    try:
        x = int(input('3의 배수를 입력하세요: '))
        if x % 3 != 0:                                 # x가 3의 배수가 아니면
            raise Exception('3의 배수가 아닙니다.')    # 예외를 발생시킴
        print(x)
    except Exception as e:                             # 함수 안에서 예외를 처리함
        print('three_multiple 함수에서 예외가 발생했습니다.', e)
        raise    # raise로 현재 예외를 다시 발생시켜서 상위 코드 블록으로 넘김
 
try:
    three_multiple()
except Exception as e:                                 # 하위 코드 블록에서 예외가 발생해도 실행됨
    print('스크립트 파일에서 예외가 발생했습니다.', e)

assert로 예외 발생시키기

assert는 지정된 조건식이 거짓일 때 AssertionError 예외를 발생시키며 조건식이 참이면 그냥 넘어갑니다. 보통 assert는 나와서는 안 되는 조건을 검사할 때 사용합니다.

x = int(input('3의 배수를 입력하세요: '))
assert x % 3 == 0, '3의 배수가 아닙니다.'    # 3의 배수가 아니면 예외 발생, 3의 배수이면 그냥 넘어감
print(x)

assert는 디버깅 모드에서만 실행됩니다. 특히 파이썬은 기본적으로 디버깅 모드이며(debug의 값이 True) assert가 실행되지 않게 하려면 python에 -O 옵션을 붙여서 실행합니다(영문 대문자 O).

profile
AI와 수학, 일상 기록들

0개의 댓글