본문 바로가기
Language/Python

[Python] 추상 메서드 ABC의 정의와 사용 | LIM

by forestlim 2022. 6. 19.
728x90
반응형

python의 ABC 클래스는 Base 클래스를 상속받는 파생 클래스가 반드시 Base 클래스의 메서드를 명시적으로 선언해서 구현하도록 강제하는 추상화 클래스 기능이다. 따라서 상속받는 클래스에서 메서드를 구현하지 않으면 에러가 발생한다. 

 

이 추상메서드를 이해하기 전에 먼저 상속과 다형성에 대한 개념을 잡아보도록 하자.

 

📌 상속(Inheritance)과 다형성

📚상속

OOP의 가장 강력한 기능중 하나인 상속은 클래스의 재사용성을 높임으로서, 코드의 반복에 따른 유지 보수 비용을 낮추는데 큰 역할을 했다. 

 

예시) Country의 속성을 상속받은 Korea 클래스이다. 오버라이딩한 show 메서드에서는 Korea에서 정의한 대로 출력되고 나머지의경우 오버라이딩 되지 않아서 부모 클래스에서 정의된대로 출력되게 된다.

class Country:
    """Super Class"""

    name = '국가명'
    population = '인구'
    capital = '수도'

    def show(self):
        print('국가 클래스의 메소드입니다.')


class Korea(Country):
    """Sub Class"""

    def __init__(self, name):
        self.name = name

    def show(self):
        print('국가 이름은 : ', self.name)


>>> korea = Korea('대한민국')
>>> korea.show()
국가 이름은 :  대한민국
>>> korea.capital
'수도'
>>> korea.name
'대한민국'

 

📚다형성

다형성이란, 하나의 인터페이스를 통해 서로 다른 여러 타입을 제공하는 것을 의미한다. 보통 OOP에서 말하는 다형성은 클래스에 선언된 메서드가 상속 받은 클래스에서 같은 이름으로 오버라이딩 되어 여러 형태로 동작함을 의미한다. 

 

예시) 먼저 OriginClass를 정의 후 여기에 func1과 func2 메서드를 선언했다. 

class OriginClass:
    def func1(self):
        pass
    
    def func2(self):
        pass

 

 

그리고 이러한 OirginClass를 상속받는 CopyClass1이 있다고 가정해보자. 이 클래스는 OriginClass의 func1과 func2를 각각 메소드 오버라이딩해서 구현했다. 각각의 결과는 CopyClass1에서 정의한대로 출력된다.

class CopyClass1(OriginClass):
    def func1(self):
        print("Func1 is inherited1")
    
    def func2(self):
        print("Func2 is inherited1")


copyclass1 = CopyClass1()
copyclass1.func1()
copyclass1.func2()

>>> Func1 is inherited1
>>> Func2 is inherited1

 

CopyClass2에서는 func1은 CopyClass1과 다르게 func2는 제외하고 구현해보았다. func1의 결과는 CopyClass2에서 정의한대로 잘 출력되지만 func2의 결과는 아무것도 출력되지 않는다.


부모 클래스의 func2메서드에서 이를 pass만 하도록 구현한 상태이기 때문에 아무것도 하지 않고 넘어간다. 이렇게 되면 나중에 이 부분의 구현을 추가해야한다는 사실을 잊고 넘어갈 수 있고, 추후 이로 인한 side effect가 생길 여지가 있다.

class CopyClass2(OriginClass):
    def func1(self):
        print(f"=================")
        print("Func1 is inherited2")
        print(f"=================")
        

copyclass2 = CopyClass2()
copyclass2.func1()
copyclass2.func2()

>>> =================
>>> Func1 is inherited2
>>> =================

 

위와 같은 불상사를 피하기 위해 아래와 같이 OriginClass에 에러 호출부분을 추가해보자.

이렇게 구현하면 OriginClass를 상속받은 클래스에서 func2()를 구현하지 않고 호출한다면 자동으로 OriginClass의 메서드를 호출하고 NotImplementedError를 발생시키게 된다. 아래에서 설명하겠지만 상속받은 클래스는 인스턴스화되지만 실제로 메서드를 실행시키는 단계에서 에러가 발생하게 된다.

class OriginClass:    
	def func1(self):        
    	raise NotImplementedError()    
        
    def func2(self):        
    	raise NotImplementedError()
        
        
class CopyClass2(OriginClass):
    def func1(self):
        print(f"=================")
        print("Func1 is inherited2")
        print(f"=================")

 

위처럼 상속 클래스들을 관리할 수 있게 되고 추후 유지보수를 용이하게 할 수 있다. 하지만 이것보다 더 Strict한 방식을 제공하는 것이 ABC 클래스이다. 

 

📌 ABC(Abstract Base Class) Class

추상 클래스는 메소드의 구체적인 구현이 없는 클래스를 말한다. 즉 일종의 틀을 제공해주는 역할을 하는 것이다. 이 틀은 추상클래스를 상속하는 클래스들에게 특정 메소드의 구현을 강제하는 효과가 있게 한다. 

Python의 추상클래스는 Java의 인터페이스라고 생각하면 된다. 인터페이스란 일종의 포맷 스펙이다. 인터페이스는 형식을 강조한다.

 

아래는 OrginClass에 abc를 적용한 예이다. 추상화시키고자 하는 메서드에 데코레이터로 @abstractmethod를 선언해주면 된다. 이렇게 적용하게 되면, OriginClass를 상속받는 모든 파생 클래스에서 해당 메서드를 선언해서 구현하지 않으면, 에러를 발생시키게 된다. 

from abc import ABCMeta, abstractmethod

class OriginClass(metaclass=ABCMeta):
    @abstractmethod
    def func1(self):
        pass
    
    @abstractmethod
    def func2(self):
        pass

 

그렇다면 ABC를 사용하는 것과 raise NotImplementedError를 메서드마다 선언해놓는 것은 어떤 차이가 있을까

 

첫째로, abc클래스를 이용하게 되면, 해당 OriginClass는 인스턴스화 될 수 없다. 단지 파생 클래스 구현을 위한 추상화 기능 제공 역할만 할 뿐이다.

 

 

두번째로, abc클래스를 이용하게 될 경우 에러 발생 시점이 다르다. NotImplementedError를 선언해놓는 경우 런타임 상황에서 메서드가 실제로 호출이 되는 시점에서 에러를 발생시키게 되지만, abc를 사용하는 경우 인스턴스를 생성하는 것부터 에러를 발생시키게 된다.

728x90
반응형

댓글