目次

前のトピックへ

8. エラーと例外

次のトピックへ

10. 標準ライブラリミニツアー

このページ

9. クラス

Python では、最小限の構文と意味付けを使ってクラス (class) のメカニズムを言語に追加しています。Python のクラスは、C++ と Modula-3 で見られるクラスメカニズムを混合したものです。モジュールがそうであるように、 Python におけるクラスでは、クラス定義とユーザとの間に絶対的な障壁をおかず、ユーザが礼儀正しく、 “定義に首を突っ込む” ことはないと あてにしています。とはいえ、クラスにおける最も重要な機能はそのままに、完全な力を持っています: クラスの継承 (inheritance) メカニズムでは、 複数の基底クラスを持つことができ、導出されたクラスでは基底クラスの任意のメソッドをオーバライド (override, 上書き) することができます。 メソッドでは、基底クラスのメソッドを同じ名前で呼び出すことができます。オブジェクトには任意のプライベートなデータを入れることができます。

C++ の用語では、全てのクラスメンバ (データメンバも含む) は public (公開されたデータ) であり、メンバ関数はすべて 仮想関数 (virtual) です。特別なコンストラクタ (constructor: 生成関数) やデストラクタ (destructor: 破壊関数) はありません。Module-3 にあるような、オブジェクトのメンバをメソッドから参照するために短縮した記法は使えません: メソッド関数の宣言では、オブジェクト自体を表すパラメタ第一引数に明示せねばなりません。第一引数のオブジェクトはメソッド呼び 出しの際に暗黙の引数として渡されます。Smalltalk に似て、クラスはそれ自体がオブジェクトです。とはいえ、これは広義のオブジェクトという意味で、 Python では全てのデータ型はオブジェクトです。このことは、import や名前変更といった操作のセマンティクスにつながります。 C++ や Modula-3 と違って、ユーザは組込みの型を基底クラスにして拡張を行えます。また、C++ とは同じで Modula-3 とは違う点として、特別な 構文を伴うほとんどの組み込み演算子 (算術演算子 (arithmetic operator) や添字表記) はクラスインスタンスで使うために再定義できます。

9.1. 用語について一言

クラスに関して広範に受け入れられている用語定義がないので、 Smalltalk と C++ の用語を場合に応じて使っていくことにします。 (オブジェクト指向における意味付けの方法は C++よりも Modula-3 のほうが Python に近いので Modula-3 の用語を使いたいのですが、 ほとんどの読者はそれを耳にしたことがないと思います。)

オブジェクトには個体性があり、同一のオブジェクトに (複数のスコープの) 複数の名前を割り当てることができます。この機能は他の言語では別名 (ailias) づけとして知られています。Python を一見しただけでは、別名づけの重要性は分からないことが多く、変更不能な基本型 (数値、文字列、タプル) を扱うときには無視して差し支えありません。しかしながら、別名付けには、リストや辞書、またプログラムの外部にある実体 (ファイル、ウィンドウ、など) を表現するためのほとんどの型が入った Python コードで意味付けを行う上で (意図的な!) 効果があります。 別名付けはいくつかの点でポインタのように振舞うので、通常はプログラムに利するように使われます。例えば、オブジェクトの受け渡しは、実装上は ポインタが渡されるだけなのでコストの低い操作になります; また、関数があるオブジェクトを引数として渡されたとき、関数の呼び出し側から オブジェクトに対する変更を見ることができます — これにより、 Pascal にあるような二つの引数渡し機構をもつ必要をなくしています。

9.2. Python のスコープと名前空間

クラスを紹介する前に、Python のスコープ規則についてあることを話しておかなければなりません。クラス定義はある巧みなトリックを名前空間に 施すので、何が起こっているのかを完全に理解するには、スコープと名前空間がどのように動作するかを理解する必要があります。ちなみに、この問題に関する知識は全ての Python プログラマにとって有用です。

まず定義から始めましょう。

名前空間 (namespace) とは、名前からオブジェクトへの対応付け (mapping) です。ほとんどの名前空間は、現状では Python の辞書として実装されていますが、そのことは通常は (パフォーマンス以外では) 目立つことはないし、将来は変更されるかもしれません。 名前空間の例には、組込み名の集合 (abs() 等の関数や組込み例外名)、モジュールないのグローバルな名前; 関数を呼び出したときの ローカルな名前、があります。その意味では、オブジェクトの属性からなる集合もまた、名前空間を形成します。名前空間について知っておくべき 重要なことは、異なった名前空間にある名前の間には全く関係がないということです; 例えば、二つの別々のモジュールの両方で関数 “maximize” という関数を定義することができ、定義自体は混同されることはありません — モジュールのユーザは名前の前にモジュール名をつけなければなりません。

