abc --- 추상 베이스 클래스

소스 코드: Lib/abc.py


이 모듈은, PEP 3119에서 설명된 대로, 파이썬에서 추상 베이스 클래스 (ABC) 를 정의하기 위한 기반 구조를 제공합니다; 이것이 왜 파이썬에 추가되었는지는 PEP를 참조하십시오. (ABC를 기반으로 하는 숫자의 형 계층 구조에 관해서는 PEP 3141numbers 모듈을 참고하십시오.)

flowdas

ABC 의 가장 중요한 목적은 isinstance() 를 통해 객체의 형을 파악하는 표준적인 방법을 제공하는 것입니다. 표준적인 절차가 필요한 이유는, 형 계층 구조가 라이브러리들이 정의하는 클래스들과 확장형들에 의해 계속 확장될 것이고, 검사하고자 하는 형을 좀 더 추상화함으로써 이런 확장이 과거 호환성 있는 형태가 되도록 만들기위함입니다. 이 작업의 일환으로 issubclass() 도 확장 가능하도록 만드는 "가상 서브 클래스" 역시 도입됩니다.

collections 모듈은 ABC로부터 파생된 몇 가지 구상(concrete) 클래스를 가지고 있습니다; 이것은 물론 더 파생될 수 있습니다. 또한, collections.abc 서브 모듈에는 클래스나 인스턴스가 특정 인터페이스를 (예를 들어, 해시 가능이거나 매핑이면) 제공하는지 검사하는 데 사용할 수 있는 ABC가 있습니다.

이 모듈은 ABC를 정의하기 위한 메타 클래스 ABCMeta와 상속을 통해 ABC를 정의하는 대안적 방법을 제공하는 도우미 클래스 ABC를 제공합니다:

class abc.ABC

ABCMeta를 메타 클래스로 가지는 도우미 클래스. 이 클래스를 사용하면, 때로 혼란스러운 메타 클래스를 사용하지 않고, 추상 베이스 클래스를 간단히 ABC에서 파생시켜서 만들 수 있습니다. 예를 들어:

from abc import ABC

class MyABC(ABC):
    pass

ABC 의 형은 여전히 ABCMeta 이므로, ABC를 상속할 때는 메타 클래스 사용에 관한 일반적인 주의가 필요한데, 다중 상속이 메타 클래스 충돌을 일으킬 수 있기 때문입니다. metaclass 키워드를 전달하고 ABCMeta를 직접 사용해서 추상 베이스 클래스를 정의할 수도 있습니다, 예를 들어:

from abc import ABCMeta

class MyABC(metaclass=ABCMeta):
    pass

flowdas

다중 상속이 메타 클래스 충돌을 일으킬 수 있다는 것은, 클래스를 정의할 때, 정의되는 클래스의 메타 클래스를 선택하는 규칙이 있고, 이 규칙을 만족하지 못하면 TypeError 를 일으킨다는 뜻입니다. 이 규칙의 자세한 내용은 적절한 메타 클래스 선택하기 를 참조하세요.

버전 3.4에 추가.

class abc.ABCMeta

추상 베이스 클래스 (ABC)를 정의하기 위한 메타 클래스.

이 메타 클래스를 사용하여 ABC를 만듭니다. ABC는 직접 서브 클래싱 될 수 있으며 믹스인 클래스의 역할을 합니다. 관련 없는 구상 클래스(심지어 내장 클래스도)와 관련 없는 ABC를 "가상 서브 클래스"로 등록 할 수 있습니다 -- 이들과 이들의 서브 클래스는 내장 issubclass() 함수에 의해 등록하는 ABC의 서브 클래스로 간주합니다. 하지만 등록하는 ABC는 그들의 MRO (메서드 결정 순서)에 나타나지 않을 것이고, 등록하는 ABC가 정의한 메서드 구현도 호출할 수 없을 것입니다 (super()를 통해서도 가능하지 않습니다). 1

flowdas

