Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 3119 – 추상 베이스 클래스 소개

Author:
Guido van Rossum <guido at python.org>, Talin <viridia at gmail.com>
Status:
Final
Type:
Standards Track
Created:
18-Apr-2007
Python-Version:
3.0
Post-History:
26-Apr-2007, 11-May-2007

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

Python에 추상 베이스 클래스(ABC) 지원을 추가하기 위한 제안입니다. 3000. 다음을 제안합니다:

  • isinstance()issubclass()를 오버로드하는 방법입니다.
  • “ABC 지원 프레임워크” 역할을 하는 새로운 abc 모듈입니다. ABC에 사용할 메타클래스와 추상 메서드를 정의하는 데 사용할 수 있는 데코레이터를 정의합니다.
  • 컨테이너와 이터레이터를 위한 특정 추상 베이스 클래스를 collections 모듈에 추가합니다.

이 제안에 담긴 사고의 상당 부분은 인터페이스나 제너릭 함수(GF)와 대비되는 ABC의 구체적인 메커니즘에 관한 것이 아니라, “무엇이 집합을 만드는가”, “무엇이 매핑을 만드는가”, “무엇이 시퀀스를 만드는가”와 같은 철학적 문제를 명확히 하는 데 관한 것입니다.

숫자형을 위한 ABC를 정의하는 동반 문서 PEP 3141도 있습니다.

감사의 말

Talin은 아래의 근거 [1]와 ABC와 인터페이스를 비교하는 절의 대부분을 작성했습니다. 그것만으로도 그는 공동 저자로 인정받을 자격이 있습니다. 이 PEP의 나머지 부분에서는 첫 번째 저자를 가리킬 때 “I”를 사용합니다.

근거

객체 지향 프로그래밍 영역에서 객체와 상호 작용하는 사용 패턴은 ‘호출’과 ‘검사’라는 두 가지 기본 범주로 나눌 수 있습니다.

호출은 객체의 메서드를 호출하여 객체와 상호 작용하는 것을 의미합니다. 일반적으로 이는 다형성과 결합되므로, 주어진 메서드를 호출하면 객체의 형식에 따라 서로 다른 코드가 실행될 수 있습니다.

검사는 외부 코드(객체의 메서드 외부에 있는 코드)가 해당 객체의 형식이나 속성을 살펴보고, 그 정보를 바탕으로 객체를 어떻게 처리할지 결정할 수 있는 능력을 의미합니다.

두 사용 패턴은 모두 다양하고 잠재적으로 새로운 객체의 처리를 일관된 방식으로 지원하면서도, 각기 다른 객체 형식에 맞게 처리 결정을 사용자 지정할 수 있게 한다는 동일한 일반 목적을 수행합니다.

고전적인 OOP 이론에서는 호출이 선호되는 사용 패턴이며, 검사는 초기의 절차적 프로그래밍 방식의 잔재로 간주되어 적극적으로 권장되지 않습니다. 그러나 실제로 이러한 관점은 지나치게 독단적이고 융통성이 없으며, Python과 같은 언어의 동적인 특성과 매우 상충하는 일종의 설계 경직성을 초래합니다.

특히 객체 클래스의 작성자가 예상하지 못한 방식으로 객체를 처리해야 하는 경우가 흔히 있습니다. 모든 가능한 객체 사용자의 요구를 충족하는 메서드를 모든 객체에 내장하는 것이 항상 최선의 해결책은 아닙니다. 더욱이 동작을 객체 내부에 엄격히 캡슐화해야 한다는 고전적 OOP의 요구 사항과 정면으로 대조되는 강력한 디스패치 철학이 많이 있으며, 규칙 또는 패턴 매칭 기반 논리가 그 예입니다.

반면 고전적 OOP 이론가들이 검사를 비판하는 이유 중 하나는 형식화가 부족하고 검사 대상이 임의적으로 정해진다는 점입니다. 객체의 거의 모든 측면을 외부 코드에서 반영하고 직접 접근할 수 있는 Python과 같은 언어에서는 객체가 특정 프로토콜을 준수하는지 검사하는 방법이 매우 다양합니다. 예를 들어 ‘이 객체가 변경 가능한 시퀀스 컨테이너인가?’라고 묻는 경우, ‘list’의 베이스 클래스를 찾거나 ‘__getitem__’이라는 이름의 메서드를 찾을 수 있습니다. 그러나 이러한 검사가 명백해 보일 수는 있어도 둘 다 올바르지 않다는 점에 유의하십시오. 하나는 거짓 음성을 생성하고 다른 하나는 거짓 양성을 생성하기 때문입니다.

일반적으로 합의된 해결책은 검사를 표준화하고 이를 형식적인 체계로 묶는 것입니다. 이는 각 클래스에 상속 메커니즘이나 다른 수단을 통해 표준적으로 검사할 수 있는 속성 집합을 연관시키는 방식으로 가장 쉽게 수행할 수 있습니다. 각 검사에는 일련의 약속이 수반됩니다. 즉, 클래스의 일반적인 동작에 대한 약속과 사용할 수 있는 다른 클래스 메서드에 대한 약속을 포함합니다.

