본문 바로가기
Python

abc (추상화 클래스)

by yororing 2024. 6. 28.
# testFile.py 내용

from abc import ABCMeta, abstractmethod

class OriginClass(metaclass=ABCMeta):
    @abstractmethod
    def func1(self):
        pass
    # @abstractmethod
    def func2(self):
        pass
    
class SubClass(OriginClass):
    def func1(self):
        print("func1 구현"))

hello = SubClass()
hello.func1()

00 개요

  • 목적: 파이썬에서 제공하는 abc 클래스의 개념 및 기능에 대해 정리하고자 함

01 abc 클래스란?

1. 정의

  • 'Abstract Base Class'
  • Python이 제공하는 '추상화 클래스'
  • abc 클래스는 Base 클래스를 상속받는 파생 클래스가 반드시 Base 클래스의 Method를 명시적으로 선언해서 구현하도록 강제하는 추상화 클래스 기능
  • 이 기능의 필요성을 이해하기 위해 상속과 다형성에 대한 이해 필요

1) 상속이란 (inheritance)

  • OOP(객체 지향 프로그래밍)의 가장 강력한 기능 중 하나인 '상속'은 클래스의 재사용성을 높임으로서 코드의 반복에 따른 유지 보수 비용을 낮추는 데 큰 역할을 함
  • 예시) Country 클래스의 속성을 상속받은 Korea 클래스
    • 오버라이딩한 show() 메서드에서는 Korea 클래스에서 정의한 대로 출력되고 나머지의 경우 오버라이딩이 되지 않아서 부모 클래스에서 정의된 대로 출력하게 됨
# testFile.py 내용

class Country:
    """Super Class"""

    name = '국가명'
    poplation = '인구'
    capital = '수도'
    
    def show(self):
        print('국가 클래스의 메소드입니다.')
        
class Korea(Country):
    """Sub class"""
    
    def __init__(self, name):
        self.name = name
    def show(self):
    	print('국가 이름은: ', self.name)

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

2) 다형성이란 (polymorphism)

  • 하나의 인터페이스를 통해 서로 다른 여러 타입을 제공하는 것을 의미
  • OOP에서의 다향성은 클래스에 선언된 메서드가 상속 받은 클래스에서 같은 이름으로 오버라이딩되어 여러 형태로 동작함을 의미
  • 예시) OriginClass 정의 후 여기에 func1과 func2 메서드 선언
    • 이러한 OirginClass를 상속받는 CopyClass1이 있다고 가정
    • 이 클래스는 OriginClass의 func1과 func2를 각각 메소드 오버라이딩해서 구현됨
    • 각각의 결과는 CopyClass1에서 정의한대로 출력됨
# testFile.py 내용

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

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

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

# 터미널에서
python testFile.py
# 출력값
Func1 is inherited1
Func2 is inherited1

  • 예시) CopyClass2에서는 func1은 CopyClass1과 다르게 func2는 제외하고 구현됨
    • func1의 결과는 CopyClass2에서 정의한대로 잘 출력되지만 func2의 결과는 아무것도 출력되지 않음
    • 부모 클래스의 func2메서드에서 이를 pass만 하도록 구현한 상태이기 때문에 아무것도 하지 않고 넘어가는 것
    • 이렇게 되면 나중에 이 부분의 구현을 추가해야한다는 사실을 잊고 넘어갈 수 있고, 추후 이로 인한 side effect가 생길 여지 있음
# testFile.py 내용

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

class CopyClass2(OriginClass):
    def func1(self):
        print("=========")
        print("Func1 is inherited2")
        print("=========")
 copyclass2 = CopyClass2()
 copyclass2.func1()
 copyclass2.func2()
 
 # 터미널에서
 python testFile.py
 # 출력값
=========
Func1 is inherited2
=========

  • 위와 같은 불상사를 피하기 위해 아래와 같이 OriginClass에 에러 호출 부분 (NotImplementedError)을 추가
    • 이렇게 구현 시 OriginClass를 상속받은 클래스에서 func2()를 구현하지 않고 호출한다면 자동으로 OriginClass의 메서드를 호출하고 NotImplementedError를 발생시킴
    • 상속받은 클래스는 인스턴스화되지만 실제로 메서드를 실행시키는 단계에서 에러 발생
# testFile.py 내용

class OriginClass:
    def func1(self):
        raise NotImplementedError()
    def func2(self):
        raise NotImplementedError()

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

class CopyClass2(OriginClass):
    def func1(self):
        print("=========")
        print("Func1 is inherited2")
        print("=========")
        
copyclass2 = CopyClass2()
print("CopyClass2().func1() being called: \n")
copyclass2.func1()
print("\nCopyClass2().func2() being called: \n")
copyclass2.func2()

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