일부 ABC 는 구상 (즉 추상이 아닌) 메서드를 제공합니다. 가령 collections.abc 에서 이런 ABC 들을 다수 정의하고 있습니다. 이런 ABC 는 믹스 인(mix-in) 클래스로 볼 수 있고, 이 구상 메서드들을 믹스 인 메서드라고 부르기도 합니다.

ABCMeta를 메타 클래스로 생성된 클래스는 다음과 같은 메서드를 가집니다:

register(subclass)

이 ABC의 "가상 서브 클래스"로 subclass 를 등록합니다. 예를 들면:

from abc import ABC

class MyABC(ABC):
    pass

MyABC.register(tuple)

assert issubclass(tuple, MyABC)
assert isinstance((), MyABC)

버전 3.3에서 변경: 클래스 데코레이터로 사용할 수 있도록, 등록된 subclass 돌려줍니다.

버전 3.4에서 변경: register() 호출을 감지하려면, get_cache_token() 함수를 사용할 수 있습니다.

추상 베이스 클래스에서 다음 메서드를 재정의할 수도 있습니다:

__subclasshook__(subclass)

(반드시 클래스 메서드로 정의되어야 합니다.)

subclass 를 이 ABC의 서브 클래스로 간주할지를 검사합니다. 이것은, ABC의 서브 클래스로 취급하고 싶은 클래스마다 register()를 호출할 필요 없이, issubclass 의 행동을 더 사용자 정의할 수 있음을 의미합니다. (이 클래스 메서드는 ABC의 __subclasscheck__() 메서드에서 호출됩니다.)

이 메서드는 True, False 또는 NotImplemented 를 반환해야 합니다. True 를 반환하면, subclass 를 이 ABC의 서브 클래스로 간주합니다. False 를 반환하면, subclass 를 ABC의 서브 클래스로 간주하지 않습니다. NotImplemented 를 반환하면, 서브 클래스 검사가 일반적인 메커니즘으로 계속됩니다.

이러한 개념들의 시연으로, 이 예제 ABC 정의를 보십시오:

class Foo:
    def __getitem__(self, index):
        ...
    def __len__(self):
        ...
    def get_iterator(self):
        return iter(self)

class MyIterable(ABC):

    @abstractmethod
    def __iter__(self):
        while False:
            yield None

    def get_iterator(self):
        return self.__iter__()

    @classmethod
    def __subclasshook__(cls, C):
        if cls is MyIterable:
            if any("__iter__" in B.__dict__ for B in C.__mro__):
                return True
        return NotImplemented

MyIterable.register(Foo)

ABC MyIterable 은 추상 메서드로 __iter__() 라는 표준 이터러블 메서드를 정의합니다. 여기에 제공된 구현은 여전히 서브 클래스에서 호출 할 수 있습니다. get_iterator() 메서드 또한 MyIterable 추상 베이스 클래스의 일부이지만, 추상이 아닌 파생 클래스에서 재정의될 필요는 없습니다.

flowdas

MyIterable 이 정의하는 __iter__() 메서드는 빈 이터레이터(좀 더 구체적으로는 제너레이터)를 돌려주도록 구현되었습니다. yield 문을 삽입하기위해 while False 로 감쌉니다. 실제 서브 클래스에서는 (여전히 빈 이터레이터를 돌려줄 것이라면 그대로 써도 되지만) 자신에 맞는 __iter__() 를 재구현해야하지만, (재정의된 __iter__() 를 호출하게될) get_iterator() 메서드는 재정의할 필요없이 그대로 써도 좋다는 뜻입니다.

여기에 정의된 __subclasshook__() 클래스 메서드는 자신의 (또는 그것의 __mro__ 리스트를 통해 액세스 되는 베이스 클래스 중 하나의) __dict____iter__() 메서드를 가진 모든 클래스도 MyIterable 로 간주한다고 말합니다.

flowdas

MyIterable 의 핵심은 get_iterator() 를 제공하는 것입니다. 때문에 MyIterable 이 메서드가 제공가는 기본 구현이 동작하는데 필요한 __iter__() 가 정의되어있는지 검사하고 있습니다.

