Python 추상 클래스와 추상 메서드: ABC·abstractmethod의 실제 동작

반응형

Python에서 추상 클래스는 “상속만 가능한 불완전한 클래스”라기보다, 구현체가 지켜야 할 최소 계약을 실행 시점에도 확인하는 도구에 가깝다. abc.ABC를 상속하고 @abstractmethod를 붙이면, 구현되지 않은 추상 메서드가 남은 클래스의 인스턴스 생성을 막을 수 있다.

여기서 자주 놓치는 조건이 있다. ABC를 상속했다는 이유만으로 항상 인스턴스화가 금지되는 것은 아니다. 클래스의 __abstractmethods__에 구현되지 않은 항목이 남아 있을 때만 TypeError가 발생한다.

가장 작은 실행 예제

직렬화 형식을 예로 들면 계약과 공통 구현을 한 클래스에 둘 수 있다.

from abc import ABC, abstractmethod
from pathlib import Path


class Serializer(ABC):
    @abstractmethod
    def dumps(self, value: object) -> str:
        """객체를 문자열로 직렬화한다."""
        raise NotImplementedError

    def dump_file(self, value: object, path: Path) -> None:
        path.write_text(self.dumps(value), encoding="utf-8")


class IncompleteSerializer(Serializer):
    pass


class JsonSerializer(Serializer):
    def dumps(self, value: object) -> str:
        import json

        return json.dumps(value, ensure_ascii=False)


try:
    IncompleteSerializer()
except TypeError as error:
    print(type(error).__name__)

serializer = JsonSerializer()
print(serializer.dumps({"name": "준희"}))

IncompleteSerializer에는 dumps()가 남아 있으므로 객체를 만들 수 없다. 반면 JsonSerializer는 계약을 충족하므로 인스턴스화할 수 있고, 부모가 제공한 dump_file()도 그대로 사용한다.

ABC와 abstractmethod의 역할은 다르다

  • ABCABCMeta를 사용하기 위한 편리한 base class다.
  • @abstractmethod는 subclass가 구현해야 할 method를 표시한다.
  • 구현하지 않은 abstract method가 하나라도 남으면 그 subclass도 abstract class다.
  • abstract method가 하나도 없다면 ABC를 상속한 class도 인스턴스화할 수 있다.
  • abstract class 안에는 일반 method, property, state를 함께 둘 수 있다.

따라서 “추상 클래스에는 구현을 넣을 수 없다”는 설명은 정확하지 않다. 계약은 추상 메서드로 표현하고, 모든 구현체가 공유할 안전한 동작은 concrete method로 제공할 수 있다.

추상 메서드에도 기본 구현을 둘 수 있다

@abstractmethod가 붙은 method body가 반드시 pass일 필요는 없다. 협력적 다중 상속처럼 하위 클래스가 super()로 공통 동작을 호출해야 하는 설계에서는 추상 메서드에도 구현을 둘 수 있다.

from abc import ABC, abstractmethod


class Normalizer(ABC):
    @abstractmethod
    def normalize(self, text: str) -> str:
        return text.strip()


class LowerNormalizer(Normalizer):
    def normalize(self, text: str) -> str:
        return super().normalize(text).lower()


print(LowerNormalizer().normalize("  Hello  "))

핵심은 method body의 유무가 아니라 decorator가 만든 abstract 상태다. 하위 클래스는 여전히 override해야 하며, 필요하면 부모 구현을 재사용한다.

property와 classmethod에 붙이는 순서

abstractmethod는 가장 안쪽 decorator로 둔다. property와 class method는 다음 순서로 선언한다.

from abc import ABC, abstractmethod


class Document(ABC):
    @property
    @abstractmethod
    def media_type(self) -> str:
        raise NotImplementedError

    @classmethod
    @abstractmethod
    def from_text(cls, text: str) -> "Document":
        raise NotImplementedError

반대 순서는 descriptor가 abstract flag를 올바르게 전달하지 못해 오류가 날 수 있다. 오래된 abstractclassmethod, abstractstaticmethod, abstractproperty 대신 이 조합을 사용한다.

virtual subclass는 일반 상속과 다르다

ABC는 register()로 virtual subclass를 등록할 수 있다.

from abc import ABC


class Payload(ABC):
    def validate(self) -> bool:
        return True


class LegacyPayload:
    pass


Payload.register(LegacyPayload)
print(issubclass(LegacyPayload, Payload))

이 경우 issubclass()True지만, Payload의 method가 LegacyPayload의 MRO에 들어오지는 않는다. 등록만으로 abstract method 구현도 강제되지 않는다. virtual subclass는 runtime 분류가 필요한 제한된 상황에서 사용하고, 실제 behavior contract까지 보장한다고 해석하지 않는다.

ABC와 Protocol 중 무엇을 고를까

기준 ABC typing.Protocol
관계 명시적 상속 중심 필요한 attribute 구조 중심
확인 시점 미구현 abstract method의 인스턴스화 차단 type checker의 정적 검사 중심
공통 구현 제공 가능 가능하지만 주로 계약 표현에 사용
기존 class 적용 상속 또는 virtual registration 필요 구조가 맞으면 명시적 상속 불필요

framework가 lifecycle과 공통 구현을 소유하고 runtime에 잘못된 subclass 생성을 막아야 한다면 ABC가 잘 맞는다. 반대로 호출자가 필요한 method shape만 표현하고 구현체와의 결합을 낮추고 싶다면 Protocol이나 duck typing이 더 자연스럽다. 상속·Protocol·composition의 선택 기준은 Python 상속과 Interface 설계에서 이어서 다룬다.

자주 묻는 질문

ABC를 상속하면 객체를 만들 수 없나

아니다. 구현되지 않은 abstract method가 남아 있을 때만 인스턴스 생성이 차단된다. abstract method가 없는 ABC subclass는 만들 수 있다.

abstractmethod에는 반드시 pass만 써야 하나

아니다. 구현을 둘 수 있으며 subclass가 super()로 호출할 수도 있다. 다만 decorator가 붙어 있는 한 subclass는 해당 method를 override해야 concrete class가 된다.

ABC가 type hint를 대신하나

완전히 대신하지 않는다. ABC는 runtime inheritance와 인스턴스화 규칙을 제공하고, type annotation과 static checker는 별도의 검사를 맡는다. 필요에 따라 함께 사용한다.

참고 자료

반응형
KEEP READING
카테고리 전체 보기 →

댓글