ところで、 属性 という言葉は、ドットに続く名前すべてに対して使っています — 例えば式 z.real で、 real はオブジェクト z の属性です。厳密にいえば、モジュール内の名前に対する参照は属性の参照です: 式 modname.funcname では、 modname はあるモジュールオブジェクトで、 funcname はその属性です。この場合には、たまたまモジュールの属性とモジュール内の グローバルな名前の間にはこの場合はたまたま、モジュールの属性とモジュールで定義されているグローバル名の間には、直接的な対応付けがされます: これらの名前は 同じ名前空間を共有しているのです! [1]

属性は読取り専用にも、書き込み専用にもできます。後者の場合、属性に代入することができます。モジュール属性は書込み可能です: modname.the_answer = 42 と書くことができます。書込み可能な属性は、 del 文で削除することも できます。例えば、 del modname.the_answer は、 modname で指定されたオブジェクトから属性 the_answer を除去します。

名前空間は様々な時点で作成され、その寿命も様々です。組み込みの名前が入った名前空間は Python インタプリタが起動するときに 作成され、決して削除されることはありません。モジュールのグローバルな名前空間は、モジュール定義が読み込まれたときに作成されます; 通常、 モジュールの名前空間は、インタプリタが終了するまで残ります。インタプリタのトップレベルで実行された文は、スクリプトファイルから 読み出されたものでも対話的に読み出されたものでも、 __main__ という名前のモジュールの一部分であるとみなされるので、独自の 名前空間を持つことになります。(組み込みの名前は実際にはモジュール内に存在します; そのモジュールは __builtin__ と呼ばれています。)

関数のローカルな名前空間は、関数が呼び出されたときに作成され、関数から戻ったときや、関数内で例外が送出され、かつ関数内で処理されなかった場合に削除されます。 (実際には、忘れられる、と言ったほうが起きていることをよく表しています。) もちろん、再帰呼出しのときには、各々の呼び出しで各自の ローカルな名前空間があります。

スコープ (scope) とは、ある名前空間が直接アクセスできる (directly accessible) ような、Python プログラムのテキスト上の領域です。 “直接アクセス可能” とは、限定なし (unqualified) である名前を参照 した際に、その名前空間から名前を見つけようと試みることを意味します。

スコープは静的に決定されますが、動的に使用されます。実行中はいつでも、直接名前空間にアクセス可能な、少なくとも三つの入れ子になったスコープがあります: 最初に検索される最も内側のスコープには、ローカルな名前が入っています; あるいは、最も内側のスコープを囲んでいる関数群のスコープで、最も 近傍のスコープから検索を始めます; 中間のスコープが次に検索され、このスコープには現在のモジュールのグローバルな名前が入っています; (最後に検索される) 最も外側のスコープは、組み込みの名前が入った名前空間です。

名前がグローバルであると宣言されている場合、その名前に対する参照や代入は全て、モジュールのグローバルな名前の入った中間のスコープに 対して直接行われます。そうでない場合、最も内側のスコープより外側にある変数は全て読み出し専用(そのような変数に対する書き込みは、単に 新しい ローカル変数もっとも内側のスコープで作成し、外部のスコープの値は変化しません)となります。

通常、ローカルスコープは (プログラムテキスト上の) 現在の関数のローカルな名前を参照します。関数の外側では、ローカルスコープは グローバルな名前空間と同じ名前空間: モジュールの名前空間を参照します。クラスを定義すると、ローカルスコープの中にもう一つ名前空間が置かれます。

スコープはテキスト上で決定されていると理解することが重要です: モジュール内で定義される関数のグローバルなスコープは、 関数がどこから呼び出されても、どんな別名をつけて呼び出されても、そのモジュールの名前空間になります。反対に、実際の名前の検索は実行時に動的に行われます — とはいえ、言語の定義は、”コンパイル” 時の静的な名前解決の方向に進化しているので、動的な名前解決に頼ってはいけません! (事実、ローカルな変数は既に静的に決定されています。)

