Python 추상 클래스와 추상 메서드: ABC와 @abstractmethod 이해하기

Ad Placeholder

앞에서 Python 클래스의 상속(Inheritance)에 대해 알아봤습니다.

상속을 사용하면 부모 클래스의 기능을 자식 클래스가 물려받을 수 있습니다.

그런데 클래스를 설계하다 보면 이런 상황이 생길 수 있습니다.

여러 자식 클래스가 반드시 같은 기능을 가지고 있어야 하는데, 부모 클래스에서는 그 기능의 구체적인 구현 방법을 정하기 어려운 경우

예를 들어 모든 동물에게 speak()라는 기능이 필요하다고 해보겠습니다.

강아지는:

def speak(self):
    print("멍멍")

고양이는:

def speak(self):
    print("야옹")

처럼 구현할 수 있습니다.

하지만 Animal 클래스에서 모든 동물의 소리를 미리 정해 놓는 것은 적절하지 않습니다.

이럴 때 사용하는 것이 추상 클래스(Abstract Class)와 추상 메서드(Abstract Method)입니다.

이번 글에서는 추상 클래스가 무엇인지, Python에서 ABC와 @abstractmethod를 어떻게 사용하는지 알아보겠습니다.


1. 추상 클래스란?

추상 클래스는 자식 클래스가 반드시 구현해야 하는 기능을 정의하기 위한 클래스라고 생각하면 이해하기 쉽습니다.

일반적인 부모 클래스는 직접 객체를 만들 수도 있습니다.

class Animal:
    def eat(self):
        print("먹습니다.")


animal = Animal()

하지만 추상 클래스는 설계 목적으로 사용합니다.

예를 들어:

Animal
 ├── Dog
 ├── Cat
 └── Bird

모든 동물에게 speak()라는 기능은 필요하지만 실제 동작은 각각 다를 수 있습니다.

따라서 부모 클래스에서는

“모든 동물은 speak() 기능을 가져야 한다.”

라는 규칙만 정하고 실제 구현은 자식 클래스에게 맡길 수 있습니다.


2. Python에서 추상 클래스를 만드는 방법

Python에서는 abc 모듈을 사용해서 추상 클래스를 만들 수 있습니다.

from abc import ABC, abstractmethod

그리고 클래스를 ABC를 상속받도록 작성합니다.

class Animal(ABC):
    pass

여기에 @abstractmethod를 사용해서 추상 메서드를 정의할 수 있습니다.

from abc import ABC, abstractmethod


class Animal(ABC):

    @abstractmethod
    def speak(self):
        pass

여기서 중요한 부분은 다음 두 가지입니다.

class Animal(ABC):

그리고:

@abstractmethod
def speak(self):

Animal은 추상 클래스가 되고 speak()는 추상 메서드가 됩니다.


3. 추상 클래스는 직접 객체를 만들 수 없다

앞에서 만든 Animal 클래스로 객체를 생성해 보겠습니다.

from abc import ABC, abstractmethod


class Animal(ABC):

    @abstractmethod
    def speak(self):
        pass


animal = Animal()

실행하면 오류가 발생합니다.

추상 메서드가 구현되지 않은 추상 클래스는 직접 인스턴스를 생성할 수 없습니다.

즉, Animal은 객체를 만들기 위한 일반적인 클래스라기보다 자식 클래스가 어떤 기능을 반드시 구현해야 하는지 정의하는 설계도에 가깝습니다.


4. 자식 클래스에서 추상 메서드를 구현하기

이제 Animal을 상속받는 Dog를 만들어 보겠습니다.

from abc import ABC, abstractmethod


class Animal(ABC):

    @abstractmethod
    def speak(self):
        pass


class Dog(Animal):

    def speak(self):
        print("멍멍")

이제 Dog 객체는 정상적으로 생성할 수 있습니다.

dog = Dog()

dog.speak()

결과:

멍멍

Dog에서 부모 클래스의 추상 메서드인 speak()를 구현했기 때문입니다.


5. 추상 메서드를 구현하지 않으면?

이번에는 speak()를 구현하지 않은 클래스를 만들어 보겠습니다.

from abc import ABC, abstractmethod


class Animal(ABC):

    @abstractmethod
    def speak(self):
        pass


class Dog(Animal):
    pass

이 상태에서:

dog = Dog()

를 실행하면 오류가 발생합니다.

왜냐하면 Dog가 부모 클래스에서 요구한 speak()를 구현하지 않았기 때문입니다.

즉, 추상 클래스는 자식 클래스에게 일종의 규칙을 강제할 수 있습니다.

Animal
 └── speak() 반드시 구현

Dog
 └── speak() 구현 → 객체 생성 가능

