Previous topic

342 機能強化したジェネレータによるコルーチン

Next topic

私家版 日本語 PEP 集

This Page

3119 抽象基底クラスの導入

  • PEP: 3119
  • Title: Introducing Abstract Base Classes
  • Author: Guido van Rossum <guido@python.org>, Talin <talin@acm.org>
  • Status: Final
  • Type: Standards Track
  • Content-Type: text/x-rst
  • Created: 18-Apr-2007
  • Post-History: 26-Apr-2007, 11-May-2007
  • Translator: 松井鉄史
  • Translation-History: 18-Feb-2011

概要

これは Python 3000 に抽象基底クラス (Abstract Base Class, ABC) を追加する提案です。提案する内容は:

  • isinstance() および issubclass() の多重定義(オーバーロード)方法。
  • “ABC フレームワーク” として使う abc 新モジュール。 このモジュールでは ABC と共に使うメタクラスと抽象メソッドを定義するのに使うデコレータを定義します。
  • コンテナとイテレータに特化した ABC が collections モジュールに追加されます。

提案に至る思考の大部分はインタフェースやジェネリック関数(GF)と対比されるような ABC の特定のメカニズムとは関係ありません。 そのほとんどが「何が集合を集合たらしめるか」「マッピングとは何か」 「シーケンスは何を具えるべきか」といった哲学的な課題を明確化することに費やされました。

この文書に付随するものとして、数値型の ABC を定める PEP 3141 もあります。

著者について

Talin が以下の提案理由 [1] と ABC 対インタフェースの節の大半を書きました。 そのことによって彼が共著者に名を連ねることになりました。 その他の部分における一人称「私」は第一著者を指します。

提案理由

オブジェクト指向プログラミングの領域で、オブジェクトをいじる方法は基本的な二つのカテゴリーに分けられます。一つは「呼び出し」もう一つは「覗き込み」です。

呼び出しとはオブジェクトにメソッド呼び出しを通して働きかけることです。 多くの場合、これに多態性が合わさって、メソッドの呼び出しがオブジェクトの型ごとに異なった実行結果をもたらします。

覗き込みは外部のコード(オブジェクトのメソッド以外のもの)が、型やオブジェクトのプロパティを調べたり、その情報に基づいてそのオブジェクトをどのように扱うか決める能力のことです。

どちらの使い方も同じ一般的な目的のためにあります。 その目的とはばらばらな全く新しい可能性もあるオブジェクトの統一的な処理を可能にし、 一方でそれぞれ異なるオブジェクトの種類ごとにカスタマイズされた決定方法を許すものです。

古典的な OOP 理論において、呼び出しこそが好まれる使い方で、 覗き込みは面と向かって止めるように言われていました。 それは古の手続き型プログラミングのスタイルだと考えられていたのです。 しかし、実践の場においてはこの見方は過度にドグマ的で柔軟性に欠けるだけで、 Python のような動的な性質の言語には似つかわしくない がちがちのデザインに向かわせるものでした。

殊に、オブジェクトをその作成者が予期しない方法で扱わなければならないことはよくあります。 全ての有り得べきユーザーの必要性を満足するようにあらゆるメソッドをオブジェクトに組み込むことがベストな解決策とは限りません。 さらに、振る舞いを厳格にオブジェクトにカプセル化する古典的 OOP の要請とは正反対の強力なディスパッチ哲学が数多くあります。 たとえば、ルールやパターンマッチ駆動の論理などです。

一方で、古典的 OOP 理論家による覗き込み批判の一つは形式化の欠如であり、 何が覗き込まれるかについての場当たり的な性質です。 Python のような言語、つまりオブジェクトのほとんど全ての部分を外部コードが覗き込み、直接アクセスできるような言語においては、オブジェクトが特定のプロトコルに適合しているかどうかを調べるいくつもの異なった方法があります。 たとえば「このオブジェクトは変更可能なシーケンスのコンテナですか?」 という問には、’list’ が基底クラスにあるかどうか探す、’__getitem__’ という名のメソッドを探すといった解答方法が思いつきます。 しかし間違えてはいけないのは、これらのテストが当たり前に見えるとしても、 どちらも正しくないということです。 一方は間違って否定することがあり、もう一方は間違って肯定することがあります。

一般に合意されている対処法は、テストを標準化し、 それらを形式的な取り合わせにグループ分けすることです。 これは各クラスを標準的なテスト可能プロパティの組に結び付けることで最も簡単に実現されます。 結びつけ方としては継承などの方法が使われます。 それぞれのテストは一揃いの約束事をもたらします。 クラスの一般的な振る舞いについての約束、 ほかにどのようなクラスメソッドが使用可能かに応じた約束などなど。

