1. 개요
abc — Abstract Base ClassesPython 표준 라이브러리에서 제공하는 추상 기저 클래스 관련 모듈이다.
2. 추상 기저 클래스
추상 기저 클래스(abstract base class, ABC)는 Python에서 추상 메서드를 이용해 클래스의 인터페이스를 만드는 방법 제공하고, 상속 관계를 직접 정의해 덕 타이핑을 지원하도록 설계된 클래스이다. Java나 C#의 인터페이스와의 공통점도 있지만 차이점도 존재한다.먼저 ABC는 추상 메서드를 이용해 클래스의 인터페이스를 정의하는 데 사용할 수 있다. 추상 메서드[abstract method]는 클래스가 일반 클래스인지 ABC인지 판별하는 기준으로, 클래스에 추상 메서드가 하나라도 존재한다면 ABC로 취급돼 인스턴스를 생성할 수 없다. 따라서 ABC를 상속받아 일반적인 클래스를 구현하려면 모든 추상 메서드를 같은 이름의 메서드로 덮어써야 한다. 반대로 일반적인 경우, 클래스가 특정 ABC의 서브클래스인지 확인함으로써, 사용할 수 있는 속성이나 메서드를 알아낼 수 있다.[1]
예를 들어
tuple
, list
, str
처럼 열(列)을 나타내는 클래스는 모듈 collections.abc
에서 정의된 ABC Sequence
를 상속받아 구현할 수 있다. 이 경우 추상 메서드 __getitem__
과 __len__
만 구현해도 앞의 기본 클래스가 가지는 여러 기능을 사용할 수 있다.#!syntax python
from collections.abc import Sequence
class MySequence(Sequence):
def __init__(self, sequence, /):
super().__init__()
self._items = tuple(sequence) # tuple에 원소를 저장
def __len__(self, /):
return self._items.__len__() # self._items의 길이
def __getitem__(self, key, /):
return self._items.__getitem__(key) # self._items의 원소
def __eq__(self, other, /):
if isinstance(other, self.__class__):
return self._items == other._items # 원소가 같으면 같다
else:
return NotImplemented
sequence0 = MySequence([0, 1, 2, 3])
sequence1 = MySequence('456')
sequence2 = MySequence((0, 1, '2', '3', 4, 5, '6', '7'))
sequence3 = MySequence([0, 1, 2, 3, 1, 1, 2, 3])
# 다음을 모두 지원하며, 반환값은 모두 True이다.
3 in sequence0
5 not in sequence1
sequence0[2] == 2
sequence1[1:3] == ('5', '6')
sequence2[1:6:2] == (1, '3', 5)
len(sequence3) == 8
min(sequence0) == 0
max(sequence1) == '6'
sequence2.index('6') == 6
sequence3.count(1) == 3
ABC는 또한 상속 관계를 정의하는 기능도 제공한다. 예를 들어 모듈
collections.abc
에서 정의된 ABC Hashable
은 해시가능한 클래스에 관한 ABC로, 이를 직접 상속받지 않았어도 클래스에 메서드 __hash__
가 None
이 아닌 값으로 정의만 돼 있다면 서브클래스로 취급한다.#!syntax python
from collections.abc import Hashable
class MyHashable:
def __hash__(self, /):
return 0
# MyHashable이 Hashable을 직접 상속받지는 않았으나,
# __hash__가 정의돼 있으므로 Hashable의 서브클래스로 취급된다.
hashable = MyHashable()
issubclass(MyHashable, Hashable) # True
isinstance(hashable, Hashable) # True
3. 구성
3.1. ABCMeta
ABCMeta
ABC의 메타클래스이다. 메타클래스는 클래스의 클래스로,
type
의 서브클래스이다. 만약 어떤 ABC를 구현하고자 한다면 해당 ABC의 메타클래스를 ABCMeta
로 설정하면 된다.#!syntax python
from abc import ABCMeta, abstractmethod
# 메서드 foo를 가지는 클래스에 관한 ABC
class HasFoo(metaclass=ABCMeta):
@abstractmethod
def foo(self):
raise NotImplementedError()
class Foo(HasFoo):
def foo(self):
print('Hello, world!')
# HasFoo는 ABC이므로 인스턴스를 생성할 수 없다
# Foo는 HasFoo를 상속받아 메서드 foo를 덮어썼으므로 인스턴스를 생성할 수 있다
foo = Foo()
foo.foo() # 'Hello, world!' 출력
3.1.1. ABCMeta.\subclasshook
@classmethod __subclasshook__(subclass)
클래스가 ABC의 서브클래스인지 판별하는 클래스 메서드이다. 사용자가 직접 정의할 수 있으며, 이를 통해 상속 관계를 사용자가 자유롭게 변형할 수 있다. 이 메서드를 구현할 때는 꼭 클래스 메서드로 구현해야 한다.
이 메서드의 반환값은
True
, False
, NotImplemented
중 하나여야 하는데, True
는 해당 클래스가 ABC의 서브클래스임을 뜻하고, False
는 그렇지 않음을 뜻하며, NotImplemented
는 일반적인 상속 관계에 따라 판별해야 하는 경우를 뜻한다.#!syntax python
from abc import ABCMeta, abstractmethod
# 메서드 foo를 가지는 클래스에 관한 ABC
class HasFoo(metaclass=ABCMeta):
# 만약 클래스가 foo를 가지고 있고, 이게 None이 아니라면
# 직접 상속받지 않았어도 HasFoo의 서브클래스로 취급한다
@classmethod
def __subclasshook__(cls, subclass):
for base in subclass.__mro__:
if 'foo' in base.__dict__:
if base.__dict__['foo'] is None:
return NotImplemented
else:
return True
return NotImplemented
@abstractmethod
def foo(self):
raise NotImplementedError()
# HasFoo를 상속받지 않았지만 foo가 구현된 클래스
class Foo:
def foo(self):
print('Hello, world!')
foo = Foo()
issubclass(Foo, HasFoo) # True
isinstance(foo, HasFoo) # True
3.1.2. ABCMeta.register
register(subclass)
특정 클래스를 ABC의 서브클래스로 등록하는 메서드이다. 기본 내장 자료형처럼 이미 구현된 클래스를 ABC의 서브클래스로 만들고 싶을 때 사용한다. 예를 들어,
int
는 모듈 numbers
에서 정의된 ABC Integral
을 직접 상속받지 않았지만 이의 서브클래스로 취급되는데, 이와 같은 경우에 사용할 수 있다. 이렇게 추가된 서브클래스를 가상 서브클래스[virtual subclass]라고 한다.#!syntax python
from abc import ABCMeta, abstractmethod
# HasFoo가 구현되기 전에 구현된 클래스
class Foo:
def foo(self):
print('Hello, world!')
# 메서드 foo를 가지는 클래스에 관한 ABC
class HasFoo(metaclass=ABCMeta):
@abstractmethod
def foo(self):
raise NotImplementedError()
HasFoo.register(Foo) # Foo를 HasFoo의 서브클래스로 등록
foo = Foo()
issubclass(Foo, HasFoo) # True
isinstance(foo, HasFoo) # True
3.2. ABC
ABC
상속만으로 편하게 추상 ABC를 구현할 수 있도록 하는 ABC이다.
ABCMeta
를 이용해 ABC를 구현하려면 메타클래스를 설정해야 하지만, 단순히 ABC
를 상속받는 방법으로도 ABC를 구현할 수 있다.#!syntax python
from abc import ABC, abstractmethod
# 메서드 foo를 가지는 클래스에 관한 ABC
# ABC를 상속받아 구현
class HasFoo(ABC):
@abstractmethod
def foo(self):
raise NotImplementedError()
class Foo(HasFoo):
def foo(self):
print('Hello, world!')
3.3. abstractmethod
@abstractmethod
추상 메서드를 생성하는 데커레이터(decorator)이다. 추상 메서드를 하나라도 가지는 클래스는 ABC로 취급된다.
classmethod
등의 다른 데커레이터와 사용하는 경우에는 무조건 abstractmethod
가 가장 안쪽에 위치해야 한다.#!syntax python
from abc import ABCMeta, abstractmethod
class HasFoo(metaclass=ABCMeta):
@abstractmethod
def foo(self):
raise NotImplementedError()
# 다른 데커레이터와 사용하는 경우에는
# abstractmethod가 가장 안쪽에 위치해야 한다
@classmethod
@abstractmethod
def class_foo(cls):
raise NotImplementedError()
@staticmethod
@abstractmethod
def static_foo():
raise NotImplementedError()
@property
@abstractmethod
def property_foo(self):
raise NotImplementedError()
3.4. get_cache_token
get_cache_token()
함수
ABCMeta.register
로 등록된 가상 서브클래스 용 캐시를 식별하기 위한 토큰을 반환하는 함수이다.3.5. update_abstractmethods
update_abstractmethods(cls)
클래스의 추상 메서드 현황을 다시 계산하는 함수이다. 클래스 생성 후 데커레이터 등의 방법을 통해 클래스의 추상 메서드가 구현됐거나 변경된 경우에 이 함수를 호출해야 한다. 이를 호출하지 않는다면 클래스의 추상 메서드가 클래스 생성 이후 모두 구현되더라도 인스턴스 생성이 불가능할 수도 있다.
3.6. 기타
@abstractclassmethod
@abstractstaticmethod
@abstractproperty
과거에 데커레이터
abstractmethod
가 다른 데커레이터와 함께 사용할 수 없었을 때 사용했던 데커레이터이다. 현재는 다른 데터레이터와 함께 사용할 수 있으므로 사용하지 않는 것을 권장한다.[1] 후술할 메서드
ABCMeta.register
를 통해 필요한 기능을 구현하지 않은 클래스를 추상 기저 클래스의 서브클래스로 등록할 수 있지만, 권장되지는 않는다.