이 PEP는 추상 베이스 클래스(Abstract Base Classes), 즉 ABC로 알려진 이러한 테스트를 구성하기 위한 특정 전략을 제안합니다. ABC는 외부 검사자에게 해당 객체의 특정 기능을 알리기 위해 객체의 상속 트리에 추가되는 단순한 Python 클래스입니다. 테스트는 isinstance()를 사용하여 수행하며, 특정 ABC가 존재한다는 것은 테스트를 통과했음을 의미합니다.

또한 ABC는 해당 타입의 특징적인 동작을 확립하는 최소한의 메서드 집합을 정의합니다. 객체를 ABC 타입에 따라 구분하는 코드는 이러한 메서드가 항상 존재한다고 신뢰할 수 있습니다. 이러한 각 메서드에는 ABC의 문서에 설명된 일반화된 추상 의미 정의가 수반됩니다. 이러한 표준 의미 정의는 강제되지는 않지만 강력히 권장됩니다.

Python의 다른 모든 것과 마찬가지로 이러한 약속은 우호적인 합의의 성격을 띠며, 이 경우에는 언어가 ABC에서 이루어진 약속 중 일부를 강제하더라도 나머지 약속을 지키는 것은 구체 클래스의 구현자에게 달려 있다는 의미입니다.

명세

명세는 초록에 나열된 범주를 따릅니다.

  • isinstance()issubclass()를 오버로드하는 방법입니다.
  • “ABC 지원 프레임워크” 역할을 하는 새로운 모듈 abc입니다. 이 모듈은 ABC와 함께 사용할 메타클래스와 추상 메서드를 정의하는 데 사용할 수 있는 데코레이터를 정의합니다.
  • 컨테이너와 이터레이터를 위한 특정 ABC로, collections 모듈에 추가됩니다.

isinstance()issubclass()의 오버로딩

이 PEP와 그 동반자인 PEP 3141을 개발하는 동안, 더 많고 세분화된 ABC를 표준화할지 아니면 더 적고 포괄적인 ABC를 표준화할지를 두고 반복해서 선택에 직면했습니다. 예를 들어 한때 PEP 3141은 복소수에 사용되는 다음과 같은 베이스 클래스 계층을 도입했습니다. MonoidUnderPlus, AdditiveGroup, Ring, Field, Complex이며, 각각 이전 클래스에서 파생됩니다. 또한 논의에서는 제외된 몇 가지 다른 대수적 분류도 언급했습니다. Algebraic, Transcendental, IntegralDomain 및 PrincipalIdealDomain입니다. 현재 PEP의 초기 버전에서는 Set, ComposableSet, MutableSet, HashableSet, MutableComposableSet, HashableComposableSet와 같은 별도 클래스의 사용 사례를 고려했습니다.

여기서의 딜레마는 ABC를 더 적게 두는 편을 선호하지만, 덜 세분화된 ABC가 필요한 사용자는 어떻게 해야 하느냐는 점입니다. 예를 들어, 자신만의 초월수 종류를 정의하면서도 float와 int를 초월수로 간주하고자 하는 수학자의 곤경을 생각해 보십시오. PEP 3141은 원래 이를 위해 float.__bases__를 패치할 것을 제안했지만, 내장 타입을 불변으로 유지해야 할 타당한 이유가 몇 가지 있습니다(그중 하나는 mod_python [16]에서처럼 동일한 주소 공간에서 실행되는 모든 Python 인터프리터가 내장 타입을 공유한다는 점입니다).

또 다른 예로 append() 메서드를 가진 모든 시퀀스를 위한 제네릭 함수(PEP 3124)를 정의하려는 사람이 있을 수 있습니다. Sequence ABC는 append() 메서드를 보장하지 않는 반면, MutableSequenceappend()뿐만 아니라 다양한 다른 변경 메서드도 요구합니다.

이러한 딜레마와 유사한 문제를 해결하기 위해 다음 절에서는 ABC를 다른 ABC를 포함한 모든 클래스에 “가상 베이스 클래스”(C++의 동일한 개념이 아님)로 추가할 수 있게 하는 메타클래스를 제안합니다. 이를 통해 표준 라이브러리는 ABC SequenceMutableSequence를 정의하고 이를 basestring, tuple, list와 같은 내장 타입의 가상 베이스 클래스로 등록할 수 있으며, 그 결과 예를 들어 다음 조건이 모두 참이 됩니다.:

isinstance([], Sequence)
issubclass(list, Sequence)
issubclass(list, MutableSequence)
isinstance((), Sequence)
not issubclass(tuple, MutableSequence)
isinstance("", Sequence)
issubclass(bytearray, MutableSequence)

여기서 제안하는 주요 메커니즘은 내장 함수 isinstance()issubclass()를 오버로드할 수 있게 하는 것입니다. 오버로딩은 다음과 같이 작동합니다. isinstance(x, C) 호출은 먼저 C.__instancecheck__가 존재하는지 확인하고, 존재하면 정상 구현 대신 C.__instancecheck__(x)를 호출합니다. 마찬가지로 issubclass(D, C) 호출은 먼저 C.__subclasscheck__가 존재하는지 확인하고, 존재하면 정상 구현 대신 C.__subclasscheck__(D)를 호출합니다.