この PEP はこうしたテストの組織化戦略、抽象基底クラス (Abstract Base Class, ABC) として知られるものを提案します。 抽象基底クラスは単純な Python クラスでオブジェクトの継承ツリーに加えられ、 外部からの覗き込みに対してそのオブジェクトの特定の特性を伝えるものです。 テスト自体は isinstance() を使って行われ、 特定の ABC の存在はそれの定めるテストを通ったことを意味します。

それに加えて、ABC はある型の特徴的な振る舞いを確立する最小メソッド集合を定義します。 ABC に基づいてオブジェクトを判別するコードは、 それらのメソッドの存在を信じることができます。 これらのメソッドのそれぞれは一般化された抽象的な意味の定義を ABC の文書中に見つけることができます。 これら標準的な意味定義は強制力はありませんが、 従うことが強く推奨されます。

Python における他の物事と同様、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 が必要なユーザーはどうしたらいいのか、ということでした。 考えてみて下さい。 ある数学者が彼自身の超越数(Transcendental)を定義したかったとして、 しかも float や int も Transcendental として考えたい、 というときの苦境を。 PEP 3141 は当初 float.__bases__ をこうした目的のためにパッチすることを提案していましたが、 組み込み型を変更不可能に保つべきいくつもの理由があります (一つ挙げておくと、float は同じアドレス空間で実行される全ての Python (mod_python [16] のように) で共有されています)。

もう一つの例として総称的関数(generic function) (PEP 3124) を全ての append() メソッドを持つシーケンスに対して定義したい人を取り上げましょう。 Sequence ABC は(以下を参照) append() メソッドについては何も約束しません。 一方で MutableSequenceappend() 以外にも多くの変更用メソッドを要求します。

これらの、そして同様な、ジレンマを解くために、次の節では 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 モジュール: ABC サポートフレームワーク

新しい標準ライブラリモジュール 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)

抽象メソッドと同様に、抽象プロパティ(デコレータ構文をつかうか長い形式で宣言されたもの)を継承したサブクラスは、具象プロパティで抽象プロパティを上書きしない限りインスタンス化できません。

コンテナとイテレータのための 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 のインスタンス xy に対しても x < y が定義されることになります (半順序だけ定義するにしても)。 しかし、これは無理です: もし liststr が共に Ordering から派生したとすると、 [1, 2] < (1, 2) が定義されなければなりません (多分 False を返すのでしょう)。 一方で、実際には (Python 3000 では!) そうした「混在モードの比較」演算は禁止すると明文化され TypeError を送出します。 詳しくは PEP 3100 および [14] を参照して下さい。 (これは同じ型の引数をもう一つ取る演算における一般的な問題の一例です。)

馬鹿の一つ覚え

ここに示すクラスはどれも唯一つのメソッド、たとえば __iter____len__ を表します。

Hashable

__hash__ を定義する基底クラスです。 __hash__ メソッドは整数を返さなければなりません。 抽象メソッドとしての実装は常に 0 を返すものです(役には立ちません)。 不変条件: 二つのクラス C1C2 がどちらも Hashable から派生しているとき、 o1C1 のインスタンスで o2C2 であって、条件 o1 == o2 が成り立つならば、 hash(o1) == hash(o2) を満たさなければなりません。 言い替えると、異なるハッシュ値をもつオブジェクトは等しくあってはなりません。

もう一つの制約はハッシュ可能オブジェクトが一度生成されたらその値 (== で比較される値) やハッシュ値を変更してはなりません。 これを保証できないクラスは Hashable を継承することができません。 特定のインスタンスでのみこの保証が破れてしまうならば、 それらのインスタンスに対する __hash__TypeError 例外を送出すべきです。

注意: このクラスのインスタンスであることは必ずしもオブジェクトが変更不可能であることを意味しません。 たとえばリストが要素になるタプルは変更不可能ではありません。 このようなタプルの __hash__ メソッドは TypeError を送出します。 (これは再帰的に各要素のハッシュを計算しているからで、 ハッシュ不可能な要素があると TypeError を送出するのです。)