Cat
 └── speak() 구현 → 객체 생성 가능

이런 구조를 만들 수 있습니다.


6. 여러 자식 클래스에서 사용하는 예제

추상 클래스의 장점을 조금 더 명확하게 살펴보겠습니다.

from abc import ABC, abstractmethod


class Animal(ABC):

    @abstractmethod
    def speak(self):
        pass


class Dog(Animal):

    def speak(self):
        print("멍멍")


class Cat(Animal):

    def speak(self):
        print("야옹")


class Bird(Animal):

    def speak(self):
        print("짹짹")

이제 각 객체를 만들 수 있습니다.

dog = Dog()
cat = Cat()
bird = Bird()

dog.speak()
cat.speak()
bird.speak()

결과:

멍멍
야옹
짹짹

모든 클래스가 speak()를 가지고 있지만 실제 동작은 각각 다릅니다.

이렇게 공통 인터페이스를 정의하고 구체적인 구현은 자식 클래스에 맡기는 것이 추상 클래스의 중요한 역할입니다.


7. 추상 메서드에 구현을 작성할 수도 있을까?

추상 메서드라고 해서 반드시 pass만 작성해야 하는 것은 아닙니다.

추상 메서드에도 기본적인 구현을 작성할 수 있습니다.

from abc import ABC, abstractmethod


class Animal(ABC):

    @abstractmethod
    def speak(self):
        print("동물이 소리를 냅니다.")

자식 클래스에서 구현하면 자신의 동작을 사용할 수 있습니다.

class Dog(Animal):

    def speak(self):
        print("멍멍")

필요하다면 super()를 사용해서 부모의 구현도 호출할 수 있습니다.

class Dog(Animal):

    def speak(self):
        super().speak()
        print("멍멍")

그러면:

동물이 소리를 냅니다.
멍멍

이 출력됩니다.

따라서 추상 메서드는 반드시 구현 내용이 없어야 하는 것은 아닙니다.

핵심은 자식 클래스에서 반드시 재정의해야 하는 메서드라는 점입니다.


8. 추상 클래스에 일반 메서드도 만들 수 있다

추상 클래스에는 추상 메서드만 존재해야 하는 것은 아닙니다.

일반적인 메서드도 함께 정의할 수 있습니다.

from abc import ABC, abstractmethod


class Animal(ABC):

    def eat(self):
        print("먹습니다.")

    @abstractmethod
    def speak(self):
        pass

이제 자식 클래스에서는 eat()을 그대로 사용할 수 있습니다.

class Dog(Animal):

    def speak(self):
        print("멍멍")

사용:

dog = Dog()

dog.eat()
dog.speak()

결과:

먹습니다.
멍멍

즉, 추상 클래스는 다음과 같은 구조를 가질 수 있습니다.

추상 클래스
├── 공통 구현 메서드
│   └── eat()
│
└── 추상 메서드
    └── speak()

공통적인 동작은 부모 클래스에서 제공하고, 자식마다 달라져야 하는 기능은 추상 메서드로 강제할 수 있습니다.


9. 추상 클래스와 일반 상속의 차이

그렇다면 일반적인 부모 클래스와 추상 클래스는 무엇이 다를까요?

일반적인 상속에서는 자식 클래스가 부모의 메서드를 선택적으로 사용할 수 있습니다.

class Animal:

    def speak(self):
        print("소리를 냅니다.")


class Dog(Animal):
    pass

Dog는 speak()를 따로 구현하지 않아도 객체를 만들 수 있습니다.

dog = Dog()

반면 추상 클래스를 사용하면 특정 메서드를 반드시 구현하도록 만들 수 있습니다.

from abc import ABC, abstractmethod


class Animal(ABC):

    @abstractmethod
    def speak(self):
        pass


class Dog(Animal):
    pass

이 경우 Dog는 speak()를 구현하지 않았기 때문에 객체를 만들 수 없습니다.

따라서 차이를 간단히 정리하면:

구분일반 부모 클래스추상 클래스
목적공통 기능 제공공통 규칙과 인터페이스 정의
직접 객체 생성가능추상 메서드가 있으면 불가능
자식 클래스의 구현 강제일반적으로 없음@abstractmethod로 가능
공통 메서드사용 가능사용 가능
추상 메서드없음사용 가능

10. 추상 클래스가 필요한 이유

추상 클래스는 프로젝트의 규모가 커질수록 유용할 수 있습니다.

예를 들어 결제 시스템을 만든다고 해보겠습니다.

결제 방법은 여러 가지가 있을 수 있습니다.

Payment
 ├── CardPayment
 ├── KakaoPayment
 └── BankPayment