Python 特有の癖として、代入を行うと – どの global 文も有効でない場合は – 名前がいつも最も内側のスコープに入るというものがあります。代入はデータのコピーを行いません — 単に名前をオブジェクトに結びつける (bind) だけです。オブジェクトの削除でも同じです: del x は、 x をローカルスコープが参照している名前空間から削除します。実際、新たな名前を導入する操作は全てローカルスコープを用います: とりわけ、 import 文や関数定義は、モジュールや関数の名前をローカルスコープに結び付けます。(global 文を使えば、 特定の変数がグローバルスコープにあることを示せます。)

9.3. クラス初見

クラスでは、新しい構文を少しと、三つの新たなオブジェクト型、そして新たな意味付けをいくつか取り入れています。

9.3.1. クラス定義の構文

クラス定義の最も単純な形式は、以下のようになります:

class ClassName:
    <文-1>
    .
    .
    .
    <文-N>

関数定義 (def 文) と同様、クラス定義が効果をもつにはまず実行しなければなりません。 (クラス定義を if 文の分岐先や関数内部に置くことも、考え方としてはありえます。)

実際には、クラス定義の内側にある文は、通常は関数定義になりますが、他の文を書くこともでき、それがそれが役に立つこともあります — これについては後で述べます。クラス内の関数定義は通常、メソッドの呼び出し規約で決められた独特の形式の引数リストを持ちます — これについても後で述べます。

クラス定義に入ると、新たな名前空間が作成され、ローカルな名前空間として使われます — 従って、ローカルな変数に対する 全ての代入はこの新たな名前空間に名要ります。特に、関数定義を行うと、新たな関数の名前はこの名前空間に結び付けられます。

クラス定義から普通に (定義の終端に到達して) 抜けると、 クラスオブジェクト (class object) が生成されます。 クラスオブジェクトは、基本的にはクラス定義で作成された名前空間の内容をくるむラッパ (wrapper) です; クラスオブジェクトについては 次の節で詳しく学ぶことにします。(クラス定義に入る前に有効だった) 元のローカルスコープが復帰し、生成されたクラスオブジェクトは 復帰したローカルスコープにクラス定義のヘッダで指定した名前 (上の例では ClassName) で結び付けられます。

9.3.2. クラスオブジェクト

クラス・オブジェクトでは2種類の演算: 属性参照とインスタンス生成をサポートしています。

属性参照 (attribute reference) は、Python におけるすべての属性参照で使われている標準的な構文、 obj.name を使います。クラスオブジェクトが生成された際にクラスの名前空間にあった名前すべてが有効な属性名です。従って、以下のようなクラス定義:

class MyClass:
    """A simple example class"""
    i = 12345
    def f(self):
        return 'hello world'

では、 MyClass.iMyClass.f は妥当な属性参照であり、それぞれ整数と関数オブジェクトを返します。 クラス属性に代入を行うこともできます。従って、 MyClass.i の値を代入して変更できます。 __doc__ も有効な属性で、そのクラスに属している docstring、この場合は "A simple example class" を返します。

クラスの インスタンス生成 (instantiation) には関数のような表記法を使います。クラスオブジェクトのことを、単にクラスの新しい インスタンスを返すパラメタを持たない関数かのように扱います。例えば (上記のクラスでいえば):

x = MyClass()

は、クラスの新しい インスタンス (instance) を生成し、そのオブジェクトをローカル変数 x へ代入します。

インスタンス生成操作 (クラスオブジェクトの “呼出し”) を行うと、空のオブジェクト (empty object) を生成します。多くのクラスは、 オブジェクトを作成する際に、カスタマイズされた特定の初期状態になってほしいと望んでいます。従って、クラスでは __init__() という名前の特別なメソッド定義することができます。例えば以下のようにします:

def __init__(self):
    self.data = []

クラスが __init__() メソッドを定義している場合、クラスのインスタンスを生成すると、新しく生成されたクラスインスタンスに対して自動的に __init__() を呼び出します。従って、この例では、新たな初期済みのインスタンスを以下のようにして得ることができます:

x = MyClass()

もちろん、より大きな柔軟性を持たせるために、 __init__() メソッドに複数の引数をもたせることができます。 その場合、クラスのインスタンス生成操作に渡された引数は __init__() に渡されます。例えば以下のように:

>>> class Complex:
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)

9.3.3. インスタンスオブジェクト

