Python ABC (Abstract Base Class) 활용법
Python의 추상 베이스 클래스(ABC)를 활용하여 인터페이스를 정의하고 강제하는 방법을 알아봅니다.
Python ABC (Abstract Base Class) 활용법
Python의 Abstract Base Class (ABC)는 인터페이스를 정의하고, 구현 클래스가 필수 메서드를 반드시 구현하도록 강제하는 데 사용됩니다.
Environment
$ python --version
Python 3.11.5
$ python -c "from abc import ABC, abstractmethod; print('ABC module available')"
ABC module availableProblem: 인터페이스 강제가 안 됨
class Animal:
def speak(self):
raise NotImplementedError("Subclasses must implement speak()")
class Dog(Animal):
pass # Forgot to implement speak()
# No error until runtime!
dog = Dog()
dog.speak() # NotImplementedError at runtimeAnalysis: ABC 동작 원리
from abc import ABC, abstractmethod
class AbstractAnimal(ABC):
@abstractmethod
def speak(self):
"""Subclasses MUST implement this"""
pass
@abstractmethod
def move(self):
pass
# Non-abstract method (optional to override)
def sleep(self):
return "Zzz..."
# This will raise TypeError at instantiation
try:
animal = AbstractAnimal()
except TypeError as e:
print(f"Error: {e}")
# Error: Can't instantiate abstract class AbstractAnimal
# with abstract methods move, speakSolution: ABC 활용법
기본 ABC 정의
from abc import ABC, abstractmethod
from typing import List
class Vehicle(ABC):
def __init__(self, make: str, model: str, year: int):
self.make = make
self.model = model
self.year = year
@abstractmethod
def start(self) -> str:
"""Start the vehicle"""
pass
@abstractmethod
def stop(self) -> str:
"""Stop the vehicle"""
pass
@abstractmethod
def fuel_type(self) -> str:
"""Return fuel type"""
pass
# Concrete method (can be overridden)
def info(self) -> str:
return f"{self.year} {self.make} {self.model}"
class Car(Vehicle):
def start(self) -> str:
return "Car started"
def stop(self) -> str:
return "Car stopped"
def fuel_type(self) -> str:
return "Gasoline"
def drive(self) -> str: # Additional method
return "Driving on road"
class ElectricCar(Vehicle):
def start(self) -> str:
return "Electric car started silently"
def stop(self) -> str:
return "Electric car stopped"
def fuel_type(self) -> str:
return "Electricity"
def charge(self) -> str:
return "Charging battery"
# Usage
car = Car("Toyota", "Camry", 2024)
ev = ElectricCar("Tesla", "Model 3", 2024)
print(car.info()) # 2024 Toyota Camry
print(car.start()) # Car started
print(car.fuel_type()) # Gasoline
print(ev.info()) # 2024 Tesla Model 3
print(ev.charge()) # Charging batteryABC 등록 (Register)
from abc import ABC, abstractmethod
class Serializer(ABC):
@abstractmethod
def serialize(self, data) -> str:
pass
class JSONSerializer(Serializer):
def serialize(self, data) -> str:
import json
return json.dumps(data)
class XMLSerializer(Serializer):
def serialize(self, data) -> str:
return f"{data}"
# Register without inheriting
class CSVSerializer:
def serialize(self, data) -> str:
return ",".join(str(v) for v in data.values())
# Register CSVSerializer as Serializer
Serializer.register(CSVSerializer)
# Check if class is registered
print(issubclass(CSVSerializer, Serializer)) # True
print(isinstance(CSVSerializer(), Serializer)) # TrueABC와 Type Hints
from abc import ABC, abstractmethod
from typing import Protocol, runtime_checkable
# Using ABC
class Drawable(ABC):
@abstractmethod
def draw(self) -> None:
pass
# Using Protocol (structural subtyping)
@runtime_checkable
class Serializable(Protocol):
def serialize(self) -> str:
...
class MyData:
def serialize(self) -> str:
return "serialized data"
# Check structural compatibility
data = MyData()
print(isinstance(data, Serializable)) # True
# ABC vs Protocol
# - ABC: Explicit inheritance required
# - Protocol: Structural subtyping (duck typing with type hints)Virtual Subclass
from abc import ABC, abstractmethod
class Plugin(ABC):
@abstractmethod
def execute(self):
pass
@classmethod
def __subclasshook__(cls, C):
if cls is Plugin:
if any("execute" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
class MyPlugin:
def execute(self):
return "Executing plugin"
# MyPlugin is recognized as Plugin subclass without inheritance
print(issubclass(MyPlugin, Plugin)) # True
print(isinstance(MyPlugin(), Plugin)) # TrueAdvanced: 복잡한 ABC 패턴
from abc import ABC, abstractmethod
from typing import Generic, TypeVar
T = TypeVar('T')
class Repository(ABC, Generic[T]):
@abstractmethod
def get(self, id: int) -> T:
pass
@abstractmethod
def save(self, entity: T) -> None:
pass
@abstractmethod
def delete(self, id: int) -> None:
pass
def find_all(self) -> list[T]:
"""Concrete method using abstract methods"""
return [] # Default implementation
@dataclass
class User:
id: int
name: str
class UserRepository(Repository[User]):
def __init__(self):
self._users: dict[int, User] = {}
def get(self, id: int) -> User:
return self._users.get(id)
def save(self, entity: User) -> None:
self._users[entity.id] = entity
def delete(self, id: int) -> None:
del self._users[id]
# Usage
repo = UserRepository()
repo.save(User(1, "Joel"))
user = repo.get(1)
print(user) # User(id=1, name='Joel')Lessons Learned
@abstractmethod 필수: ABC에서 추상 메서드로 정의하면, 하위 클래스에서 반드시 구현해야 합니다.
인스턴스 생성 방지: 추상 클래스는 직접 인스턴스를 생성할 수 없으며, 오직 상속된 클래스만 인스턴스를 만들 수 있습니다.
구체 메서드 포함: ABC에는 추상 메서드 외에 구체 메서드도 포함할 수 있습니다.
register 메서드: 기존 클래스를 ABC의 서브클래스로 등록하여 타입 체크를 할 수 있습니다.
Protocol과 비교: ABC는 명시적 상속이 필요하지만, Protocol은 구조적 서브타이핑(덕 타이핑)을 지원합니다.
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.