파이썬은 클래스를 정의할 때 클래스를 상속하고 클래스를 생성하는 메타 클래스를 지정한다.
추상 클래스와 추상 메타 클래스도 일반적인 클래스와 메타 클래스처럼 생성 관계로 처리된다.
import abc
for i in dir(abc):
if not i.startswith('_'): # 모듈 내에 있는 클래스와 함수 확인
print(i)
# startswith(시작하는문자, 시작지점), 문자열이 특정 문자로 시작하는지 알려준다.
# 추상 클래스와 추상 메타 클래스는 상속 관계는 성립하지 않지만, 생성 관계는 성립한다.
issubclass(abc.ABC, abc.ABCMeta) # False
isinstance(abc.ABC, abc.ABCMeta) # True
사용자 정의 추상 클래스를 정의할 때는 매개변수 metaclass에 ABCMeta 클래스를 지정한다.
class MyABC(metaclass=abc.ABCMeta):
@abc.abstractmethod # 반드시 구현할 메소드를 데코레이터로 지정
def foo(self, ):
pass
class MyCon(MyABC): # 추상 클래스를 상속하는 구현 클래스 정의
pass
m = MyCon() # 추상 클래스에 정의한 함수를 구현하지 않았기 때문에 예외가 발생한다.
구현 클래스에 메소드를 구현하면 예외가 발생하지 않는다.
class MyCon_(MyABC):
def foo(self): # 추상 메소드에 해당하는 함수를 구현
print('foo')
m = MyCon_()
m.foo() # foo
# 구현 클래스와 추상 클래스는 상속 관계가 성립한다.
issubclass(MyCon, MyABC) # True
# 구현 클래스의 객체와 추상 클래스는 생성 관계가 성립한다.
isinstance(m, MyABC) # True
추상 메소드 처리
from abc import *
class BaseCS(metaclass=ABCMeta):
@abstractmethod
def foo(self):
pass
@abstractmethod
def bar(self):
pass
@classmethod # 2개의 데코레이터를 연속 사용해서 추상 메소드에 클래스 메소드 정의
@abstractmethod
def clsmethod(cls):
pass
@staticmethod # 2개의 데코레이터를 연속 사용해서 추상 메소드에 정적 메소드 정의
@abstractmethod
def stamethod(a):
pass
@property # 2개의 데코레이터를 연속 사용해서 추상 메소드에 프로퍼티 메소드 정의
@abstractmethod
def propmethod(self):
pass
추상 클래스를 상속해서 클래스를 정의할 때 2개의 메소드 재정의
class Concrete_CS(BaseCS): # 현 클래스를 정의할 때, 2개의 추상 메소드만 구현
def foo(self):
pass
def bar(self):
pass
c = Concrete_CS() # 예외 발생
추상 클래스는 작성된 모든 추상 메소드, 추상 프로퍼티를 모두 구현해야 한다.
class Concrete_CS1(BaseCS): # 모든 추상 메소드 구현
def foo(self):
pass
def bar(self):
pass
@classmethod
def clsmethod(cls):
pass
@staticmethod
def stamethod(a):
pass
@property
def propmethod(self):
pass
cs1 = Concrete_CS1()
# 구현 클래스와 추상 클래스 사이에는 상속 관계가 성립한다.
issubclass(Concrete_CS1, BaseCS) # True
# 객체와 추상 클래스 간에 생성 관계가 성립한다.
isinstance(cs1, BaseCS) # True
추상 클래스를 상속하지 않아도 상속 관계가 성립한느 방식을 지원한다.
import abc
class MyABC(abc.ABC):
pass
# 추상 클래스에 클래스를 register 함수로 등록하면 이 추상 클래스를 상속한 클래스로 인식한다.
# 이렇게 되면 추상 클래스와 튜플 클래스의 상속 관계가 성립하고, 튜플 객체와의 생성 관계도 성립한다.
MyABC.register(tuple)
issubclass(tuple, MyABC), isinstance(tuple(), MyABC) # (True, True)
추상 메타 클래스를 지정한 추상 클래스 정의
class LoadSave(metaclass=abc.ABCMeta):
@abc.abstractmethod
def load(self, input):
'''입력 소스에서 데이터를 검색해서 객체를 반환한다.'''
@abc.abstractmethod
def save(self, output, data):
'''데이터 객체를 저장한다.'''
# 위의 추상 클래스와 같은 메소드를 가진 클래스를 정의하고 데코레이터로 클래스 등록
@LoadSave.register
class RegImp:
def load(self, input):
pass
def save(self, output, data):
pass
# 추상 클래스와 구현 클래스 사이에 상속 관계가 성립하고, 객체와 추상 클래스의 생성 관계도 성립한다.
issubclass(RegImp, LoadSave) # True
isinstance(RegImp(), LoadSave) # True
상속 관계를 처리하는 스페셜 메소드 __subclasshook__를 수정한다.
이 스페셜 메소드는 classmethod로 구현해야 한다.
class ABCD(abc.ABC):
@abc.abstractmethod
def __len__(self):
return NotImplemented
@classmethod # 추상 클래스의 상속 관계를 체크하는 __subclasshook__ 메소드를 오버라이딩 처리
def __subclasshook__(cls, C):
print('__subclasshook__')
if any('__len__' in B.__dict__ for B in C.__mro__): # 상속한 클래스의 mro를 확인해서 __len__이 있으면 상속 관계를 추가
return True
else:
return False
추상 클래스를 상속한 구현 클래스를 작성하고 스페셜 메소드 __len__을 작성한다.
class Seq(ABCD):
def __init__(self, seqs):
self.seqs = seqs
def __len__(self):
print('Seq __len__')
return str.__len__(self.seqs)
s = Seq('string')
# 상속 관계를 확인하면 추상 클래스의 스페셜 메소드 __subclasshook__ 를 호출
issubclass(Seq, ABCD) # __subclasshook__와 True가 출력된다.
# 생성 관계를 확인할 때는 __subclasshook__가 호출되지 않는다.
isinstance(s, ABCD) # True
# 내장 함수 len을 실행하면 구현 클래스 내의 __len__이 실행된다.
len(s) # Seq __len__과 6이 출력된다.
생성 관계를 처리하는 스페셜 메소드 __instancecheck__를 재정의한다.
class Enumeration(abc.ABCMeta):
def __instancecheck__(self, other):
print('hi')
if type(other) == type(self):
return True
else:
return False
class EnumInt(metaclass=Enumeration):
pass
# 생성 관계를 확인하는 isinstance 함수를 실행하면 내부의 스페셜 메소드 __instancecheck__가 호출된다.
isinstance('abc', EnumInt) # hi와 False가 출력된다. 메타 클래스의 스페셜 메소드가 호출된다.
# 추상 클래스로 객체를 생성하고 객체와 클래스의 관계를 isinstance 함수로 확인하면 메타 클래스의 스페셜 메소드가 호출되지 않는다.
c = EnumInt()
isinstance(c, EnumInt) # True
# 추상 메타 클래스의 스페셜 메소드에 객체를 전달해서 처리하면 두 객체를 비교한 결과가 처리된다.
Enumeration.__instancecheck__(c, c) # hi와 True 출력, 클래스로 정의할 때는 메타 클래스의 생성 관계 메소드를 호출한다.
에러나 예외를 처리하는 최상위 클래스는 Exception이다.
Exception # 최상위 예외 클래스
isinstance(Exception, type) # True, 예외 클래스도 메타 클래스로 생성
# 예외 클래스로 객체를 만들고 속성을 확인하면 예외 메시지가 들어가 있는 것을 볼 수 있다.
e = Exception('Exception!')
e.args # ('Exception!',)
TypeError 클래스
TypeError # TypeError, 클래스가 잘못되었을 때 이 예외가 발생한다.
issubclass(TypeError, Exception) # True, 예외 최상위 클래스 상속
TypeError.__bases__ # (Exception,), __bases__는 상속 관계를 확인하는 속성
예외 처리
# 예외 처리 안했을 때
# 실수를 넣으면 정수로 변환되지ㅣ 않아 예외가 발생한다.
while True:
x = int(input('Give me integer: '))
if isinstance(x, int):
print('input :', x)
# 예외 처리
while True:
try: # 예외가 발생해도 추가적인 처리를 하려면 try 구문에 예외 발생 문장을 넣는다.
x = int(input('Give me integer: '))
break
except ValueError: # 예외를 잡을 때는 except 구문에 발생할 예외 클래스 정의
print('Not Integer') # 예외가 발생하면 예외 클래스에 따라 except 구문 실행
ZeroDivisionError
def this_fails():
x = 1/0
try:
this_fails()
except ZeroDivisionError as err:
print('Error : ', err)
특별한 경우에 예외를 강제로 발생시킬 수 있다. 이때는 raise문이 예외 객체를 발생시키고, 예외를 잡으면 그 내부의 속성을 출력할 수 있다.
class InputError(Exception):
'''
Exception raised for errors in the input.
'''
def __init__(self, expression, module):
self.expression = expression
self.module = module
try:
raise InputError('Exception!', __name__)
except InputError as e:
print(e.expression)
print(e.module)
finally:
print('반드시 처리')
''' 출력
Exception!
__main__
반드시 처리
'''
예외가 발생했는지 점검이 필요한 경우는 else문을 추가한다. 예외가 없을 때 else문에 있는 기능을 처리한다.
try:
print('Normal')
except InputError as e:
print(e.expression)
print(e.module)
else:
print('No Exception')
finally:
print('반드시 처리')
'''출력
Normal
No Exception
반드시 처리
'''
경고는 예외가 아니고, 특정 함수나 클래스를 사용할 때 조건의 버전이 다르거나 기능을 더 이상 사용하지 않을 경우에 주의 메시지를 보내는 것이다.
import warnings
print('before warning)
warnings.warn('this is stopped')
print('after warning')
# before warning과 after warning이 출력되고 경고 메시지 this is stopped가 출력된다.
함수에 넣으면 함수를 사용하는 사람에게 경고 메시지를 전달할 수 있다.
def add(x, y):
warnings.warn('Function Warning')
return x + y
add(5, 5) # 10, Function Warning이라는 경고 메시지가 출력된다.
경고 메시지도 특징 필터 조건을 처리해서 제어할 수 있다. simplefilter 함수를 사용한다.
def function_with_warning():
warnings.warn('Warning message')
warnings.simplefilter('once', UserWarning) # 한 번만 경고 지정
function_with_warning()
function_with_warning()
function_with_warning() # 3번 함수를 실행해도 경고는 한 번만 발생한다.
지원하지 않는 메소드의 경우, DeprecationWarning을 넣어 작성한다.
def fxn():
warnings.warn('deprecated', DeprecationWarning)
print('Executed')
fxn() # Executed와 DeprecationWarning 경고 메시지가 출력된다.
단언문은 예약어 assert, 조건식, 그리고 예외 발생시 정보로 구성된다.
단언문은 항상 참인 조건일 경우에 작성한다. 참인 조건이 발생하지 않는 경우에 예외가 발생한다.
assert 100 < 98, 'Exception' # Exception 출력, 조건이 만족하지 않았기 때문에 예외가 발생한다.
함수에서 매개변수의 자료형을 확인해서 항상 정수만 처리할 때도 사용한다.
def add(x, y):
assert (type(x), type(y)) in [(int, int)], 'Not Int'
return x + y
add(10, 20) # 30 출력, 정수를 인자로 전달해서 처리할 때는 단언문이 처리되지 않는다.
add(10, 1.1) # Not Int 출력, 정수와 실수를 넣고 처리하면 단언문이 실행된다.