각 결제 방법마다 실제 처리 방식은 다르지만 모든 결제 클래스에 pay()가 필요하다고 해보겠습니다.

이럴 때 다음과 같이 정의할 수 있습니다.

from abc import ABC, abstractmethod


class Payment(ABC):

    @abstractmethod
    def pay(self, amount):
        pass

그리고 각각 구현합니다.

class CardPayment(Payment):

    def pay(self, amount):
        print(f"카드로 {amount}원 결제")
class BankPayment(Payment):

    def pay(self, amount):
        print(f"계좌이체로 {amount}원 결제")

이렇게 하면 Payment를 상속받는 클래스는 pay()를 반드시 구현해야 합니다.


11. 공통 인터페이스를 만드는 효과

추상 클래스의 중요한 장점 중 하나는 일관된 사용 방법을 만들 수 있다는 것입니다.

예를 들어 다음과 같은 결제 객체가 있다고 해보겠습니다.

payments = [
    CardPayment(),
    BankPayment()
]

각 객체의 실제 구현은 다르지만 호출 방법은 동일합니다.

for payment in payments:
    payment.pay(10000)

결과:

카드로 10000원 결제
계좌이체로 10000원 결제

사용하는 쪽에서는 각각의 클래스 내부 구현을 자세히 알 필요가 없습니다.

단지:

payment.pay(amount)

이라는 공통된 인터페이스만 알고 있으면 됩니다.

이런 구조는 객체지향 프로그래밍에서 상당히 중요한 개념입니다.


12. 추상 클래스와 다형성

앞의 예제에서 중요한 개념이 하나 더 등장합니다.

바로 다형성(Polymorphism)입니다.

CardPayment와 BankPayment는 서로 다른 클래스입니다.

하지만 둘 다 Payment를 상속하고 pay()를 구현합니다.

for payment in payments:
    payment.pay(10000)

같은 pay()를 호출하지만 객체의 종류에 따라 실제 동작이 달라집니다.

Payment
   │
   ├── CardPayment → 카드 결제
   │
   └── BankPayment → 계좌이체

이처럼 같은 인터페이스를 사용하면서 객체에 따라 다른 동작을 수행하는 것이 다형성의 대표적인 형태입니다.


13. @abstractmethod는 어디에서 가져올까?

@abstractmethod는 Python의 기본 abc 모듈에서 가져옵니다.

from abc import ABC, abstractmethod

보통 다음과 같이 작성합니다.

from abc import ABC, abstractmethod


class Animal(ABC):

    @abstractmethod
    def speak(self):
        pass

여기서:

ABC

는 추상 클래스를 만들기 위한 기반 클래스이고,

@abstractmethod

는 자식 클래스에서 구현해야 하는 추상 메서드를 지정하는 데 사용합니다.


14. 추상 메서드를 여러 개 만들 수도 있다

하나의 추상 클래스에 여러 개의 추상 메서드를 정의할 수도 있습니다.

예를 들어 파일 저장 기능을 추상화한다고 해보겠습니다.

from abc import ABC, abstractmethod


class Storage(ABC):

    @abstractmethod
    def save(self, data):
        pass

    @abstractmethod
    def load(self):
        pass

이제 자식 클래스에서는 두 메서드를 모두 구현해야 합니다.

class FileStorage(Storage):

    def save(self, data):
        print("파일에 저장합니다.")

    def load(self):
        print("파일에서 불러옵니다.")

만약 save()나 load() 중 하나라도 구현하지 않으면 해당 자식 클래스는 완전한 구현 클래스가 되지 않습니다.

이렇게 추상 클래스에서 여러 가지 규칙을 정의할 수 있습니다.


15. 실무에서는 어디에 사용할까?

추상 클래스는 작은 프로그램에서는 굳이 필요하지 않을 수도 있습니다.

하지만 여러 구현체가 존재하는 큰 프로그램에서는 유용하게 사용할 수 있습니다.

대표적인 예는 다음과 같습니다.

데이터 저장소

Storage
├── MySQLStorage
├── PostgreSQLStorage
└── FileStorage

결제 시스템

Payment
├── CardPayment
├── BankPayment
└── MobilePayment

머신러닝 모델

Model
├── ClassificationModel
├── RegressionModel
└── AnomalyDetectionModel

각 구현체의 내부 동작은 다르더라도 공통적으로 필요한 메서드를 정의할 수 있습니다.

예를 들어:

class Model(ABC):

    @abstractmethod
    def train(self):
        pass

    @abstractmethod
    def predict(self, data):
        pass

각 모델은 train()과 predict()를 자신에게 맞는 방식으로 구현하면 됩니다.