매직 이름은 __isinstance____issubclass__가 아니라는 점에 유의하십시오. 인자의 순서가 뒤바뀌면 혼동을 일으킬 수 있기 때문이며, 특히 issubclass() 오버로더에서 그렇습니다.

이에 대한 프로토타입 구현이 [12]에 제시되어 있습니다.

다음은 __instancecheck____subclasscheck__를 (순진할 정도로 단순하게) 구현한 예입니다.:

class ABCMeta(type):

    def __instancecheck__(cls, inst):
        """Implement isinstance(inst, cls)."""
        return any(cls.__subclasscheck__(c)
                   for c in {type(inst), inst.__class__})

    def __subclasscheck__(cls, sub):
        """Implement issubclass(sub, cls)."""
        candidates = cls.__dict__.get("__subclass__", set()) | {cls}
        return any(c in candidates for c in sub.mro())

class Sequence(metaclass=ABCMeta):
    __subclass__ = {list, tuple}

assert issubclass(list, Sequence)
assert issubclass(tuple, Sequence)

class AppendableSequence(Sequence):
    __subclass__ = {list}

assert issubclass(list, AppendableSequence)
assert isinstance([], AppendableSequence)

assert not issubclass(tuple, AppendableSequence)
assert not isinstance((), AppendableSequence)

다음 절에서는 완전한 기능을 갖춘 구현을 제안합니다.

abc 모듈: ABC 지원 프레임워크

순수 Python으로 작성된 새로운 표준 라이브러리 모듈 abc는 ABC 지원 프레임워크로 사용됩니다. 메타클래스 ABCMeta와 데코레이터 @abstractmethod@abstractproperty를 정의합니다. 샘플 구현은 [13]에서 제시합니다.

ABCMeta 클래스는 __instancecheck____subclasscheck__를 재정의하고 register 메서드를 정의합니다. register 메서드는 클래스여야 하는 하나의 인자를 받습니다. B.register(C)를 호출한 후에는 B.__subclasscheck__(C)가 True를 반환하므로 issubclass(C, B) 호출이 True를 반환합니다. 또한 isinstance(x, B)issubclass(x.__class__, B) 또는 issubclass(type(x), B)입니다. (\ type(x)도 가능하며 x.__class__가 동일한 객체가 아닐 수도 있습니다.)

이러한 메서드는 메타클래스가 ABCMeta에서 파생된 클래스에서 호출하도록 의도되었습니다. 예를 들어:

from abc import ABCMeta

class MyABC(metaclass=ABCMeta):
    pass

MyABC.register(tuple)

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

마지막 두 assert는 다음 두 assert와 동등합니다.:

assert MyABC.__subclasscheck__(tuple)
assert MyABC.__instancecheck__(())

물론 MyABC를 직접 서브클래스화할 수도 있습니다.:

class MyClass(MyABC):
    pass

assert issubclass(MyClass, MyABC)
assert isinstance(MyClass(), MyABC)

물론 튜플은 MyClass가 아닙니다.:

assert not issubclass(tuple, MyClass)
assert not isinstance((), MyClass)

다른 클래스를 MyClass의 서브클래스로 등록할 수 있습니다.:

MyClass.register(list)

assert issubclass(list, MyClass)
assert issubclass(list, MyABC)

다른 ABC도 등록할 수 있습니다.:

class AnotherClass(metaclass=ABCMeta):
    pass

AnotherClass.register(basestring)

MyClass.register(AnotherClass)

assert isinstance(str, MyABC)

마지막 assert를 확인하려면 다음 슈퍼클래스-서브클래스 관계를 추적해야 합니다.:

MyABC -> MyClass (using regular subclassing)
MyClass -> AnotherClass (using registration)
AnotherClass -> basestring (using registration)
basestring -> str (using regular subclassing)

abc 모듈은 추상 메서드를 선언하는 데 사용할 새로운 데코레이터 @abstractmethod도 정의합니다. 이 데코레이터로 선언된 메서드 중 아직 재정의되지 않은 것이 하나라도 포함된 클래스는 인스턴스화할 수 없습니다. 이러한 메서드는 서브클래스의 재정의 메서드에서 super를 사용하거나 직접 호출할 수 있습니다. 예를 들어:

from abc import ABCMeta, abstractmethod

class A(metaclass=ABCMeta):
    @abstractmethod
    def foo(self): pass

A()  # raises TypeError

class B(A):
    pass

B()  # raises TypeError

class C(A):
    def foo(self): print(42)

C()  # works

참고: @abstractmethod 데코레이터는 클래스 본문 내부에서만 사용해야 하며, 메타클래스가 ABCMeta에서 파생된 클래스에서만 사용해야 합니다. 클래스에 추상 메서드를 동적으로 추가하거나, 메서드 또는 클래스가 생성된 후 그 추상화 상태를 수정하려는 시도는 지원되지 않습니다. @abstractmethod는 일반 상속을 사용하여 파생된 서브클래스에만 영향을 미치며, register() 메서드로 등록된 “가상 서브클래스”에는 영향을 미치지 않습니다.

