Skip to content

Latest commit

 

History

History
351 lines (297 loc) · 11.6 KB

211119.md

File metadata and controls

351 lines (297 loc) · 11.6 KB

Day 47

한 권으로 개발자가 원하던 파이썬 심화 A to Z

추상 메타 클래스와 추상 클래스

파이썬은 클래스를 정의할 때 클래스를 상속하고 클래스를 생성하는 메타 클래스를 지정한다.
추상 클래스와 추상 메타 클래스도 일반적인 클래스와 메타 클래스처럼 생성 관계로 처리된다.

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 출력, 정수와 실수를 넣고 처리하면 단언문이 실행된다.