これは Python 3000 に抽象基底クラス (Abstract Base Class, ABC) を追加する提案です。提案する内容は:
提案に至る思考の大部分はインタフェースやジェネリック関数(GF)と対比されるような ABC の特定のメカニズムとは関係ありません。 そのほとんどが「何が集合を集合たらしめるか」「マッピングとは何か」 「シーケンスは何を具えるべきか」といった哲学的な課題を明確化することに費やされました。
この文書に付随するものとして、数値型の ABC を定める PEP 3141 もあります。
オブジェクト指向プログラミングの領域で、オブジェクトをいじる方法は基本的な二つのカテゴリーに分けられます。一つは「呼び出し」もう一つは「覗き込み」です。
呼び出しとはオブジェクトにメソッド呼び出しを通して働きかけることです。 多くの場合、これに多態性が合わさって、メソッドの呼び出しがオブジェクトの型ごとに異なった実行結果をもたらします。
覗き込みは外部のコード(オブジェクトのメソッド以外のもの)が、型やオブジェクトのプロパティを調べたり、その情報に基づいてそのオブジェクトをどのように扱うか決める能力のことです。
どちらの使い方も同じ一般的な目的のためにあります。 その目的とはばらばらな全く新しい可能性もあるオブジェクトの統一的な処理を可能にし、 一方でそれぞれ異なるオブジェクトの種類ごとにカスタマイズされた決定方法を許すものです。
古典的な OOP 理論において、呼び出しこそが好まれる使い方で、 覗き込みは面と向かって止めるように言われていました。 それは古の手続き型プログラミングのスタイルだと考えられていたのです。 しかし、実践の場においてはこの見方は過度にドグマ的で柔軟性に欠けるだけで、 Python のような動的な性質の言語には似つかわしくない がちがちのデザインに向かわせるものでした。
殊に、オブジェクトをその作成者が予期しない方法で扱わなければならないことはよくあります。 全ての有り得べきユーザーの必要性を満足するようにあらゆるメソッドをオブジェクトに組み込むことがベストな解決策とは限りません。 さらに、振る舞いを厳格にオブジェクトにカプセル化する古典的 OOP の要請とは正反対の強力なディスパッチ哲学が数多くあります。 たとえば、ルールやパターンマッチ駆動の論理などです。
一方で、古典的 OOP 理論家による覗き込み批判の一つは形式化の欠如であり、 何が覗き込まれるかについての場当たり的な性質です。 Python のような言語、つまりオブジェクトのほとんど全ての部分を外部コードが覗き込み、直接アクセスできるような言語においては、オブジェクトが特定のプロトコルに適合しているかどうかを調べるいくつもの異なった方法があります。 たとえば「このオブジェクトは変更可能なシーケンスのコンテナですか?」 という問には、’list’ が基底クラスにあるかどうか探す、’__getitem__’ という名のメソッドを探すといった解答方法が思いつきます。 しかし間違えてはいけないのは、これらのテストが当たり前に見えるとしても、 どちらも正しくないということです。 一方は間違って否定することがあり、もう一方は間違って肯定することがあります。
一般に合意されている対処法は、テストを標準化し、 それらを形式的な取り合わせにグループ分けすることです。 これは各クラスを標準的なテスト可能プロパティの組に結び付けることで最も簡単に実現されます。 結びつけ方としては継承などの方法が使われます。 それぞれのテストは一揃いの約束事をもたらします。 クラスの一般的な振る舞いについての約束、 ほかにどのようなクラスメソッドが使用可能かに応じた約束などなど。
この PEP はこうしたテストの組織化戦略、抽象基底クラス (Abstract Base Class, ABC) として知られるものを提案します。 抽象基底クラスは単純な Python クラスでオブジェクトの継承ツリーに加えられ、 外部からの覗き込みに対してそのオブジェクトの特定の特性を伝えるものです。 テスト自体は isinstance() を使って行われ、 特定の ABC の存在はそれの定めるテストを通ったことを意味します。
それに加えて、ABC はある型の特徴的な振る舞いを確立する最小メソッド集合を定義します。 ABC に基づいてオブジェクトを判別するコードは、 それらのメソッドの存在を信じることができます。 これらのメソッドのそれぞれは一般化された抽象的な意味の定義を ABC の文書中に見つけることができます。 これら標準的な意味定義は強制力はありませんが、 従うことが強く推奨されます。
Python における他の物事と同様、ABC の約束は紳士協定に過ぎません。 もう少し噛み砕いて言うと、Python が ABC で交わされた約束のある部分を強制すると言っても、 具体的なクラスにおいてその他の約束を守ることが保証されるかどうかは実装者次第です。
仕様は概要で列挙されたカテゴリーの順番に従います:
この 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__ をこうした目的のためにパッチすることを提案していましたが、 組み込み型を変更不可能に保つべきいくつもの理由があります (一つ挙げておくと、float は同じアドレス空間で実行される全ての Python (mod_python [16] のように) で共有されています)。
もう一つの例として総称的関数(generic function) (PEP 3124) を全ての append() メソッドを持つシーケンスに対して定義したい人を取り上げましょう。 Sequence ABC は(以下を参照) append() メソッドについては何も約束しません。 一方で MutableSequence は append() 以外にも多くの変更用メソッドを要求します。
これらの、そして同様な、ジレンマを解くために、次の節では ABC と共に用いるメタクラスを提案します。 このメタクラスは ABC を「仮想基底クラス」(C++ の概念とは異なりますが) を任意のクラス(他の ABC も含めて)に付け加えることができるようにするものです。 こうすることで標準ライブラリに Sequence, MutableSequence といった ABC を定義してこれらを組み込み型の 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 は Python だけで書かれており、 ABC サポートフレームワークとしての役目を果たします。 そこではメタクラス ABCMeta が定義され、 デコレータ @abstractmethod および @abstractproperty も定義されます。 サンプル実装が [13] にあります。
ABCMeta クラスは __instancecheck__ および __subclasscheck__ を上書き(オーバーライド)し、 register メソッドを定義します。 register メソッドは引数を一つ取り、それはクラスです。 ひとたび B.register(C) を呼び出すと、以降 issubclass(C, B) は B.__subclasscheck__(C) が True を返すと True を返すようになります。 同様に isinstance(x, B) は issubclass(x.__class__, B) or issubclass(type(x), B) と等価です。 (type(x) と x.__class__ が異なるクラスであることはたとえば x がプロキシーオブジェクトのときにありえます)
これらのメソッドはメタクラスが ABCMeta (から派生した) クラスに対して呼び出されることを想定しています。 たとえば:
from abc import ABCMeta
class MyABC(metaclass=ABCMeta):
pass
MyABC.register(tuple)
assert issubclass(tuple, MyABC)
assert isinstance((), MyABC)
最後の二つの 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__ メソッドは型属性 __abstractmethods__ を __isabstractmethod__ 属性が真であるメソッド名の集合として求めます。 もう少し詳細に見ると、基底クラスたちの __abstractmethods__ を合算し、 新しいクラスの辞書の中で __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)
抽象メソッドと同様に、抽象プロパティ(デコレータ構文をつかうか長い形式で宣言されたもの)を継承したサブクラスは、具象プロパティで抽象プロパティを上書きしない限りインスタンス化できません。
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__ を表します。
__hash__ を定義する基底クラスです。 __hash__ メソッドは整数を返さなければなりません。 抽象メソッドとしての実装は常に 0 を返すものです(役には立ちません)。 不変条件: 二つのクラス C1 と C2 がどちらも Hashable から派生しているとき、 o1 が C1 のインスタンスで o2 が C2 であって、条件 o1 == o2 が成り立つならば、 hash(o1) == hash(o2) を満たさなければなりません。 言い替えると、異なるハッシュ値をもつオブジェクトは等しくあってはなりません。
もう一つの制約はハッシュ可能オブジェクトが一度生成されたらその値 (== で比較される値) やハッシュ値を変更してはなりません。 これを保証できないクラスは Hashable を継承することができません。 特定のインスタンスでのみこの保証が破れてしまうならば、 それらのインスタンスに対する __hash__ は TypeError 例外を送出すべきです。
注意: このクラスのインスタンスであることは必ずしもオブジェクトが変更不可能であることを意味しません。 たとえばリストが要素になるタプルは変更不可能ではありません。 このようなタプルの __hash__ メソッドは TypeError を送出します。 (これは再帰的に各要素のハッシュを計算しているからで、 ハッシュ不可能な要素があると TypeError を送出するのです。)
未解決問題: おそらく、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__")
この方法は登録の手間を取らせないという利点があります。 しかしながら、意味論はインスタンス属性 vs. クラス属性の間違いやすい意味論やクラスがそのメタクラスのインスタンスであることなどに起因して、 正確に正しく解釈するのが困難です。 __bases__ をチェックするのは望ましい意味論の近似に過ぎません。 腕試し: やってみましょう。でも、登録 API も機能するような形で実現して下さい。
読み取り専用の集合と変更可能な集合の抽象クラスです。 一番基礎的な集合の演算といえば所属判定です。 これを x in s と書き、実装方法は s.__contains__(x) です。 この操作はすでに Container で定義しました。 というわけで、集合は大きさの決まった(sized) イテレート可能な(iterable) コンテナ(container)として定義し、さらに数学の集合論から来る不変条件を課します。
組み込み型 set は MutableSet から派生します。 もう一つの組み込み型 frozenset は Set と Hashable から派生します。
このクラスは大きさの決まったイテレート可能なコンテナです。 つまり、 Sized, Iterable, Container のサブクラスです。 これら三つのサブクラスだからといって集合とは限らないわけですが! 集合には追加の不変条件があり、全ての要素はただ一度しか現れません (イテレートしてみれば判ります)。 さらに、部分集合/上位集合テストとなる不等号演算を実装する非抽象演算子を定義します。 一般に、数学的な意味での有限集合の不変条件を満たします。 [11]
異なる実装の Set 同士は安全に、(たいていは)効率的で正しく比較できます。 使うのは有限集合の部分集合/上位集合演算の数学的定義です。 順序演算は実際的な実装になっていますので、 サブクラスはこれを高速化するために再定義しても構いませんが、 意味論を壊さないようにして下さい。 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__ の別名に過ぎません。
Set のサブクラスで、要素を加えたり取り除いたりするのために追加された操作を実装するものです。 それぞれのメソッドは Python 2 の set 型で知られる意味論を満たすようになっています (discard は例外で、これは Java をモデルにしています):
このクラスは他にその場での変更操作も提供しています: |=, &=, ^=, -= 。 これらの非抽象メソッドの右演算対象は任意の Iterable になります。 例外的に &= では Container でなければなりません。 この ABC では組み込み型 set で使うことができるほぼ同じことをする名前付きメソッドは提供されていません。
読み取り専用のマッピングと変更可能なマッピングの抽象クラスです。 Mapping クラスはごく普通の読み取り専用マッピング API を表します。
組み込み型の dict は MutableMapping から派生します。
Container, Iterable, Sized のサブクラスです。 マッピングのキーは自然に集合を成します。 (キー, 値) ペア (タプルです) をアイテムとも呼びます。 アイテムも集合を成します。 メソッド:
以下の不変条件が任意のマッピング 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()]
即ち、アイテム、キー、値のイテレーションはどれも同じ順番で結果を返さなければなりません。
読み取り専用のシーケンスと変更可能なシーケンスの抽象クラスです。
MutableSequence からは組み込み型 list と byte が派生します。 tuple と str は Sequence と Hashable からの派生です。
Iterable, Sized, Container のサブクラスです。 新しい抽象メソッド __getitem__ を定義します。 そのメソッドにはある意味で複雑なシグニチャがあります: 整数を引数に呼ばれた場合には、シーケンス中の要素を返すか IndexError を送出します。 slice オブジェクトを引数に呼び出された場合には、 別の Sequence を返します。 非抽象メソッド __iter__ は要素のイテレーションに __getitem__ に整数 0, 1, … を引数に渡して、 IndexError が送出されるまで使います。 長さとイテレータの返す値の個数は一致すべきです。
未解決問題: 他にも __len__ と整数引数の __getitem__ だけにしか依存しないので具体的な実装を持ち得るメソッドの候補があります: __reversed__, index, count, __add__, __mul__ 。
Python 3000 には少なくとも2種類の文字列型が存在することになりそうです: バイト列 (bytes) は MutableSequence から、 (ユニコード)文字列 (str) は Sequence と Hashable から、 それぞれ派生します。
未解決問題: これらの基底インタフェースを定義して、代替実装やサブクラスがどの種類のものかを知ることができるようにしてください。 この問題は新しい PEP (複数かもしれません) の主題になるでしょう。 (bytes 型については PEP 358 も一緒に考えなければなりません)。
このセクションでは ABC と他のアプローチを比較対照しようと思います。
ABC の導入によってダックタイピングは滅びるのでしょうか? 多分そうはならないでしょう。 Python は __getitem__ メソッドを定義する時に BasicMapping や Sequence から派生するようには要求しませんし、 x[y] の文法も x にどちらかの ABC のインスタンスであることを要求しません。 依然として write メソッドさえ具えている「ファイル風」オブジェクトであれば、 sys.stdout に割り当てることができます。
もちろん、ユーザーが適切な基底クラスを使う方向に進むようにニンジンをぶらさげることはあるかもしれません。 たとえば特定の機能性に対するデフォルト実装であったり、 マッピングとシーケンスを見分ける能力を向上させることであったり。 しかしムチが飛ぶわけではありません。 hasattr(x, "__len__") で十分ということであれば、そのままで結構。 ABC は Python 2 に良い解決策が無かった、たとえばマッピングとシーケンスを見分けるといった問題を解決したいというものなのです。
ABC は総称関数 (generic function, GF) と互換性があります。 たとえば私自身の総称関数の実装 [4] は引数のクラス(型)をディスパッチのキーとして使い、 派生クラスが基底クラスを再定義することを許します。 ABC は (Python の視点からは) ごく普通のクラスで、 ABC を GF のデフォルト実装に使うのはとても自然です。 たとえば、私は多重定義された pretyprint 関数を持っていますが、 集合のプリティプリントの定義を以下のようにしておいて無理はないでしょう:
@prettyprint.register(Set)
def pp_set(s):
return "{" + ... + "}" # 詳細は練習問題です
そして Set の特定のサブクラスに対する実装を追加するのは簡単です。
その他、 RuleDispatch や Phillip Eby の PEAK にある GF 実装 [5] とも一緒に使って問題ないと私は信じています。
もちろん、GF 支持者は GF が (そして具象的クラスつまり実装されたクラス) が欲しいと主張することでしょう。 しかし彼らとて継承の有用性を否定はしないでしょう。 この PEP で提案されている ABC をオプションの実装基底クラスと考えても構わないでしょう。 ユーザーが定義する全てのマッピングが BasicMapping から派生しなければならないというような要求はありません。
ABC は本来的にインタフェースと非互換的というわけではありませんが、 多くの共通部分があります。 ここでは、インタフェース支持者になぜインタフェースの方が優れているかという説明は任せることにします。 思うに、進めてきた作業の多く、例えば「マッピング性」の様々な側面の定義および命名規則は、ABC の代わりにインタフェースを使う提案に簡単に適用できそうです。
この文脈で「インタフェース」というのはクラスに付加される追加のメタデータ要素の提案で、これらのメタデータは通常のクラス階層とは別物です。 一方でこれらによってある種の継承テストができるようになるものです。
そうしたメタデータは、少なくとも幾つかの提案では、 アプリケーションごとに容易に変更できるようにデザインされ、 アプリケーション作者がオブジェクトの普通の分類を再定義することが許されます。
このクラスに変更可能なメタデータを付加するというアイディアの欠点は、 クラスというものは共有される状態であり、 それを変更してしまうと意図がずれてしまうかもしれないことです。 付け加えて、オブジェクトの分類を再定義するニーズは総称関数によってより簡潔に実現できます。 一番単純な場合、「カテゴリーへの所属」総称関数を定義して、 それが基底実装では単純に False を返し関心のあるクラスに対しては True を返すように再定義したものを提供することができます。
[1] | An Introduction to ABC’s, by Talin (http://mail.python.org/pipermail/python-3000/2007-April/006614.html) |
[2] | Incomplete implementation prototype, by GvR (http://svn.python.org/view/sandbox/trunk/abc/) |
[3] | Possible Python 3K Class Tree?, wiki page created by Bill Janssen (http://wiki.python.org/moin/AbstractBaseClasses) |
[4] | Generic Functions implementation, by GvR (http://svn.python.org/view/sandbox/trunk/overload/) |
[5] | Charming Python: Scaling a new PEAK, by David Mertz (http://www-128.ibm.com/developerworks/library/l-cppeak2/) |
[6] | Implementation of @abstractmethod (http://python.org/sf/1706989) |
[7] | Unifying types and classes in Python 2.2, by GvR (http://www.python.org/download/releases/2.2.3/descrintro/) |
[8] | Putting Metaclasses to Work: A New Dimension in Object-Oriented Programming, by Ira R. Forman and Scott H. Danforth (http://www.amazon.com/gp/product/0201433052) |
[9] | Partial order, in Wikipedia (http://en.wikipedia.org/wiki/Partial_order) |
[10] | Total order, in Wikipedia (http://en.wikipedia.org/wiki/Total_order) |
[11] | Finite set, in Wikipedia (http://en.wikipedia.org/wiki/Finite_set) |
[12] | Make isinstance/issubclass overloadable (http://python.org/sf/1708353) |
[13] | ABCMeta sample implementation (http://svn.python.org/view/sandbox/trunk/abc/xyz.py) |
[14] | python-dev email (“Comparing heterogeneous types”) http://mail.python.org/pipermail/python-dev/2004-June/045111.html |
[15] | Function frozenset_hash() in Object/setobject.c (http://svn.python.org/view/python/trunk/Objects/setobject.c) |
[16] | Multiple interpreters in mod_python (http://www.modpython.org/live/current/doc-html/pyapi-interps.html) |
この文書はパブリックドメインです。