구현: @abstractmethod 데코레이터는 함수 속성 __isabstractmethod__True 값으로 설정합니다. ABCMeta.__new__ 메서드는 __isabstractmethod__ 속성을 가지며 그 값이 참인 모든 메서드 이름의 집합으로 타입 속성 __abstractmethods__를 계산합니다. 이는 베이스 클래스의 __abstractmethods__ 속성을 결합하고, __isabstractmethod__ 속성이 참인 새 클래스 딕셔너리의 모든 메서드 이름을 추가하며, __isabstractmethod__ 속성이 참이 아닌 새 클래스 딕셔너리의 모든 메서드 이름을 제거하여 수행합니다. 그 결과로 얻은 __abstractmethods__ 집합이 비어 있지 않으면 해당 클래스는 추상 클래스로 간주되며, 이를 인스턴스화하려고 하면 TypeError를 발생시킵니다. (이것이 CPython에서 구현되었다면 내부 플래그 Py_TPFLAGS_ABSTRACT를 사용하여 이 검사를 빠르게 수행할 수 있습니다 [6],.)

논의: Java의 추상 메서드나 C++의 순수 추상 메서드와 달리, 여기에서 정의한 추상 메서드는 구현을 가질 수 있습니다. 이 구현은 해당 메서드를 재정의하는 클래스에서 super 메커니즘을 통해 호출할 수 있습니다. 이는 협력적 다중 상속을 사용하는 프레임워크에서 super 호출의 종점으로 유용할 수 있습니다 [7], [8].

두 번째 데코레이터인 @abstractproperty는 추상 데이터 속성을 정의하기 위해 정의됩니다. 이 구현은 내장 property의 서브클래스로, __isabstractmethod__ 속성을 추가합니다.:

class abstractproperty(property):
    __isabstractmethod__ = True

두 가지 방식으로 사용할 수 있습니다.:

class C(metaclass=ABCMeta):

    # A read-only property:

    @abstractproperty
    def readonly(self):
        return self.__x

    # A read-write property (cannot use decorator syntax):

    def getx(self):
        return self.__x
    def setx(self, value):
        self.__x = value
    x = abstractproperty(getx, setx)

추상 메서드와 마찬가지로, 추상 프로퍼티(데코레이터 구문이나 더 긴 형식을 사용하여 선언됨)를 상속하는 서브클래스는 해당 추상 프로퍼티를 구체적인 프로퍼티로 재정의하지 않으면 인스턴스화할 수 없습니다.

컨테이너와 이터레이터를 위한 ABC

collections 모듈은 집합, 매핑, 시퀀스와 이터레이터 및 딕셔너리 뷰와 같은 일부 도우미 타입을 다루는 데 필요한 동시에 충분한 ABC를 정의합니다. 모든 ABC는 위에서 언급한 ABCMeta를 메타클래스로 갖습니다.

ABC는 기술적으로 유효하지만 상당히 쓸모없는 추상 메서드 구현을 제공합니다. 예를 들어, __hash__는 0을 반환하고 __iter__는 빈 이터레이터를 반환합니다. 일반적으로 추상 메서드는 지정된 타입의 빈 컨테이너 동작을 나타냅니다.

일부 ABC는 구체적인(즉, 추상이 아닌) 메서드도 제공합니다. 예를 들어, Iterator 클래스에는 자신을 반환하는 __iter__ 메서드가 있으며, 이는 이터레이터의 중요한 불변 조건을 충족합니다(이 조건은 Python 2에서는 각 이터레이터 클래스가 새로 구현해야 합니다). 이러한 ABC는 “믹스인” 클래스로 간주할 수 있습니다.

PEP에서 정의된 ABC 중 __init__, __new__, __str__ 또는 __repr__를 재정의하는 것은 없습니다. 표준 생성자 시그니처를 정의하면 사용자 지정 컨테이너 타입, 예를 들어 Patricia 트리나 gdbm 파일이 불필요하게 제한됩니다. 컬렉션의 특정 문자열 표현을 정의하는 것 역시 개별 구현에 맡겨집니다.

참고: 정렬 연산(__lt__, __le__, __ge__, __gt__)을 위한 ABC는 없습니다. 추상 여부와 관계없이 이를 베이스 클래스에 정의하면 두 번째 피연산자에 허용되는 타입과 관련한 문제가 발생합니다. 예를 들어, Ordering 클래스가 __lt__를 정의한다면, 모든 Ordering 인스턴스 xy에 대해 x < y가 정의되어 있을 것이라고 가정하게 됩니다(부분 순서만 정의하더라도 그렇습니다). 그러나 이는 성립할 수 없습니다. liststr가 모두 Ordering에서 파생된다면, 이는 [1, 2] < (1, 2)가 정의되어야 하며(아마 False를 반환해야 하며) 실제로는 (Python 3000에서는!) 이러한 “혼합 모드 비교” 연산이 명시적으로 금지되어 TypeError를 발생시킨다는 것을 의미합니다. 자세한 내용은 PEP 3100[14]를 참조하십시오. (이는 다른 동일한 타입의 인자를 받는 연산과 관련된 더 일반적인 문제의 특수한 경우입니다.)