마지막으로, 마지막 줄은, Foo__iter__() 메서드를 정의하지는 않았음에도 불구하고 (이것은 __len__()__getitem__() 로 정의되는 이전 스타일의 이터러블 프로토콜을 사용합니다), MyIterable 의 가상 서브 클래스로 만듭니다. 이렇게 하면 get_iteratorFoo 의 메서드로 사용할 수 있지 않으므로, 별도로 제공됩니다.

flowdas

하지만 이터레이터를 얻는데 꼭 __iter__() 가 정의되어야 할 필요는 없고, __len__()__getitem__() 을 사용해도 됩니다. 그래서 Foo 역시 MyIterable 의 서브 클래스로 취급하기위해 MyIterable.register(Foo) 로 등록합니다. 하지만 이렇게 등록하는 경우 FooMyIterable 을 계승하지 않았기 때문에, MyIterable 의 기본 구현 get_iterator() 를 제공받지 못합니다. 때문에 Fooget_iterator() 를 직접 구현해서 사용자들이 MyIterable 에서 기대하는 get_iterator() 이 제공되도록 만듭니다.

flowdas

any("__iter__" in B.__dict__ for B in C.__mro__) 과 같은 구문은 자주 쓰이는 표현인데, hasattr(C, '__iter') 에 비해 메타 클래스나 어트리뷰트 조회에 관련된 특수 메서드와 디스크립터의 개입을 회피하는 결과를 줍니다.

abc 모듈은 다음 데코레이터도 제공합니다:

@abc.abstractmethod

추상 메서드를 나타내는 데코레이터.

이 데코레이터를 사용하려면 클래스의 메타 클래스가 ABCMeta 이거나 여기에서 파생된 것이어야 합니다. ABCMeta 에서 파생된 메타 클래스를 가진 클래스는 모든 추상 메서드와 프로퍼티가 재정의되지 않는 한 인스턴스로 만들 수 없습니다. 추상 메서드는 일반적인 'super' 호출 메커니즘을 사용하여 호출 할 수 있습니다. abstractmethod() 는 프로퍼티와 디스크립터에 대한 추상 메서드를 선언하는 데 사용될 수 있습니다.

클래스에 추상 메서드를 동적으로 추가하거나, 메서드나 클래스가 작성된 후에 추상화 상태를 수정하려고 시도하는 것은 지원되지 않습니다. abstractmethod() 는 정규 상속을 사용하여 파생된 서브 클래스에만 영향을 줍니다; ABC의 register() 메서드로 등록된 "가상 서브 클래스" 는 영향을 받지 않습니다.

abstractmethod() 가 다른 메서드 디스크립터와 함께 적용될 때, 다음 사용 예제와 같이 가장 안쪽의 데코레이터로 적용되어야 합니다:

class C(ABC):
    @abstractmethod
    def my_abstract_method(self, ...):
        ...
    @classmethod
    @abstractmethod
    def my_abstract_classmethod(cls, ...):
        ...
    @staticmethod
    @abstractmethod
    def my_abstract_staticmethod(...):
        ...

    @property
    @abstractmethod
    def my_abstract_property(self):
        ...
    @my_abstract_property.setter
    @abstractmethod
    def my_abstract_property(self, val):
        ...

    @abstractmethod
    def _get_x(self):
        ...
    @abstractmethod
    def _set_x(self, val):
        ...
    x = property(_get_x, _set_x)

추상 베이스 클래스 장치와 정확하게 상호 작용하기 위해서, 디스크립터는 __isabstractmethod__ 를 사용하여 자신을 추상으로 식별해야 합니다. 일반적으로 이 어트리뷰트는 디스크립터를 구성하는 데 사용된 메서드 중 어느 하나라도 추상이면 True 여야 합니다. 예를 들어, 파이썬의 내장 property는 다음과 동등한 일을 합니다:

class Descriptor:
    ...
    @property
    def __isabstractmethod__(self):
        return any(getattr(f, '__isabstractmethod__', False) for
                   f in (self._fget, self._fset, self._fdel))

참고

자바 추상 메서드와 달리, 이 추상 메서드는 구현을 가질 수 있습니다. 이 구현은 그것을 재정의하는 클래스에서 super() 메커니즘을 통해 호출 할 수 있습니다. 이는 협업적 다중 상속을 사용하는 프레임워크에서 super-호출의 종점으로 유용 할 수 있습니다.

abc 모듈은 다음 레거시 데코레이터도 지원합니다:

@abc.abstractclassmethod

버전 3.2에 추가.

버전 3.3부터 폐지: 이제 classmethodabstractmethod() 를 함께 사용할 수 있어서, 이 데코레이터는 필요 없습니다.

내장 classmethod() 의 서브 클래스로, 추상 classmethod를 나타냅니다. 그 외에는 abstractmethod() 와 유사합니다.

classmethod() 데코레이터가 이제 추상 메서드에 적용될 때 추상으로 정확하게 식별되기 때문에, 이 특별한 경우는 폐지되었습니다.:

class C(ABC):
    @classmethod
    @abstractmethod
    def my_abstract_classmethod(cls, ...):
        ...
@abc.abstractstaticmethod

버전 3.2에 추가.

버전 3.3부터 폐지: 이제 staticmethodabstractmethod() 를 함께 사용할 수 있어서, 이 데코레이터는 필요 없습니다.

내장 staticmethod() 의 서브 클래스로, 추상 staticmethod를 나타냅니다. 그 외에는 abstractmethod() 와 유사합니다.

staticmethod() 데코레이터가 이제 추상 메서드에 적용될 때 추상으로 정확하게 식별되기 때문에, 이 특별한 경우는 폐지되었습니다.:

class C(ABC):
    @staticmethod
    @abstractmethod
    def my_abstract_staticmethod(...):
        ...
@abc.abstractproperty

버전 3.3부터 폐지: 이제 property, property.getter(), property.setter(), property.deleter()abstractmethod() 를 함께 사용할 수 있어서, 이 데코레이터는 필요 없습니다.

내장 property() 의 서브 클래스로, 추상 property를 나타냅니다.

property() 데코레이터가 이제 추상 메서드에 적용될 때 추상으로 정확하게 식별되기 때문에, 이 특별한 경우는 폐지되었습니다.:

class C(ABC):
    @property
    @abstractmethod
    def my_abstract_property(self):
        ...

위의 예제는 읽기 전용 프로퍼티를 정의합니다; 하나나 그 이상의 하부 메서드를 추상으로 적절하게 표시하여 읽기-쓰기 추상 프로퍼티를 정의할 수도 있습니다:

class C(ABC):
    @property
    def x(self):
        ...

    @x.setter
    @abstractmethod
    def x(self, val):
        ...

일부 구성 요소만 추상인 경우, 서브 클래스에서 구상 프로퍼티를 만들기 위해서는 해당 구성 요소만 갱신하면 됩니다:

class D(C):
    @C.x.setter
    def x(self, val):
        ...

abc 모듈은 또한 다음과 같은 기능을 제공합니다 :

abc.get_cache_token()

현재의 추상 베이스 클래스 캐시 토큰을 반환합니다.

토큰은 가상 서브 클래스를 위한 추상 베이스 클래스 캐시의 현재 버전을 식별하는 (동등성 검사를 지원하는) 불투명 객체입니다. 임의의 ABC에서 ABCMeta.register() 가 호출될 때마다 토큰이 변경됩니다.

버전 3.4에 추가.

flowdas

이 함수는 issubclass() 결과를 캐싱하는 경우(가령 functools.singledispatch()), 가상 서브 클래스가 변경되었는지를 감지해서, 캐시를 무효화시키려고 할 때 사용됩니다.

각주

1

C++ 프로그래머는 파이썬의 가상 베이스 클래스 개념이 C++과 다르다는 것을 알아야 합니다.