ところで、インスタンスオブジェクトを使うと何ができるのでしょうか?インスタンスオブジェクトが理解できる唯一の操作は、属性の参照です。 有効な属性の名前には二種類(データ属性およびメソッド)あります。

データ属性 (data attribute) は、これは Smalltalk の “インスタンス変数” (instance variable) や C++の “データメンバ” (data member) に相当します。データ属性を宣言する必要はありません; ローカルな変数と同様に、 これらの属性は最初に代入された時点で湧き出てきます。例えば、上で生成した MyClass のインスタンス x に対して、 以下のコード断片を実行すると、値 16 を印字し、 x の痕跡は残りません。

x.counter = 1
while x.counter < 10:
    x.counter = x.counter * 2
print x.counter
del x.counter

もうひとつのインスタンス属性は メソッド (method) です。メソッドとは、オブジェクトに “属している” 関数のことです。(Python では、メソッドという用語はクラスインスタンスだけのものではありません: オブジェクト型にもメソッドを持つことができます。例えば、リストオブジェクトには、 append, insert, remove, sort などといったメソッドがあります。とはいえ、以下では特に明記しない限り、クラスの インスタンスオブジェクトのメソッドだけを意味するものとして使うことにします。)

インスタンスオブジェクトで有効なメソッド名は、そのクラスによります。定義により、クラスの全てのo関数オブジェクトである属性が インスタンスオブジェクトの妥当なメソッド名に決まります。従って、例では、 MyClass.f は関数なので、 x.f はメソッドの参照として有効です。しかし、 MyClass.i は関数ではないので、 x.i はメソッドの参照 として有効ではありません。 x.fMyClass.f と同じものではありません — 関数オブジェクトではなく、 メソッドオブジェクト (method object) です。

9.3.4. メソッドオブジェクト

普通、メソッドはバインドされた直後に呼び出されます:

x.f()

MyClass の例では、上のコードは文字列 'hello world' を返すでしょう。 しかしながら、必ずしもメソッドをその場で呼び出さなければならないわけではありません: x.f はメソッドオブジェクトであり、 どこかに記憶しておいて後で呼び出すことができます。例えば以下のコード:

xf = x.f
while True:
    print xf()

は、 hello world を時が終わるまで印字し続けるでしょう。

メソッドが呼び出されるときには実際には何が起きているのでしょうか? f() の関数定義では引数を一つ指定していたにもかかわらず、上記では x.f が引数なしで呼び出されたことに気付いているかもしれませんね。引数はどうなったのでしょうか?たしか、引数が必要な関数を 引数無しで呼び出すと、Python が例外を送出するはずです — たとえその引数が実際には使われなくても…。

実際、もう答は想像できているかもしれませんね: メソッドについて特別なこととして、オブジェクトが関数の第 1 引数として渡される、 ということがあります。我々の例では、 x.f() という呼び出しは、 MyClass.f(x) と厳密に等価なものです。一般に、 n 個の引数リストもったメソッドの呼出しは、そのメソッドのオブジェクトを最初の引数の前に挿入した引数リストでメソッドに対応する関数を呼び出すことと等価です。

もしもまだメソッドの働きかたを理解できなければ、一度実装を見てみると事情がよく分かるかもしれません。 データ属性ではないインスタンス属性が参照された時は、そのクラスが検索されます。 その名前が有効なクラス属性を表している関数オブジェクトなら、インスタンスオブジェクトと見つかった関数オブジェクト (へのポインタ) を抽象オブジェクト: すなわちメソッドオブジェクトにパック (pack) して作成します。 メソッドオブジェクトは、引数リストを伴って呼び出される際に再度アンパック (unpack) され、新たな引数リストがインスタンスオブジェクトとオリジナルの引数リストから構成され、関数オブジェクトは新たな引数リストを使って呼び出されます。

9.4. いろいろな注意点

データ属性は同じ名前のメソッド属性を上書きしてしまいます; 大規模なプログラムでみつけにくいバグを引き起こすことがある この偶然的な名前の衝突を避けるには、衝突の可能性を最小限にするような規約を使うのが賢明です。 可能な規約としては、メソッド名を大文字で始める、データ属性名の先頭に短い一意的な文字列 (あるいはただの下線) をつける、またメソッドには動詞、 データ属性には名詞を用いる、などがあります。