한 가지 재주만 부리는 조랑말

이러한 추상 클래스는 __iter__ 또는 __len__과 같은 단일 메서드를 나타냅니다.

Hashable
__hash__를 정의하는 클래스의 베이스 클래스입니다. __hash__ 메서드는 정수를 반환해야 합니다. 추상 __hash__ 메서드는 항상 0을 반환하며, 이는 유효하지만(비효율적이기는 해도) 구현입니다. 불변 조건: C1C2 클래스가 모두 Hashable에서 파생되는 경우, 모든 C1의 인스턴스 o1 및 모든 C2의 인스턴스 o2에 대해 o1 == o2라는 조건은 hash(o1) == hash(o2)를 의미해야 합니다. 즉, 해시 값이 서로 다른 두 객체가 절대로 동일하다고 비교되어서는 안 됩니다.

또 다른 제약은 해시 가능 객체가 생성된 후에는 ==로 비교한 값이나 해시 값을 절대로 변경해서는 안 된다는 것입니다. 클래스가 이를 보장할 수 없다면 Hashable에서 파생되어서는 안 됩니다. 특정 인스턴스에 대해 이를 보장할 수 없다면 해당 인스턴스의 __hash__TypeError 예외를 발생시켜야 합니다.

참고: 이 클래스의 인스턴스라는 사실이 객체가 불변임을 의미하지는 않습니다. 예를 들어, 멤버로 리스트를 포함하는 튜플은 불변이 아니며, 해당 __hash__ 메서드는 TypeError를 발생시킵니다. (이는 각 멤버의 해시를 재귀적으로 계산하려고 시도하기 때문입니다. 멤버가 해시 불가능하면 TypeError를 발생시킵니다.)

Iterable
__iter__를 정의하는 클래스의 베이스 클래스입니다. __iter__ 메서드는 항상 Iterator의 인스턴스를 반환해야 합니다(아래 참조). 추상 __iter__ 메서드는 빈 이터레이터를 반환합니다.
Iterator
__next__를 정의하는 클래스의 기본 클래스입니다. 이 클래스는 Iterable에서 파생됩니다. 추상 __next__메서드는 StopIteration을 발생시킵니다. 구체적인 __iter__메서드는 self를 반환합니다. IterableIterator의 차이에 유의하십시오. Iterable은 반복할 수 있으며, 즉 __iter__ 메서드를 지원합니다. Iterator는 내장 함수 iter()가 반환하는 것으로, 즉 __next__ 메서드를 지원합니다.
Sized
__len__을 정의하는 클래스의 기본 클래스입니다. __len__메서드는 0 이상인 Integer를 반환해야 합니다(아래의 “Numbers”를 참조하십시오). 추상 __len__메서드는 0을 반환합니다. Invariant: 클래스 CSizedIterable모두에서 파생되는 경우, C의 모든 인스턴스 c에 대해 sum(1 for x in c) == len(c)라는 불변 조건이 성립해야 합니다.
Container
__contains__를 정의하는 클래스의 기본 클래스입니다. __contains__메서드는 bool을 반환해야 합니다. 추상 __contains__메서드는 False를 반환합니다. Invariant: 클래스 CContainerIterable모두에서 파생되는 경우, C의 모든 인스턴스 c에 대해 (x in c for x in c)는 True 값만 생성하는 제너레이터여야 합니다.

Open issues: ABCMeta 메타클래스를 사용하는 대신, 이 클래스들이 해당 특수 메서드의 존재 여부를 확인하도록 __instancecheck____subclasscheck__를 재정의할 수도 있습니다. 예를 들면 다음과 같습니다.:

class Sized(metaclass=ABCMeta):
    @abstractmethod
    def __hash__(self):
        return 0
    @classmethod
    def __instancecheck__(cls, x):
        return hasattr(x, "__len__")
    @classmethod
    def __subclasscheck__(cls, C):
        return hasattr(C, "__bases__") and hasattr(C, "__len__")

이렇게 하면 명시적인 등록이 필요하지 않다는 장점이 있습니다. 그러나 인스턴스 특성과 클래스 특성의 혼동되는 의미론, 그리고 클래스가 해당 메타클래스의 인스턴스라는 점 때문에 의미론을 정확히 구현하기가 어렵습니다. __bases__에 대한 검사는 원하는 의미론의 근사치일 뿐입니다. Strawman: 그렇게 하되 등록 API도 작동하도록 구성합시다.

집합

이러한 추상 클래스는 읽기 전용 집합과 변경 가능한 집합을 나타냅니다. 가장 기본적인 집합 연산은 멤버십 테스트이며, x in s로 표기하고 s.__contains__(x)로 구현합니다. 이 연산은 위에서 정의한 Container 클래스에 이미 정의되어 있습니다. 따라서 특정 수학적 집합론의 불변 조건이 성립하는, 크기가 정해지고 반복 가능한 컨테이너를 집합으로 정의합니다.

내장 타입 setMutableSet을 상속합니다. 내장 타입 frozensetSetHashable을 상속합니다.