Iterable
__iter__ を定義する基底クラスです。 __iter__ メソッドは常に Iterator (次で説明します) インスタンスを返さなければなりません。 抽象メソッドとしての実装は空のイテレータを返します。
Iterator
__next__ を定義する基底クラスです。 このクラスは Iterable から派生します。 抽象的メソッドとしての実装は StopIteration を送出します。 具象 __iter__ メソッドは self を返します。 IterableIterator の違いに気を付けましょう: Iterable は繰り返しに使えます、つまり __iter__ メソッドが使えます。 一方 Iterator は組み込みの関数 iter() が返すもので、 言い替えると __next__ メソッドが使えます。
Sized
__len__ を定義する基底クラスです。 __len__ メソッドは Integer (「数」の項を参照してください) >= 0 を返します。 抽象メソッドとしての実装は 0 を返します。 不変条件: クラス CSized と同時に Iterable からも派生しているならば、不変条件式 sum(1 for x in c) == len(c)C の任意のインスタンス c について成り立ちます。
Container
__contains__ を定義する基底クラスです。 __contains__ メソッドは bool を返さなければなりません。 抽象的メソッドとしての実装は False を返します。 不変条件: クラス CContainer と同時に Iterable からも派生しているならば、 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__")

この方法は登録の手間を取らせないという利点があります。 しかしながら、意味論はインスタンス属性 vs. クラス属性の間違いやすい意味論やクラスがそのメタクラスのインスタンスであることなどに起因して、 正確に正しく解釈するのが困難です。 __bases__ をチェックするのは望ましい意味論の近似に過ぎません。 腕試し: やってみましょう。でも、登録 API も機能するような形で実現して下さい。

集合

読み取り専用の集合と変更可能な集合の抽象クラスです。 一番基礎的な集合の演算といえば所属判定です。 これを x in s と書き、実装方法は s.__contains__(x) です。 この操作はすでに Container で定義しました。 というわけで、集合は大きさの決まった(sized) イテレート可能な(iterable) コンテナ(container)として定義し、さらに数学の集合論から来る不変条件を課します。

組み込み型 setMutableSet から派生します。 もう一つの組み込み型 frozensetSetHashable から派生します。

Set

このクラスは大きさの決まったイテレート可能なコンテナです。 つまり、 Sized, Iterable, Container のサブクラスです。 これら三つのサブクラスだからといって集合とは限らないわけですが! 集合には追加の不変条件があり、全ての要素はただ一度しか現れません (イテレートしてみれば判ります)。 さらに、部分集合/上位集合テストとなる不等号演算を実装する非抽象演算子を定義します。 一般に、数学的な意味での有限集合の不変条件を満たします。 [11]

異なる実装の Set 同士は安全に、(たいていは)効率的で正しく比較できます。 使うのは有限集合の部分集合/上位集合演算の数学的定義です。 順序演算は実際的な実装になっていますので、 サブクラスはこれを高速化するために再定義しても構いませんが、 意味論を壊さないようにして下さい。 SetSized から派生しますので、 __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 型にあった issubsetissuperset については関知しません。 これらはほとんど __le____ge__ の別名に過ぎません。

MutableSet

Set のサブクラスで、要素を加えたり取り除いたりするのために追加された操作を実装するものです。 それぞれのメソッドは Python 2 の set 型で知られる意味論を満たすようになっています (discard は例外で、これは Java をモデルにしています):

.add(x)
抽象的メソッドです。 返される型は bool で要素 x が集合に属していなければ追加するものです。 x が追加された場合は True を、既に同じ要素が属していれば False を返さなければなりません。 抽象実装は NotImplementedError を送出します。
.discard(x)
抽象的メソッドです。 返される型は bool で要素 x が集合に属していれば削除するものです。 x が存在していれば True を、存在していなければ False を返さなければなりません。 抽象実装は NotImplementedError を送出します。
.pop()
非抽象メソッドで、恣意的に選んだアイテムを削除して返します。 集合が空ならば、 KeyError を送出します。 デフォルト実装ではイテレータの返す最初の要素が削除対象になります。
.toggle(x)
非抽象メソッドで、返される型は boolx が集合に属していなければ追加し、 属していれば逆に削除します。 x が追加された時には True を、削除時には False を返さなければなりません。
.clear()
非抽象メソッドで、集合を空にします。 デフォルト実装では KeyError を受け取るまで self.pop() を繰り返し呼び出します。 (注意: これは単に新しい集合を作るより、 より速いアプローチで再実装したとしても、 ずっと遅くなることでしょう。 しかし、オブジェクトの同一性が重要な場合もあるのです。)