データ属性は、メソッドから参照できると同時に、通常のオブジェクトのユーザ (“クライアント”) からも参照できます。言い換えると、 クラスは純粋な抽象データ型として使うことができません。実際、 Python では、データ隠蔽を補強するための機構はなにもありません — データの隠蔽はすべて規約に基づいています。(逆に、C 言語で書かれた Python の実装では実装の詳細を完全に隠蔽し、必要に応じてオブジェクト へのアクセスを制御できます; この機構は C 言語で書かれた Python 拡張で使うことができます)

クライアントはデータ属性を注意深く扱うべきです — クライアントは、メソッドを使うことで維持しているデータ属性の不変式を踏みにじり、 台無しにするかもしれません。クライアントは、名前の衝突が回避されている限り、メソッドの有効性に 影響を及ぼすことなくインスタンスに独自の属性を追加することができる、ということに注意してください — ここでも、名前付けの規約は 頭痛の種を無くしてくれます。

データ属性を (またはその他のメソッドも!) メソッドの中で参照するための短縮された記法はありません。私は、この仕様が実際にメソッドの 可読性を高めていると考えています: あるメソッドを眺めているときにローカルな変数とインスタンス変数を混同する可能性はまったくありません。

しばしば、メソッドの最初の引数を、しばしば self と呼びます。この名前付けは単なる慣行でしかありません: self という名前は、 Python では何ら特殊な意味を持ちません。 (とはいえ、この慣行に従わないと、コードは他の Python プログラマにとってやや読みにくいものとなります。 また、 クラスブラウザ (class browser) プログラムがこの慣行をあてにして書かれているかもしれません。)

クラス属性である関数オブジェクトはいずれも、そのクラスのインスタンスのためのメソッドを定義しています。関数定義は、テキスト上では クラス定義の中に入っていなければならないわけではありません: 関数オブジェクトをクラスのローカルな変数の中に代入するのも OK です。 例えば以下のコードのようにします:

# クラスの外側で定義された関数
def f1(self, x, y):
    return min(x, x+y)

class C:
    f = f1
    def g(self):
        return 'hello world'
    h = g

これで、 fg 、および h は、すべて C の属性であり関数オブジェクトを参照しています。 従って、これら全ては、 C のインスタンスのメソッドとなります — hg と全く等価です。これを実践しても、大抵は 単にプログラムの読者に混乱をもたらすだけなので注意してください。

メソッドは、 self 引数のメソッド属性を使って、他のメソッドを呼び出すことができます:

class Bag:
    def __init__(self):
        self.data = []
    def add(self, x):
        self.data.append(x)
    def addtwice(self, x):
        self.add(x)
        self.add(x)

メソッドは、通常の関数と同じようにして、グローバルな名前を参照してもかまいません。あるメソッドに関連付けられたグローバルなスコープは、 クラス定義の入っているモジュールになります。 (クラス自体はグローバルなスコープとして用いられることはありません!) メソッドでグローバルな データを使う良い理由はほとんどありませんが、グローバルなスコープを使う合法的な使い方は多々あります: 一つ挙げると、メソッド内では、グローバルなスコープに import された関数やモジュールや、その中で定義された関数やクラスを使うことができます。通常、メソッドの入っているクラス自体はグローバルなスコープ内で 定義されています。次の章では、メソッドが自分のクラスを参照する理由として正当なものを見てみましょう!

個々の値はオブジェクトなので、 クラス ( とも言います) を持っています。 それは object.__class__ に保持されています。

9.5. 継承

言うまでもなく、継承の概念をサポートしない言語機能は “クラス” と呼ぶに値しません。導出クラス (derived class) を定義する構文は以下のように なります:

class DerivedClassName(BaseClassName):
    <文-1>
    .
    .
    .
    <文-N>

基底クラス (base class) の名前 BaseClassName は、 導出クラス定義の入っているスコープで定義されていなければなりません。基底クラス名のかわりに任意の式を入れることもできます。これは以下のように、

class DerivedClassName(modname.BaseClassName):

基底クラスが別モジュールで定義されているときに便利なことがあります。

導出クラス定義の実行は、基底クラスの場合と同じように進められます。クラスオブジェクトが構築される時、基底クラスが記憶されます。 記憶された基底クラスは、属性参照を解決するために使われます: 要求された属性がクラスに見つからなかった場合、基底クラスに検索 が進みます。この規則は、基底クラスが他の何らかのクラスから導出されたものであった場合、再帰的に適用されます。