Set
이는 크기가 정해지고 반복 가능한 컨테이너, 즉 Sized, IterableContainer를 상속한 서브클래스입니다. 그렇다고 이 세 클래스의 모든 서브클래스가 집합인 것은 아닙니다! 집합에는 반복을 통해 확인할 수 있듯이 각 원소가 한 번만 나타난다는 추가 불변 조건이 있으며, 또한 집합은 부등식 연산을 부분집합/상위집합 테스트로 구현하는 구체적인 연산자를 정의합니다. 일반적으로 수학에서 유한 집합에 대해 성립하는 불변 조건이 성립합니다. [11]

구현이 서로 다른 집합은 유한 집합에 대한 부분집합/상위집합 연산의 수학적 정의를 사용하여 안전하고, (대개) 효율적이며 정확하게 비교할 수 있습니다. 순서 연산에는 구체적인 구현이 있으며, 서브클래스는 속도를 위해 이를 재정의할 수 있지만 의미론은 유지해야 합니다. SetSized에서 파생되므로, 길이가 서로 다른 두 집합을 비교할 때 __eq__은 지름길을 사용하여 즉시 False를 반환할 수 있습니다. 마찬가지로 첫 번째 집합이 두 번째 집합보다 더 많은 멤버를 가지면 __le__은 즉시 False를 반환할 수 있습니다. 집합 포함은 부분 순서만 구현한다는 점에 유의하십시오. 예를 들어 {1, 2}{1, 3}은 순서가 정해지지 않습니다(이 인자에 대해 <, ==> 세 연산 모두 False를 반환합니다). 집합은 매핑이나 시퀀스와 관련하여 순서를 정할 수 없지만, 동등성에 대해서는 이들과 비교할 수 있으며, 이 경우 항상 서로 같지 않은 것으로 비교됩니다.

이 클래스는 합집합, 교집합, 대칭 차집합 및 비대칭 차집합을 계산하는 구체적인 연산자도 정의하며, 각각 __or__, __and__, __xor____sub__입니다. 이러한 연산자는 Set의 인스턴스를 반환해야 합니다. 기본 구현은 반복 가능한 인자를 사용하여 재정의 가능한 클래스 메서드 _from_iterable()를 호출합니다. 이 팩토리 메서드의 기본 구현은 frozenset 인스턴스를 반환하며, 다른 적절한 Set 서브클래스를 반환하도록 재정의할 수 있습니다.

마지막으로 이 클래스는 요소에서 해시 값을 계산하는 구체적인 메서드 _hash를 정의합니다. 해시 가능한 Set의 서브클래스는 _hash를 호출하여 __hash__를 구현하거나 동일한 알고리즘을 더 효율적으로 다시 구현할 수 있지만, 구현되는 알고리즘은 동일해야 합니다. 현재 이 알고리즘은 소스 코드 [15] 에 의해서만 완전히 명세화되어 있습니다.

Note: Python 2의 set 타입에서 발견되는 issubsetissuperset메서드는 지원되지 않습니다. 이러한 메서드는 대부분 __le____ge__의 별칭일 뿐이기 때문입니다.

MutableSet
이는 요소를 추가하고 제거하는 추가 연산을 구현하는 Set의 서브클래스입니다. 지원되는 메서드는 Python 2의 set 타입에서 알려진 의미론을 따릅니다(discard는 Java를 본뜬다는 점은 제외합니다).
.add(x)
요소 x가 집합에 아직 없으면 이를 추가하는 bool을 반환하는 추상 메서드입니다. x가 추가되었으면 True를, 이미 있었다면 False를 반환해야 합니다. 추상 구현은 NotImplementedError를 발생시킵니다.
.discard(x)
요소 x가 존재하면 이를 제거하는 bool을 반환하는 추상 메서드입니다. 요소가 존재했으면 True를, 존재하지 않았으면 False를 반환해야 합니다. 추상 구현은 NotImplementedError를 발생시킵니다.
.pop()
임의의 항목을 제거하고 반환하는 구체적인 메서드입니다. 집합이 비어 있으면 KeyError를 발생시킵니다. 기본 구현은 집합의 이터레이터가 반환하는 첫 번째 항목을 제거합니다.
.toggle(x)
x가 집합에 없으면 추가하고, 있으면 제거하는 bool을 반환하는 구체적인 메서드입니다. x가 추가되었으면 True를, 제거되었으면 False를 반환해야 합니다.
.clear()
집합을 비우는 구체 메서드입니다. 기본 구현은 KeyError가 잡힐 때까지 self.pop()을 반복해서 호출합니다. (참고: 구현이 더 빠른 방식으로 이를 재정의하더라도 새 집합을 단순히 만드는 것보다 훨씬 느릴 가능성이 높습니다. 그러나 일부 경우에는 객체 식별성이 중요합니다.)

이는 제자리 변경 연산인 |=, &=, ^=, -=도 지원합니다. 이는 오른쪽 피연산자가 임의의 Iterable일 수 있는 구체 메서드이지만, &=의 경우에는 오른쪽 피연산자가 Container이어야 합니다. 이 ABC는 거의 동일한 연산을 수행하는 내장 구체 set 타입에 있는 명명된 메서드를 제공하지 않습니다.