このクラスは他にその場での変更操作も提供しています: |=, &=, ^=, -= 。 これらの非抽象メソッドの右演算対象は任意の Iterable になります。 例外的に &= では Container でなければなりません。 この ABC では組み込み型 set で使うことができるほぼ同じことをする名前付きメソッドは提供されていません。

マッピング

読み取り専用のマッピングと変更可能なマッピングの抽象クラスです。 Mapping クラスはごく普通の読み取り専用マッピング API を表します。

組み込み型の dictMutableMapping から派生します。

Mapping

Container, Iterable, Sized のサブクラスです。 マッピングのキーは自然に集合を成します。 (キー, 値) ペア (タプルです) をアイテムとも呼びます。 アイテムも集合を成します。 メソッド:

.__getitem__(key)
抽象メソッドで、 key に対応する値を返すか、 KeyError を送出します。 デフォルトの実装はいつも KeyError を送出します。
.get(key, default=None)
非抽象メソッドで、 KeyError が発生しない場合に限り、 self[key] を返します。 発生したら default の値を返します。
.__contains__(key)
非抽象メソッドで、 self[key] 呼び出しで KeyError が送出されなければ True を、 それ以外は False を返します。
.__len__()
抽象メソッドで相異なるキーの個数 (つまりキー集合の長さ) を返します。
.__iter__()
抽象メソッドでキー集合の各キーをちょうど1回ずつ返します。
.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含まれません未解決問題: メソッドの仕様を書いて!

シーケンス

読み取り専用のシーケンスと変更可能なシーケンスの抽象クラスです。

MutableSequence からは組み込み型 listbyte が派生します。 tuplestrSequenceHashable からの派生です。

Sequence

Iterable, Sized, Container のサブクラスです。 新しい抽象メソッド __getitem__ を定義します。 そのメソッドにはある意味で複雑なシグニチャがあります: 整数を引数に呼ばれた場合には、シーケンス中の要素を返すか IndexError を送出します。 slice オブジェクトを引数に呼び出された場合には、 別の Sequence を返します。 非抽象メソッド __iter__ は要素のイテレーションに __getitem__ に整数 0, 1, … を引数に渡して、 IndexError が送出されるまで使います。 長さとイテレータの返す値の個数は一致すべきです。

未解決問題: 他にも __len__ と整数引数の __getitem__ だけにしか依存しないので具体的な実装を持ち得るメソッドの候補があります: __reversed__, index, count, __add__, __mul__

MutableSequence
Sequence のサブクラスで標準的な変更用メソッドを実装します。 変更用抽象メソッド: __setitem__ (整数インデクスとスライス), __delitem (同左), insert 。 変更用具象メソッド: append, reverse, extend, pop, remove 。 変更用具象演算子: +=, *= (これらはその場でオブジェクトを変更します)。 注意: この ABC は sort() を定義しません – 存在が要求されるのは真の list インスタンスだけです。

文字列

Python 3000 には少なくとも2種類の文字列型が存在することになりそうです: バイト列 (bytes) は MutableSequence から、 (ユニコード)文字列 (str) は SequenceHashable から、 それぞれ派生します。

未解決問題: これらの基底インタフェースを定義して、代替実装やサブクラスがどの種類のものかを知ることができるようにしてください。 この問題は新しい PEP (複数かもしれません) の主題になるでしょう。 (bytes 型については PEP 358 も一緒に考えなければなりません)。

ABCs vs. 代替案

このセクションでは ABC と他のアプローチを比較対照しようと思います。

ABCs vs. ダックタイピング

ABC の導入によってダックタイピングは滅びるのでしょうか? 多分そうはならないでしょう。 Python は __getitem__ メソッドを定義する時に BasicMappingSequence から派生するようには要求しませんし、 x[y] の文法も x にどちらかの ABC のインスタンスであることを要求しません。 依然として write メソッドさえ具えている「ファイル風」オブジェクトであれば、 sys.stdout に割り当てることができます。

もちろん、ユーザーが適切な基底クラスを使う方向に進むようにニンジンをぶらさげることはあるかもしれません。 たとえば特定の機能性に対するデフォルト実装であったり、 マッピングとシーケンスを見分ける能力を向上させることであったり。 しかしムチが飛ぶわけではありません。 hasattr(x, "__len__") で十分ということであれば、そのままで結構。 ABC は Python 2 に良い解決策が無かった、たとえばマッピングとシーケンスを見分けるといった問題を解決したいというものなのです。

ABCs vs. 総称関数

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 vs. インタフェース

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)