導出クラスのインスタンス化では、特別なことは何もありません: DerivedClassName() はクラスの新たなインスタンスを生成します。 メソッドの参照は以下のようにしてい解決されます: まず対応するクラス属性が検索されます。検索は、必要に応じ、基底クラス連鎖を下って行われ、 検索の結果として何らかの関数オブジェクトがもたらされた場合、メソッド参照は有効なものとなります。

導出クラスは基底クラスのメソッドを上書き (override) してもかまいません。メソッドは同じオブジェクトの別のメソッドを呼び出す際に何ら特殊な権限を 持ちません。このため、ある基底クラスのメソッドが、同じ基底クラスで定義されているもう一つのメソッド呼び出しを行っている場合、 導出クラスで上書きされた何らかのメソッドが呼び出されることになるかもしれません。 (C++ プログラマへ: Python では、すべてのメソッドは事実上 virtual です。)

導出クラスで上書きしているメソッドでは、実際は単に基底クラスの同名のメソッドを置き換えるだけではなく、拡張を行いたいかもしれません。 基底クラスのメソッドを直接呼び出す簡単な方法があります: 単に BaseClassName.methodname(self, arguments) を呼び出すだけです。この仕様は、場合によってはクライアントでも役に立ちます。 (この呼び出し方が動作するのは、基底クラスがグローバルなスコープ内で 定義されているか、直接 import されている場合だけなので注意してください。)

Python には継承に関係する 2 つの組み込み関数があります:

  • isinstance() を使うとオブジェクトの型が調べられます: isinstance(obj, int)obj.__class__intint の導出クラスの場合に限り True になります。
  • issubclass() を使うとクラスの継承関係が調べられます: boolint のサブクラスなので issubclass(bool, int)True です。しかし、 unicodestr のサブクラスではない (単に共通の祖先 basestring を共有している) ので issubclass(unicode, str)False です。

9.5.1. 多重継承

Python では、限られた形式の多重継承 (multiple inheritance) もサポートしています。複数の基底クラスをもつクラス定義は以下のように なります:

class DerivedClassName(Base1, Base2, Base3):
    <文-1>
    .
    .
    .
    <文-N>

旧形式のクラスでは、 解決規則は深さ優先 (depth-first)、左から右へ (left-to-right) だけです。従って、ある属性が DerivedClassName で見つからなければ Base1 で検索され、次に Base1 の 基底クラスで (再帰的に) 検索されます。それでも見つからなければはじめて Base2 で検索される、といった具合です。

(人によっては、幅優先 (breadth first) — Base2Base3 を検索してから Base1 の基底クラスで検索する — のほうが自然のように見えます。しかしながら、幅優先の検索では、 Base1 の特定の属性のうち、実際に定義されているのが Base1 なのか、その基底クラスなのかを知らなければ、 Base2 の属性との名前衝突がどんな結果をもたらすのか分からないことになります。深さ優先規則では、 Base1 の直接の 属性と継承された属性とを区別しません。)

new-style class では、 super() が適切に呼び出せるようにするためにメソッドの解決順序は動的に変わります。 このアプローチは他の多重継承のある言語で call-next-method として知られており、単一継承しかない言語の super 呼び出しよりも強力です。

新形式のクラスについて、多重継承の全ての場合に 1 つかそれ以上のダイヤモンド継承 (少なくとも 1 つの祖先クラスに対し最も下のクラスから到達する経路が複数ある状態) があるので動的順序付けが必要です。 例えば、全ての新形式のクラスは object を継承しているので、どの多重継承でも object へ到達するための道は複数存在します。 基底クラスが複数回アクセスされないようにするために、動的アルゴリズムで検索順序を直列化し、各クラスで指定されている祖先クラスどうしの左から右への順序は崩さず、各祖先クラスを一度だけ呼び出し、かつ一様になる (つまり祖先クラスの順序に影響を与えずにサブクラス化できる) ようにします。 まとめると、これらの特徴のおかげで信頼性と拡張性のある多重継承したクラスを設計することができるのです。 さらに詳細を知りたければ、 http://www.python.org/download/releases/2.3/mro/ を見てください。

9.6. プライベート変数