매핑

이 추상 클래스들은 읽기 전용 매핑과 변경 가능한 매핑을 나타냅니다. Mapping클래스는 가장 일반적인 읽기 전용 매핑 API를 나타냅니다.

내장 타입 dictMutableMapping에서 파생됩니다.

Mapping
Container, IterableSized의 서브클래스입니다. 매핑의 키는 자연스럽게 집합을 이룹니다. (키, 값) 쌍(튜플이어야 함)은 항목이라고도 합니다. 항목도 집합을 이룹니다. 메서드:
.__getitem__(key)
key에 해당하는 값을 반환하거나 KeyError를 발생시키는 추상 메서드입니다. 구현은 항상 KeyError를 발생시킵니다.
.get(key, default=None)
KeyError를 발생시키지 않으면 self[key]를 반환하고, 발생시키면 default값을 반환하는 구체 메서드입니다.
.__contains__(key)
self[key]KeyError를 발생시키지 않으면 True를 반환하고, 발생시키면 False를 반환하는 구체 메서드입니다.
.__len__()
서로 다른 키의 개수(즉, 키 집합의 길이)를 반환하는 추상 메서드입니다.
.__iter__()
키 집합의 각 키를 정확히 한 번씩 반환하는 추상 메서드입니다.
.keys()
키 집합을 Set으로 반환하는 구체 메서드입니다. 기본 구체 구현은 키 집합에 대한 “뷰”를 반환합니다(기본 매핑이 수정되면 뷰의 값도 그에 따라 변경된다는 의미입니다). 서브클래스는 뷰를 반환할 필요는 없지만 Set을 반환해야 합니다.
.items()
Set로 항목을 반환하는 구체 메서드입니다. 기본 구체 구현은 항목 집합에 대한 “뷰”를 반환합니다. 서브클래스는 뷰를 반환할 필요는 없지만 Set을 반환해야 합니다.
.values()
크기가 있고 이터러블인 컨테이너로 값을 반환하는 구체 메서드입니다(집합이 아닙니다!). 기본 구체 구현은 매핑의 값에 대한 “뷰”를 반환합니다. 서브클래스는 뷰를 반환할 필요는 없지만 크기가 있고 이터러블인 컨테이너를 반환해야 합니다.

다음 불변식은 모든 매핑 m에 대해 성립해야 합니다.:

len(m.values()) == len(m.keys()) == len(m.items()) == len(m)
[value for value in m.values()] == [m[key] for key in m.keys()]
[item for item in m.items()] == [(key, m[key]) for key in m.keys()]

즉, 항목, 키, 값을 반복하면 동일한 순서로 결과를 반환해야 합니다.

MutableMapping
표준 변경 메서드도 구현하는 Mapping의 서브클래스입니다. 추상 메서드에는 __setitem____delitem__이 포함됩니다. 구체 메서드에는 pop, popitem, clear, update가 포함됩니다. 참고: setdefault포함되지 않습니다. 미해결 문제: 메서드의 사양을 작성하십시오.

시퀀스

이러한 추상 클래스는 읽기 전용 시퀀스와 변경 가능한 시퀀스를 나타냅니다.

내장 listbytes 형식은 MutableSequence에서 파생됩니다. 내장 tuplestr 형식은 SequenceHashable에서 파생됩니다.

Sequence
Iterable, Sized, Container의 서브클래스입니다. 다소 복잡한 시그니처를 가진 새로운 추상 메서드 __getitem__을 정의합니다. 정수로 호출하면 시퀀스의 요소를 반환하거나 IndexError를 발생시키며, slice객체로 호출하면 또 다른 Sequence를 반환합니다. 구체 메서드 __iter__IndexError가 발생할 때까지 정수 인자 0, 1 등을 사용하여 __getitem__으로 요소를 반복합니다. 길이는 이터레이터가 반환하는 값의 개수와 같아야 합니다.

미해결 문제: 정수 인자를 사용하는 __len____getitem__에만 의존하는 기본 구체 구현을 가질 수 있는 다른 후보 메서드는 다음과 같습니다: __reversed__, index, count, __add__, __mul__.

MutableSequence
표준 변경 메서드를 추가한 Sequence의 서브클래스입니다. 추상 변경 메서드: 정수 인덱스와 슬라이스 모두에 대한 __setitem__, 같은 방식의 __delitem__, insert입니다. 구체 변경 메서드: append, reverse, extend, pop, remove입니다. 구체 변경 연산자: +=, *=입니다(이 연산자들은 객체를 제자리에서 변경합니다). 참고: sort()는 정의하지 않습니다. 이는 실제 list 인스턴스에만 존재하면 됩니다.

문자열

Python 3000에는 최소 두 가지 내장 문자열 형식이 있을 가능성이 큽니다. MutableSequence에서 파생되는 바이트 문자열(bytes)과, SequenceHashable에서 파생되는 (유니코드) 문자 문자열(str)입니다.

미해결 문제: 대체 구현과 서브클래스가 자신들이 무엇을 구현해야 하는지 알 수 있도록 이러한 형식의 기본 인터페이스를 정의하십시오. 이는 새로운 PEP 또는 PEP들의 주제가 될 수 있습니다(PEP 358bytes 형식을 위해 전용해야 합니다).