3) abc (Abstract Base Class) 클래스란

  • 추상 클래스는 메소드의 구체적인 구현이 없는 클래스
  • 즉, 일종의 틀을 제공해주는 역할
  • 이 틀은 추상 클래스를 상속하는 클래스들에게 특정 메소드의 구현을 강제하는 효과가 있게 함
  • 어떻게? → abc를 부모 클래스에 적용 시 부모 클래스를 상속받는 모든 파생/자식/하위 클래스 (derived/child/sub class)에서 해당 메서드를 선언해서 구현하지 않으면 에러 발생
  • Python의 추상 클래스는 Java의 인터페이스와 비슷한 개념
    • 인터페이스란 일종의 포맷 스펙이며 형식을 강조함
    • 즉, 일관된 인터페이스를 제공하기 위해 사용됨..?
  • 예시) OrginClass에 abc를 적용
    • 추상화시키고자 하는 메서드들에 데코레이터로 @abstractmethod를 선언하여 추상화 적용
    • 적용 시 OriginClass를 상속받는 모든 파생 클래스 (sub class)에서 해당 메서드들을 선언해서 구현하지 않으면 에러 발생
# testFile.py 내용

from abc import ABCMeta, abstractmethod

class OriginClass(metaclass=ABCMeta):
    @abstractmethod
    def func1(self):
        pass
    @abstractmethod
    def func2(self):
        pass
    
class SubClass(OriginClass):   # NOTE: fun1만 구현하고 fun2는 구현 안함
    def func1(self):
        print("func1 구현")

hello = SubClass()
hello.func1()

  • ERROR 내용: func2 매서드 implement 안하고서는 SubClass()를 인스턴스화할 수 없음
    • 즉, SubClass는 OriginClass를 상송받는데, OriginClass에서는 func1과 func2에 @abstractmethod가 적용되었기에 func1과 func2 둘 다  OriginClass를 상속받은 SubClass에서 구현되어야지만 SubClass()를 인스턴스화 가능한 것
  • 해결 방법: 1) OriginClass를 상속받은 SubClass() 에서 func2를 구현, 또는 2) OriginClass에서 func2를 @abstractmethod 미적용
  • 1) OriginClass를 상속받은 SubClass() 에서 func2를 구현
# testFile.py 내용

from abc import ABCMeta, abstractmethod

class OriginClass(metaclass=ABCMeta):
    @abstractmethod
    def func1(self):
        pass
    @abstractmethod
    def func2(self):
        pass
    
class SubClass(OriginClass):
    def func1(self):
        print("func1 구현")
    def func2(self):
        print("func2 구현")

hello = SubClass()
hello.func1()
hello.func2()

  • 설명: Subclass()에서 func1과 func2 (OriginClass()에서 @abstractmethod 적용하여 추상화된 매소드들) 가 모두 구현되었기에 SubClass()가 hello라는 변수에 인스턴스화가 정상적으로 되었으며 hello 인스턴스로 해당 매소드(func1, func2)들이 정상적으로 호출되는 것 확인
  • 2) OriginClass에서 func2를 @abstractmethod 미적용
# testFile.py 내용

from abc import ABCMeta, abstractmethod

class OriginClass(metaclass=ABCMeta):
    @abstractmethod
    def func1(self):
        pass
    # @abstractmethod   주석처리, 즉 추상화 미적용
    def func2(self):
        pass
    
class SubClass(OriginClass):
    def func1(self):
        print("func1 구현")

hello = SubClass()
hello.func1()

  • 설명: OriginClass()에서 func2에는 @abstractmethod를 적용하지 않았기에 SubClass에서 func1만 구현해도 SubClass()가 hello라는 변수에 인스턴스화가 정상적으로 되며 hello 인스턴스로 해당 매소드(func1)가 정상적으로 호출되는 것 확인

4)  abc 사용하는 것과 raise NotImplementedError를 메서드마다 선언해놓는 것의 차이

  • 1> abc 클래스 사용 시 해당 OriginClass는 인스턴스화 될 수 없음; 단지 파생 클래스 구현을 위한 추상화 기능만 제공

  • 2> abc 클래스 사용 시 에러 발생 시점이 다름
    • NotImplementedError 선언 시 런타임 상황에서 메서드가 실제로 호출이 되는 시점에서 에러 발생
    • abc 사용 시 인스턴스를 생성하는 것부터 에러 발생

참조

  1. https://amazelimi.tistory.com/entry/Python-%EC%B6%94%EC%83%81-%EB%A9%94%EC%84%9C%EB%93%9C-ABC%EC%9D%98-%EC%A0%95%EC%9D%98%EC%99%80-%EC%82%AC%EC%9A%A9
  2. https://xangmin.tistory.com/161
  3.  
  4.