クラスプライベート (class-private) の識別子に関して限定的なサポートがなされています。 __spam (先頭に二個以上の下線文字、末尾に 高々一個の下線文字) という形式の識別子、テキスト上では _classname__spam へと置換されるようになりました。ここで classname は、現在のクラス名から先頭の下線文字をはぎとった名前になります。このような難号化 (mangle) は、識別子の 文法的な位置にかかわらず行われるので、クラスプライベートなインスタンス変数やクラス変数、メソッド、グローバル変数、そしてインスタンスに含まれる変数を 定義するために利用できます。また、このクラスにとってプライベートなインスタンス変数を 他の クラスのインスタンスに格納するために 使うことさえできます。難号化した名前が 255 文字より長くなるときは、切り詰めが起こるかもしれません。 クラスの外側や、クラス名が下線文字だけからできているときには、難号化加工は起こりません。

名前の難号化は、クラスにおいて、 “プライベートな” インスタンス変数やメソッドを定義する際に、導出クラスで定義されるインスタンス変数を気に したり、クラスの外側のコードからインスタンス変数をいじりまわすことがないように簡単に定義できるようにするためのものです。 難号化の規則は主に不慮の事故を防ぐためのものだということに注意してください; 確信犯的な方法で、プライベートとされている変数にアクセス したり変更することは依然として可能なのです。デバッガのような特殊な状況では、この仕様は便利ですらあります。そのため、この抜け穴は塞がれていません。 (些細なバグ: 基底クラスと同じ名前のクラスを導出すると、基底クラスのプライベート変数を使えるようになります。)

execeval()execfile() へ渡されたコードでは、 呼出し元のクラス名を現在のクラスと見なさないことに注意してください; この仕様は global 文の効果と似ており、その効果もまた同様に、 バイトコンパイルされたコードに制限されています。同じ制約が getattr()setattr()delattr() にも適用されます。また、 __dict__ を直接参照するときにも適用されます。

9.7. 残りのはしばし

Pascal の “レコード (record)” や、C 言語の “構造体 (struct)” のような、名前つきのデータ要素を一まとめにするデータ型があると 便利なことがたまにあります。空のクラス定義を使うとうまくできます:

class Employee:
    pass

john = Employee() # 空の従業員レコードを造る

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000

ある特定の抽象データ型を要求する Python コードの断片には、そのデータ型のメソッドをエミュレーションするクラスを代わりに渡す ことができます。例えば、ファイルオブジェクトから何らかのデータを書式化する関数がある場合、 read()readline() を持つクラスを定義して、ファイルではなく文字列バッファからデータを書式するようにしておき、引数として渡すことができます。 (残念なことに、このテクニックには限界があります: クラスにはシーケンスの添字アクセスや算術演算などの特殊構文でアクセスされる操作が定義できず、”疑似ファイル” を sys.stdin に代入してもそこからインタープリタに入力データを読み込ませることはできません。)

インスタンスメソッドオブジェクトにもまた、属性があります: m.im_self はメソッド m() の属しているインスタンスオブジェクトで、 m.im_func はメソッドに対応する関数オブジェクトです。

9.7.1. 例外はクラスであってもよい

ユーザ定義の例外をクラスとして識別することもできます。このメカニズムを使って、拡張可能な階層化された例外を作成することができます。

新しく二つの (意味付け的な) 形式の raise 文ができました:

raise Class, instance

raise instance

第一の形式では、 instanceClass またはその導出クラスのインスタンスでなければなりません。第二の形式は以下の表記:

raise instance.__class__, instance

の短縮された記法です。

except 節には、文字列オブジェクトだけでなくクラスを並べることができます。 except 節のクラスは、同じクラスか基底クラスの例外のときに互換 (compatible) となります (逆方向では成り立ちません — 導出クラスの例外がリストされている except 節は基底クラスの例外と互換ではありません)。例えば、次のコードは、 B, C, D を順序通りに出力します:

class B:
    pass
class C(B):
    pass
class D(C):
    pass

for c in [B, C, D]:
    try:
        raise c()
    except D:
        print "D"
    except C:
        print "C"
    except B:
        print "B"

except 節が逆に並んでいた場合 (except B が最初にくる場合)、 B, B, B と出力されるはずだったことに注意してください — 最初に一致した except 節が駆動されるのです。

処理されないクラスの例外に対してエラーメッセージが出力されるとき、まずクラス名が出力され、続いてコロン、スペース、最後に組み込み関数 str() を使って文字列に変換したインスタンスが出力されます。