추상 베이스 클래스와 대안

이 절에서는 제안된 다른 접근 방식과 추상 베이스 클래스를 비교하고 대조해 보겠습니다.

ABC와 덕 타이핑

ABC의 도입은 덕 타이핑의 종말을 의미합니까? 그렇지는 않다고 생각합니다. Python은 클래스가 __getitem__ 메서드를 정의할 때 BasicMapping에서 상속하거나 Sequence에서 상속하도록 요구하지 않으며, x[y] 구문도 x가 어느 ABC의 인스턴스이도록 요구하지 않습니다. write 메서드를 가지고 있기만 하다면, 어떤 “파일과 같은” 객체든 sys.stdout에 할당할 수 있습니다.

물론 사용자가 적절한 베이스 클래스에서 상속하도록 장려하는 몇 가지 유인책은 있을 것이며, 이는 특정 기능에 대한 기본 구현부터 매핑과 시퀀스를 구분하는 향상된 능력까지 다양합니다. 그러나 불이익은 없습니다. hasattr(x, "__len__")이 작동한다면, 훌륭합니다! ABC는 Python 2에서 전혀 좋은 해결책이 없었던 문제, 예를 들어 매핑과 시퀀스를 구분하는 문제를 해결하기 위한 것입니다.

ABC와 제너릭 함수

ABC는 제너릭 함수(GF)와 호환됩니다. 예를 들어, 제가 직접 구현한 제너릭 함수 [4]은 인자의 클래스(타입)를 디스패치 키로 사용하므로 파생 클래스가 베이스 클래스를 재정의할 수 있습니다. ABC는 (Python의 관점에서) 매우 평범한 클래스이므로 GF의 기본 구현에서 ABC를 사용하는 것은 매우 적절할 수 있습니다. 예를 들어, 오버로드된 prettyprint 함수를 가지고 있다면 다음과 같이 집합의 예쁜 출력을 정의하는 것은 완전히 타당합니다.:

@prettyprint.register(Set)
def pp_set(s):
    return "{" + ... + "}"  # Details left as an exercise

또한 Set의 특정 서브클래스에 대한 구현도 쉽게 추가할 수 있습니다.

ABC는 RuleDispatch, 즉 PEAK [5]에서 Phillip Eby가 구현한 GF에도 아무런 문제를 일으키지 않을 것이라고 생각합니다.

물론 GF 지지자들은 GF(그리고 구체 클래스 또는 구현 클래스)만 있으면 모두 해결된다고 주장할 수도 있습니다. 그러나 그들조차도 상속의 유용성을 부정하지는 않을 것이며, 이 PEP에서 제안하는 ABC를 선택적인 구현 베이스 클래스로 쉽게 생각할 수 있습니다. 모든 사용자 정의 매핑이 BasicMapping에서 상속해야 한다는 요구 사항은 없습니다.

ABC와 인터페이스

ABC는 본질적으로 인터페이스와 호환되지 않는 것은 아니지만, 상당 부분 겹칩니다. 지금은 인터페이스 지지자들이 인터페이스가 더 나은 이유를 설명하도록 맡기겠습니다. 예를 들어 “매핑성”의 다양한 정도와 명칭을 정의하는 데 들어간 작업의 상당 부분은 ABC 대신 인터페이스를 사용하는 제안에 쉽게 적용할 수 있을 것으로 예상합니다.

이 문맥에서 “인터페이스”는 정규 클래스 계층 구조의 일부는 아니지만 특정 유형의 상속 검사를 가능하게 하는, 클래스에 첨부되는 추가 메타데이터 요소 집합에 관한 일련의 제안을 의미합니다.

이러한 메타데이터는 적어도 일부 제안에서는 애플리케이션이 쉽게 변경할 수 있도록 설계되며, 애플리케이션 작성자가 객체의 일반적인 분류를 재정의할 수 있게 합니다.

변경 가능한 메타데이터를 클래스에 첨부한다는 이 아이디어의 단점은 클래스가 공유 상태이므로 이를 변경하면 의도 충돌이 발생할 수 있다는 점입니다. 또한 객체의 분류를 재정의해야 하는 경우에는 제너릭 함수를 사용하는 편이 더 깔끔합니다. 가장 간단한 경우에는 기본 구현에서 단순히 False를 반환하는 “범주 소속” 제너릭 함수를 정의한 다음, 관심 있는 모든 클래스에 대해 True를 반환하는 재정의를 제공할 수 있습니다.

참고 자료

[2] GvR가 작성한 불완전한 구현 프로토타입 (https://web.archive.org/web/20170223133820/http://svn.python.org/view/sandbox/trunk/abc/)

[3] Bill Janssen이 작성한 위키 페이지, 가능한 Python 3K 클래스 트리? (https://wiki.python.org/moin/AbstractBaseClasses)

[9] 위키백과의 부분 순서 (https://en.wikipedia.org/wiki/Partial_order)

[10] 위키백과의 전순서 (https://en.wikipedia.org/wiki/Total_order)