abc는 Python 2, 3 모두에서 사용할 수 있는 파이썬 표준 라이브러리 중 하나다. abc는 Abstract Base Class의 약자인데, 말 그대로 객체지향에서의 abstract
개념을 파이썬에서 잘 사용할 수 있도록 도와준다. PEP 3119에서 2007년 4월에 최초 제안되었다.
OOP를 내세우는 언어들은 보통 추상 메소드가 있다. 나는 Java가 편하므로 Java를 예로 들겠다.
interface Hashable {
abstract public int hash();
}
class Number implements Hashable {
@Override
public int hash() {
return ...;
}
}
abstract
가 명시된 추상 메소드를 가진 추상 클래스, 혹은 인터페이스를 상속받은 일반 클래스는, 해당 추상 메소드를 모두 override하지 않으면 에러가 발생한다. 객체지향 5원칙인 SOLID 중 L인 리스코브 치환 원칙(LSP, Liskov Substitution Principle)
에게 필요한 개념이다 뭐다 이런 이야기들을 하는데, 구체적인 것들은 이 글의 주제를 벗어나므로 직접 알아보기 바란다.
Python에는 abstract
키워드와 같은 언어 레벨의 추상 메소드 표현법이 따로 없다. abc가 세상에 널리 알려지기 전까지는, 추상 메소드를 표현하기 위해 메소드의 body에 raise NotImplementedError
를 두는 방법을 사용하곤 했다.
class Abstract:
def foo(self):
raise NotImplementedError()
대표적인 old-school way이며, abc를 사용하는 것에 비해 허점이 많다.
기본적으로, NotImplementedError
를 사용하는 방식은 엄격하지 않다. 추상 메소드를 구현하지 않더라도, 메소드를 실제로 호출하기 전까지는 알아차릴 수 없다. 추상 메소드와 관련된 에러를 raise하는 시점이 런타임이라는 것이다.
만약 인스턴스화 시점, 클래스 정의 시점, 컴파일 타임 등 이보다 더 상위의 시점에 추상 메소드의 구현 여부를 확인할 수 있다면 더 좋을 것이다. abc.ABC
를 상속받고, 원하는 메소드에 abc.abstractmethod
데코레이터를 달아주면 된다.
from abc import ABC, abstractmethod
class Abstract(ABC):
@abstractmethod
def foo(self):
pass
class WithOverride(Abstract):
def foo(self):
pass
class WithoutOverride(Abstract):
pass
WithOverride()
WithoutOverride()
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: Can't instantiate abstract class WithoutOverride with abstract methods foo
또 다른 표준 라이브러리인 collections의 서브 모듈 collections.abc에서 abc를 잘 쓰고 있다. 이 모듈은 어떤 클래스가 특정 기능을 지원하는지 여부를 테스트하는 데 써먹을 수 있는 추상 클래스를 제공한다. hashable, callable 여부 등을 예로 들 수 있다.
from collections.abc import Container, Hashable, Sized, Callable, MutableSequence
print(isinstance([], Container)) # True
print(isinstance([], Hashable)) # False
print(isinstance(0, Hashable)) # True
print(isinstance(0, Sized)) # False
print(isinstance('', Sized)) # True
print(isinstance(lambda: 0, Callable)) # True
print(isinstance([], MutableSequence)) # True
print(isinstance(set(), MutableSequence)) # False
이런저런 복잡한 것들을 제외하고 쉽게 왜곡해 설명하면, 메타클래스로 abc.ABCMeta
를 상속받은 클래스를 비교 대상으로 하여 isinstance
호출 시 __subclasshook__
메소드가 그 결과를 결정하게 된다. 예제로 살펴보자.
from abc import ABCMeta
class Object(metaclass=ABCMeta):
@classmethod
def __subclasshook__(cls, C):
return True
print(isinstance(0, Object)) # True