9.8. イテレータ (iterator)

すでに気づいているでしょうが、 for 文を使うとほとんどのコンテナオブジェクトにわたってループを行うことができます:

for element in [1, 2, 3]:
    print element
for element in (1, 2, 3):
    print element
for key in {'one':1, 'two':2}:
    print key
for char in "123":
    print char
for line in open("myfile.txt"):
    print line

こうしたアクセス方法は明確で、簡潔で、かつ便利なものです。イテレータの使用は Python 全体に普及していて、統一性をもたらしています。背後では、 for 文はコンテナオブジェクトの iter() を呼び出しています。この関数は next() メソッドの定義されたイテレータオブジェクトを返します。 next() メソッドは一度コンテナ内の要素に一度に一つづつアクセスします。コンテナ内にアクセスすべき要素がなくなると、 next()StopIteration 例外を送出し、 for ループを終了させます。実際にどのように動作するかを以下の例に示します:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> it.next()
'a'
>>> it.next()
'b'
>>> it.next()
'c'
>>> it.next()

Traceback (most recent call last):
  File "<stdin>", line 1, in ?
    it.next()
StopIteration

イテレータプロトコルの背後にあるメカニズムを一度目にすれば、自作のクラスにイテレータとしての振る舞いを追加するのは簡単です。 __iter__() メソッドを定義して、 next() メソッドを持つオブジェクトを返すようにしてください。クラス自体で next() を定義している場合、 __iter__() では単に self を返すようにできます:

class Reverse:
    "Iterator for looping over a sequence backwards"
    def __init__(self, data):
        self.data = data
        self.index = len(data)
    def __iter__(self):
        return self
    def next(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

>>> for char in Reverse('spam'):
...     print char
...
m
a
p
s

9.9. ジェネレータ (generator)

Generator は、イテレータを作成するための簡潔で強力なツールです。ジェネレータは通常の関数のように書かれますが、何らかのデータを返すときには yield 文を使います。 next() が呼び出されるたびに、ジェネレータは以前に中断した処理を再開します (ジェネレータは、全てのデータ値と最後にどの文が実行されたかを記憶しています)。以下の例を見れば、ジェネレータがとても簡単に作成できることがわかります:

def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]

>>> for char in reverse('golf'):
...     print char
...
f
l
o
g

ジェネレータを使ってできることは、前節で記述したクラスに基づいたイテレータを使えばできます。ジェネレータを使うとコンパクトに記述できるのは、 __iter__()next() メソッドが自動的に作成されるからです。

ジェネレータのもう一つの重要な機能は、呼び出しごとにローカル変数と実行状態が自動的に保存されるということです。これにより、 self.indexself.data といったインスタンス変数を使ったアプローチよりも簡単に関数を書くことができるようになります。

メソッドを自動生成したりプログラムの実行状態を自動保存するほかに、ジェネレータは終了時に自動的に StopIteration を送出します。 これらの機能を組み合わせると、通常の関数を書くのに比べ、全く苦労することなく簡単にイテレータを生成できます。

9.10. ジェネレータ式

単純なジェネレータなら、式を使って簡潔にコードする方法があります。リスト内包に似た構文の式ですが、各括弧ではなく丸括弧を使います。 ジェネレータ式は、関数の中でジェネレータをすぐに使いたいような状況のために用意されています。ジェネレータ式はコンパクトですが、 完全なジェネレータに比べてちょっと融通の効かないところがあります。同じ内容を返すリスト内包よりはメモリに優しいことが多いという利点もあります。

例:

>>> sum(i*i for i in range(10))                 # 平方和を求める
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec))         # 内積を求める
260

>>> from math import pi, sin
>>> sine_table = dict((x, sin(x*pi/180)) for x in range(0, 91))

>>> unique_words = set(word  for line in page  for word in line.split())

>>> valedictorian = max((student.gpa, student.name) for student in graduates)

>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1,-1,-1))
['f', 'l', 'o', 'g']

Footnotes

[1]例外が一つあります。モジュールオブジェクトには、秘密の読取り専用の属性 __dict__ があり、モジュールの名前空間を実装するために使われている辞書を返します; __dict__ という名前は属性ですが、グローバルな名前では ありません。この属性を利用すると名前空間の実装に対する抽象化を侵すことになるので、プログラムを検死するデバッガのような用途に限るべきです。