이런 구조는 프로젝트가 커졌을 때 코드의 규칙을 유지하는 데 도움이 됩니다.


16. 추상 클래스와 인터페이스

다른 프로그래밍 언어를 공부했다면 인터페이스(Interface)라는 개념을 접해봤을 수도 있습니다.

Python에는 Java나 C#과 완전히 동일한 형태의 인터페이스가 별도로 존재하는 것은 아닙니다.

대신 ABC와 @abstractmethod를 사용해서 비슷한 목적의 구조를 만들 수 있습니다.

예를 들어:

class Payment(ABC):

    @abstractmethod
    def pay(self, amount):
        pass

이렇게 하면 Payment를 사용하는 클래스들이 어떤 메서드를 제공해야 하는지 명확하게 정의할 수 있습니다.

다만 Python에서는 추상 클래스만이 유일한 방법은 아닙니다.

Duck Typing이나 Protocol 같은 다른 방식으로도 인터페이스에 해당하는 구조를 만들 수 있습니다.

프로젝트의 목적과 구조에 따라 적절한 방법을 선택하면 됩니다.


17. 추상 클래스 사용 시 주의할 점

추상 클래스는 강력하지만 모든 클래스에 사용할 필요는 없습니다.

클래스가 몇 개 없는 간단한 프로그램이라면 일반적인 상속만으로도 충분할 수 있습니다.

반면 다음과 같은 상황에서는 추상 클래스를 고려할 수 있습니다.

  • 여러 자식 클래스가 동일한 기능을 반드시 제공해야 하는 경우
  • 클래스마다 구현은 다르지만 사용 방법은 동일해야 하는 경우
  • 프로젝트에서 특정 구조를 강제해야 하는 경우
  • 여러 개발자가 함께 작업하면서 클래스의 공통 규칙을 명확히 해야 하는 경우

반대로 단순히 코드를 재사용하기 위해 추상 클래스를 만드는 것은 오히려 구조를 복잡하게 만들 수 있습니다.


18. 추상 클래스의 전체 구조

지금까지 배운 내용을 하나의 예제로 정리해 보겠습니다.

from abc import ABC, abstractmethod


class Animal(ABC):

    def eat(self):
        print("먹습니다.")

    @abstractmethod
    def speak(self):
        pass


class Dog(Animal):

    def speak(self):
        print("멍멍")


class Cat(Animal):

    def speak(self):
        print("야옹")

이제 사용할 수 있습니다.

dog = Dog()
cat = Cat()

dog.eat()
dog.speak()

cat.eat()
cat.speak()

결과:

먹습니다.
멍멍
먹습니다.
야옹

구조를 정리하면 다음과 같습니다.

                 Animal
                /      \
               /        \
             Dog        Cat
              │          │
           speak()     speak()
           "멍멍"       "야옹"

Animal은 공통 기능인 eat()을 제공하고, speak()는 자식 클래스가 각각 구현하도록 강제합니다.

이것이 추상 클래스의 대표적인 사용 방식입니다.


마무리

이번 글에서는 추상 클래스(Abstract Class)와 추상 메서드(Abstract Method)를 알아봤습니다.

핵심은 ABC와 @abstractmethod입니다.

from abc import ABC, abstractmethod


class Animal(ABC):

    @abstractmethod
    def speak(self):
        pass

이렇게 만들면 Animal은 추상 클래스가 되고 speak()는 추상 메서드가 됩니다.

그리고 이를 상속받는 자식 클래스는 speak()를 구현해야 합니다.

class Dog(Animal):

    def speak(self):
        print("멍멍")

추상 클래스는 단순히 부모 클래스의 기능을 물려주는 것에서 한 단계 더 나아가,

자식 클래스가 반드시 지켜야 할 구조와 규칙을 정의하는 역할

을 합니다.

특히 여러 구현체가 존재하는 프로젝트에서 공통 인터페이스를 만들고 코드의 일관성을 유지하는 데 유용합니다.

이번 글을 마지막으로 클래스 시리즈에서 다룬 내용을 정리하면 다음과 같습니다.

1. 클래스 기본 개념
2. 인스턴스 메서드 / 클래스 메서드 / 정적 메서드
3. __dict__ / __slots__
4. property
5. Special Method
6. Callable Object
7. 클래스 데코레이터
8. 상속
9. 추상 클래스와 추상 메서드

이제 클래스의 기본 문법부터 객체의 동작을 커스터마이징하는 방법, 상속과 추상화까지 한 번씩 살펴본 셈입니다.

jiji

AI, Python, 생활정보와 정부 정책 정보를 쉽게 설명하는 정보 블로그입니다.

Ad Placeholder
Scroll to Top