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
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
이 문서는 Python 3000에 추상 베이스 클래스(ABC) 지원을 추가하자는 제안입니다. 이 제안의 내용은 다음과 같습니다.
isinstance()와issubclass()를 오버로드하는 방법.- “ABC 지원 프레임워크” 역할을 하는 새 모듈
abc. 이 모듈은 ABC와 함께 사용할 메타클래스와, 추상 메서드를 정의하는 데 사용할 수 있는 데코레이터를 정의합니다. - collections 모듈에 추가할, 컨테이너와 이터레이터를 위한 구체적인 ABC.
이 제안에 들어간 고민의 상당 부분은 인터페이스나 제네릭 함수(GF)와 대비되는 ABC의 구체적인 메커니즘에 관한 것이 아니라, “무엇이 집합을 이루는가”, “무엇이 매핑을 이루는가”, “무엇이 시퀀스를 이루는가”와 같은 철학적 문제를 명확히 하는 것에 관한 것입니다.
이와 짝을 이루는 PEP 3141도 있으며, 이 문서는 숫자 타입을 위한 ABC를 정의합니다.
감사의 글
Talin은 아래의 근거 [1]와 ABC 대 인터페이스 절의 대부분을 작성했습니다. 그것만으로도 그는 공동 저자로 인정받을 자격이 있습니다. 이 PEP의 나머지 부분에서 “나”는 제1 저자를 가리킵니다.
근거
객체 지향 프로그래밍 영역에서 객체와 상호 작용하는 사용 패턴은 ‘호출’과 ‘검사’라는 두 가지 기본 범주로 나눌 수 있습니다.
호출이란 객체의 메서드를 호출하여 객체와 상호 작용하는 것을 의미합니다. 보통 이는 다형성과 결합되므로, 주어진 메서드를 호출하더라도 객체의 타입에 따라 서로 다른 코드가 실행될 수 있습니다.
검사란 (객체의 메서드 바깥에 있는) 외부 코드가 해당 객체의 타입이나 속성을 살펴보고, 그 정보를 바탕으로 해당 객체를 어떻게 다룰지 결정할 수 있는 능력을 의미합니다.
두 사용 패턴은 같은 일반적인 목적에 기여합니다. 즉, 다양하고 잠재적으로 새로운 객체들을 균일한 방식으로 처리할 수 있도록 지원하면서도, 동시에 서로 다른 타입의 객체마다 처리 결정을 맞춤화할 수 있도록 하는 것입니다.
고전적인 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와 함께 사용할 메타클래스와, 추상 메서드를 정의하는 데 사용할 수 있는 데코레이터를 정의합니다. - collections 모듈에 추가될, 컨테이너와 이터레이터를 위한 구체적인 ABC.
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가 필요한 사용자는 어떻게 해야 하느냐는 것입니다. 예를 들어, 자신만의 초월수(Transcendental) 종류를 정의하고 싶으면서 float와 int도 Transcendental로 간주되기를 바라는 수학자의 곤경을 생각해 보십시오. PEP 3141은 원래 이를 위해 float.__bases__를 패치하자고 제안했지만, 내장 타입을 불변으로 유지해야 할 타당한 이유가 몇 가지 있습니다(한 가지를 들자면, 내장 타입은 mod_python [16]에서처럼 같은 주소 공간에서 실행되는 모든 Python 인터프리터 사이에서 공유됩니다).
또 다른 예로는 append() 메서드가 있는 모든 시퀀스에 대해 제네릭 함수(PEP 3124)를 정의하려는 사람을 들 수 있습니다. Sequence ABC(아래 참조)는 append() 메서드를 약속하지 않는 반면, MutableSequence는 append()뿐만 아니라 다양한 다른 변경 메서드도 요구합니다.
이러한 딜레마와 그와 비슷한 딜레마를 해결하기 위해, 다음 절에서는 ABC와 함께 사용할 메타클래스를 제안합니다. 이 메타클래스를 사용하면 다른 ABC를 포함한 어떤 클래스에든 ABC를 “가상 베이스 클래스”(C++의 개념과는 다름)로 추가할 수 있습니다. 이를 통해 표준 라이브러리는 ABC Sequence와 MutableSequence를 정의하고, 이들을 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) or issubclass(type(x), B)와 동등합니다. (예를 들어 x가 프록시 객체인 경우처럼 type(x)와 x.__class__가 같은 객체가 아닐 수도 있습니다.)
이 메서드들은 메타클래스가 ABCMeta이거나 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이거나 ABCMeta에서 파생된 클래스에 대해서만 사용해야 합니다. 클래스에 추상 메서드를 동적으로 추가하거나, 메서드나 클래스가 생성된 후 그 추상화 상태를 변경하려는 시도는 지원되지 않습니다. @abstractmethod는 일반 상속으로 파생된 서브클래스에만 영향을 줍니다. register() 메서드로 등록된 “가상 서브클래스”는 영향을 받지 않습니다.
구현: @abstractmethod 데코레이터는 함수 속성 __isabstractmethod__를 True 값으로 설정합니다. ABCMeta.__new__ 메서드는 타입 속성 __abstractmethods__를, 값이 참인 __isabstractmethod__ 속성을 가진 모든 메서드 이름의 집합으로 계산합니다. 이를 위해 베이스 클래스의 __abstractmethods__ 속성을 결합하고, 새 클래스 dict에서 참인 __isabstractmethod__ 속성을 가진 모든 메서드의 이름을 추가하며, 새 클래스 dict에서 참인 __isabstractmethod__ 속성을 갖지 않는 모든 메서드의 이름을 제거합니다. 그 결과로 얻은 __abstractmethods__ 집합이 비어 있지 않으면 해당 클래스는 추상 클래스로 간주되며, 인스턴스화를 시도하면 TypeError가 발생합니다. (이것이 CPython에 구현된다면 이 검사를 빠르게 하기 위해 내부 플래그 Py_TPFLAGS_ABSTRACT를 사용할 수 있을 것입니다 [6].)
논의: Java의 추상 메서드나 C++의 순수 추상 메서드와 달리, 여기서 정의하는 추상 메서드는 구현을 가질 수 있습니다. 이 구현은 이를 오버라이드하는 클래스에서 super 메커니즘을 통해 호출할 수 있습니다. 이는 협력적 다중 상속을 사용하는 프레임워크에서 super 호출의 종점으로 유용할 수 있습니다 [7], [8].
추상 데이터 속성을 정의하기 위해 두 번째 데코레이터인 @abstractproperty가 정의됩니다. 그 구현은 __isabstractmethod__ 속성을 추가하는 내장 property 클래스의 서브클래스입니다:
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는 “믹스인(mix-in)” 클래스로 볼 수 있습니다.
이 PEP에서 정의하는 어떤 ABC도 __init__, __new__, __str__, __repr__를 오버라이드하지 않습니다. 표준 생성자 시그니처를 정의하면 패트리샤 트리나 gdbm 파일 같은 사용자 정의 컨테이너 타입을 불필요하게 제약하게 됩니다. 컬렉션의 특정 문자열 표현을 정의하는 일도 마찬가지로 개별 구현에 맡깁니다.
참고: 순서 연산(__lt__, __le__, __ge__, __gt__)을 위한 ABC는 없습니다. 이러한 연산을 (추상 클래스든 아니든) 베이스 클래스에 정의하면 두 번째 피연산자로 받아들일 타입과 관련하여 문제가 생깁니다. 예를 들어 Ordering 클래스가 __lt__를 정의했다면, 누구나 임의의 Ordering 인스턴스 x와 y에 대해 x < y가 (부분 순서만 정의하더라도) 정의되어 있으리라 가정할 것입니다. 하지만 실제로는 그럴 수 없습니다. list, str 둘 다 Ordering에서 파생된다면 [1, 2] < (1, 2)가 정의되어야 하고 (아마 False를 반환해야) 한다는 뜻이 되는데, 실제로는 (Python 3000에서는!) 이러한 “혼합 모드 비교” 연산이 명시적으로 금지되어 있으며 TypeError를 발생시킵니다. 자세한 내용은 PEP 3100과 [14]를 참조하십시오. (이는 같은 타입의 다른 인자를 받는 연산에 관한 더 일반적인 문제의 특수한 경우입니다.)
한 가지 재주만 부리는 클래스들
이 추상 클래스들은 __iter__나 __len__ 같은 단일 메서드를 나타냅니다.
Hashable__hash__를 정의하는 클래스를 위한 베이스 클래스입니다.__hash__메서드는 정수를 반환해야 합니다. 추상__hash__메서드는 항상 0을 반환하며, 이는 (비효율적이기는 하지만) 유효한 구현입니다. 불변 조건: 클래스C1,C2가 모두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를 반환합니다.Iterable과Iterator의 차이에 유의하십시오.Iterable은 이터레이션할 수 있는 것, 즉__iter__메서드를 지원하는 것이고,Iterator는 내장 함수iter()가 반환하는 것, 즉__next__메서드를 지원하는 것입니다.Sized__len__을 정의하는 클래스를 위한 베이스 클래스입니다.__len__메서드는 0 이상인Integer를 반환해야 합니다(아래 “Numbers” 참조). 추상__len__메서드는 0을 반환합니다. 불변 조건: 클래스C가Iterable뿐만 아니라Sized에서도 파생된다면,C의 모든 인스턴스c에 대해 불변 조건sum(1 for x in c) == len(c)가 성립해야 합니다.Container__contains__를 정의하는 클래스를 위한 베이스 클래스입니다.__contains__메서드는bool을 반환해야 합니다. 추상__contains__메서드는False를 반환합니다. 불변 조건: 클래스C가Iterable뿐만 아니라Container에서도 파생된다면,C의 모든 인스턴스c에 대해(x in c for x in c)는 True 값만 산출하는 제너레이터여야 합니다.
미해결 문제: 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__에 대한 검사는 원하는 의미의 근사치에 불과합니다. 허수아비 제안: 이렇게 합시다. 다만 등록 API도 함께 동작하도록 구성합시다.
집합
이 추상 클래스들은 읽기 전용 집합과 변경 가능한 집합을 나타냅니다. 가장 기본적인 집합 연산은 멤버십 검사로, x in s로 작성하며 s.__contains__(x)로 구현됩니다. 이 연산은 위에서 정의한 Container 클래스에 이미 정의되어 있습니다. 따라서 우리는 집합을, 수학적 집합론의 특정 불변 조건이 성립하는, 크기가 있고 이터러블한 컨테이너로 정의합니다.
내장 타입 set은 MutableSet에서 파생됩니다. 내장 타입 frozenset은 Set과 Hashable에서 파생됩니다.
Set- 이것은 크기가 있고 이터러블한 컨테이너, 즉
Sized,Iterable,Container의 서브클래스입니다. 하지만 이 세 클래스의 서브클래스가 모두 집합인 것은 아닙니다! 집합에는 (이터레이션으로 확인할 수 있듯이) 각 원소가 한 번만 나타난다는 추가 불변 조건이 있으며, 또한 집합은 부등 연산을 부분집합/상위집합 검사로 구현하는 구체 연산자를 정의합니다. 일반적으로 수학에서 유한 집합에 대해 성립하는 불변 조건이 성립합니다. [11]구현이 서로 다른 집합들은 유한 집합에 대한 부분집합/상위집합 연산의 수학적 정의를 사용하여 안전하고, (대개) 효율적이며, 올바르게 비교할 수 있습니다. 순서 비교 연산에는 구체적인 구현이 있습니다. 서브클래스는 속도를 위해 이를 재정의할 수 있지만 그 의미는 유지해야 합니다.
Set이Sized에서 파생되므로, 길이가 서로 다른 두 집합을 비교하는 경우__eq__는 지름길을 택해 즉시False를 반환할 수 있습니다. 마찬가지로, 첫 번째 집합의 멤버가 두 번째 집합보다 많으면__le__는 즉시False를 반환할 수 있습니다. 집합 포함 관계는 부분 순서만 구현한다는 점에 유의하십시오. 예를 들어{1, 2}와{1, 3}은 순서가 정해지지 않습니다(이 인자들에 대해서는<,==,>세 가지 모두False를 반환합니다). 집합은 매핑이나 시퀀스와 순서를 비교할 수 없지만, 동등성은 비교할 수 있습니다(그리고 이 경우 항상 같지 않은 것으로 비교됩니다).이 클래스는 합집합, 교집합, 대칭 차집합, 비대칭 차집합을 계산하는 구체적인 연산자, 즉 각각
__or__,__and__,__xor__,__sub__도 정의합니다. 이 연산자들은Set의 인스턴스를 반환해야 합니다. 기본 구현은 재정의 가능한 클래스 메서드_from_iterable()을 이터러블 인자로 호출합니다. 이 팩토리 메서드의 기본 구현은frozenset인스턴스를 반환합니다. 다른 적절한Set서브클래스를 반환하도록 재정의할 수 있습니다.마지막으로, 이 클래스는 원소들로부터 해시값을 계산하는 구체적인 메서드
_hash를 정의합니다.Set의 해시 가능한 서브클래스는_hash를 호출하여__hash__를 구현하거나, 같은 알고리즘을 더 효율적으로 다시 구현할 수 있습니다. 다만 구현된 알고리즘은 같아야 합니다. 현재 이 알고리즘은 소스 코드 [15]에서만 완전하게 명시되어 있습니다.참고: Python 2의 set 타입에 있는
issubset과issuperset메서드는 지원하지 않습니다. 이들은 대부분__le__와__ge__의 별칭에 불과하기 때문입니다. MutableSet- 원소를 추가하고 제거하는 추가 연산을 구현하는
Set의 서브클래스입니다. 지원되는 메서드는 Python 2의set타입에서 알려진 의미를 가집니다(Java를 본뜬discard는 예외입니다)..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를 나타냅니다.
내장 타입 dict는 MutableMapping에서 파생됩니다.
MappingContainer,Iterable,Sized의 서브클래스입니다. 매핑의 키들은 자연스럽게 집합을 이룹니다. (키, 값) 쌍(반드시 튜플이어야 합니다)은 항목(item)이라고도 합니다. 항목들 역시 집합을 이룹니다. 메서드:.__getitem__(key)key에 대응하는 값을 반환하거나KeyError를 발생시키는 추상 메서드입니다. 구현은 항상KeyError를 발생시킵니다..get(key, default=None)self[key]가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는 포함되지 않습니다. 미해결 문제: 메서드들의 명세를 작성하십시오.
시퀀스
이 추상 클래스들은 읽기 전용 시퀀스와 변경 가능한 시퀀스를 나타냅니다.
내장 list와 bytes 타입은 MutableSequence에서 파생됩니다. 내장 tuple과 str 타입은 Sequence와 Hashable에서 파생됩니다.
SequenceIterable,Sized,Container의 서브클래스입니다. 다소 복잡한 시그니처를 가진 새로운 추상 메서드__getitem__을 정의합니다. 정수로 호출하면 시퀀스의 요소를 반환하거나IndexError를 발생시키고,slice객체로 호출하면 또 다른Sequence를 반환합니다. 구체__iter__메서드는IndexError가 발생할 때까지 정수 인자 0, 1 등으로__getitem__을 호출하여 요소들을 이터레이션합니다. 길이는 이터레이터가 반환하는 값의 개수와 같아야 합니다.미해결 문제: 그 밖의 후보 메서드로
__reversed__,index,count,__add__,__mul__이 있으며, 이들은 모두__len__과 정수 인자를 받는__getitem__에만 의존하는 기본 구체 구현을 가질 수 있습니다.MutableSequence- 몇 가지 표준 변경 메서드를 추가한
Sequence의 서브클래스입니다. 추상 변경 메서드:__setitem__(정수 인덱스와 슬라이스 모두에 대해),__delitem__(마찬가지),insert. 구체 변경 메서드:append,reverse,extend,pop,remove. 구체 변경 연산자:+=,*=(이들은 객체를 제자리에서 변경합니다). 참고: 이 클래스는sort()를 정의하지 않습니다 – 이 메서드는 실제list인스턴스에만 있으면 됩니다.
문자열
Python 3000에는 적어도 두 가지 내장 문자열 타입이 있을 것으로 보입니다. MutableSequence에서 파생되는 바이트 문자열(bytes)과 Sequence 및 Hashable에서 파생되는 (유니코드) 문자 문자열(str)입니다.
미해결 문제: 대안 구현과 서브클래스가 무엇을 감당해야 하는지 알 수 있도록 이들의 베이스 인터페이스를 정의하십시오. 이는 새로운 PEP 하나 또는 여러 PEP의 주제가 될 수 있습니다(PEP 358은 bytes 타입을 위해 전용되어야 합니다).
ABC와 대안 비교
이 절에서는 지금까지 제안된 다른 접근 방식들과 ABC를 비교하고 대조해 보겠습니다.
ABC 대 덕 타이핑
ABC의 도입은 덕 타이핑의 종말을 의미합니까? 저는 그렇게 생각하지 않습니다. Python은 클래스가 __getitem__ 메서드를 정의할 때 BasicMapping이나 Sequence에서 파생될 것을 요구하지 않으며, x[y] 문법도 x가 두 ABC 중 어느 하나의 인스턴스일 것을 요구하지 않습니다. write 메서드만 있다면 여전히 어떤 “파일류” 객체든 sys.stdout에 대입할 수 있습니다.
물론 사용자가 적절한 베이스 클래스에서 파생하도록 장려하는 당근은 몇 가지 있을 것입니다. 그 당근은 특정 기능의 기본 구현부터 매핑과 시퀀스를 구별하는 향상된 능력까지 다양합니다. 하지만 채찍은 없습니다. hasattr(x, "__len__")로 충분하다면, 훌륭합니다! ABC는 매핑과 시퀀스를 구별하는 것처럼 Python 2에서는 좋은 해결책이 전혀 없는 문제를 해결하기 위한 것입니다.
ABC 대 제네릭 함수
ABC는 제네릭 함수(GF)와 호환됩니다. 예를 들어, 제가 직접 만든 제네릭 함수 구현 [4]은 인자의 클래스(타입)를 디스패치 키로 사용하므로, 파생 클래스가 베이스 클래스를 재정의할 수 있습니다. (Python 관점에서) ABC는 아주 평범한 클래스이므로, GF의 기본 구현에서 ABC를 사용하는 것이 매우 적절할 수 있습니다. 예를 들어, 오버로드된 prettyprint 함수가 있다면, 집합의 예쁜 출력(pretty-printing)을 다음과 같이 정의하는 것은 매우 타당합니다:
@prettyprint.register(Set)
def pp_set(s):
return "{" + ... + "}" # Details left as an exercise
그리고 Set의 특정 서브클래스를 위한 구현도 쉽게 추가할 수 있습니다.
저는 RuleDispatch, 즉 PEAK [5]에 있는 Phillip Eby의 GF 구현에도 ABC가 아무런 문제를 일으키지 않으리라 믿습니다.
물론 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] Possible Python 3K Class Tree?, Bill Janssen이 만든 위키 페이지 (https://wiki.python.org/moin/AbstractBaseClasses)
[9] 부분 순서, 위키백과 (https://en.wikipedia.org/wiki/Partial_order)
[10] 전순서, 위키백과 (https://en.wikipedia.org/wiki/Total_order)
frozenset_hash() 함수 (https://web.archive.org/web/20170224204758/http://svn.python.org/view/python/trunk/Objects/setobject.c)Copyright
This document has been placed in the public domain.