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) や 添字表記) はクラスインスタンスで使うために再定義することができます。
クラスに関して広範に受け入れられている用語定義がないので、 Smalltalk と C++ の用語を場合に応じて使っていくことに します。 (オブジェクト指向における意味付けの方法は C++よりも Modula-3 のほうが Python に近いので Modula-3 の用語を使いたいのですが、 ほとんどの読者はそれを耳にしたことがないと思います。)
オブジェクトには個体性があり、同一のオブジェクトに (複数のスコープの) 複数の名前を割り当てることができます。この機能は他の言語では 別名 (ailias) づけとして知られています。Python を一見しただけでは、 別名づけの重要性は分からないことが多く、変更不能な基本型 (数値、文字列、 タプル) を扱うときには無視して差し支えありません。 しかしながら、別名付けには、リストや辞書、またプログラムの外部 にある実体 (ファイル、ウィンドウ、など) を表現するためのほとんどの型 が入った Python コードで意味付けを行う上で (意図的な!) 効果があります。 別名付けはいくつかの点でポインタのように振舞うので、通常はプログラムに 利するように使われます。例えば、オブジェクトの受け渡しは、実装上は ポインタが渡されるだけなのでコストの低い操作になります; また、関数が あるオブジェクトを引数として渡されたとき、関数の呼び出し側から オブジェクトに対する変更を見ることができます -- これにより、 Pascal にあるような二つの引数渡し機構をもつ必要をなくしています。
クラスを紹介する前に、Python のスコープ規則についてあることを話して おかなければなりません。クラス定義はある巧みなトリックを名前空間に 施すので、何が起こっているのかを完全に理解するには、スコープと 名前空間がどのように動作するかを理解する必要があります。 ちなみに、この問題に関する知識は全ての Python プログラマにとって 有用です。
まず定義から始めましょう。
名前空間 (namespace) とは、 名前からオブジェクトへの対応付け (mapping) です。 ほとんどの名前空間は、現状では Python の辞書として実装されていますが、 そのことは通常は (パフォーマンス以外では) 目立つことはないし、 将来は変更されるかもしれません。 名前空間の例には、組込み名の集合 (abs() 等の関数や組込み 例外名)、モジュールないのグローバルな名前; 関数を呼び出したときの ローカルな名前、があります。その意味では、オブジェクトの属性から なる集合もまた、名前空間を形成します。名前空間について知っておくべき 重要なことは、異なった名前空間にある名前の間には全く関係がないと いうことです; 例えば、二つの別々のモジュールの両方で関数 ``maximize'' という関数を定義することができ、定義自体は混同され ることはありません -- モジュールのユーザは名前の前にモジュール名を つけなければなりません。
ところで、属性という言葉は、ドットに続く名前すべてに対して
使っています -- 例えば式 z.real
で、real
は
オブジェクト z
の属性です。厳密にいえば、モジュール内の名前に
対する参照は属性の参照です: 式 modname.funcname
では、
modname
はあるモジュールオブジェクトで、funcname
は
その属性です。この場合には、たまたまモジュールの属性とモジュール内の
グローバルな名前の間には
この場合はたまたま、モジュールの属性とモジュールで定義されている
グローバル名の間には、直接的な対応付けがされます: これらの名前は
同じ名前空間を共有しているのです!
9.1
属性は読取り専用にも、書き込み専用にもできます。
後者の場合、属性に代入することができます。
モジュール属性は書込み可能です: "modname.the_answer = 42" と書く
ことができます。書込み可能な属性は、del 文で削除することも
できます。例えば、"del modname.the_answer" は、modname
で指定されたオブジェクトから属性 the_answer を除去します。
名前空間は様々な時点で作成され、その寿命も様々です。 組み込みの名前が入った名前空間は Python インタプリタが起動するときに 作成され、決して削除されることはありません。モジュールのグローバルな 名前空間は、モジュール定義が読み込まれたときに作成されます; 通常、 モジュールの名前空間は、インタプリタが終了するまで残ります。 インタプリタのトップレベルで実行された文は、スクリプトファイルから 読み出されたものでも対話的に読み出されたものでも、__main__ という名前のモジュールの一部分であるとみなされるので、独自の 名前空間を持つことになります。(組み込みの名前は実際にはモジュール内 に存在します; そのモジュールは __builtin__ と呼ばれています。)
関数のローカルな名前空間は、関数が呼び出されたときに作成され、 関数から戻ったときや、関数内で例外が送出され、かつ関数内で処理され なかった場合に削除されます。 (実際には、忘れられる、と言ったほうが起きていることをよく表して います。) もちろん、再帰呼出しのときには、各々の呼び出しで各自の ローカルな名前空間があります。
スコープ (scope) とは、ある名前空間が直接アクセスできる (directly accessible) ような、Python プログラムのテキスト上の領域 です。 ``直接アクセス可能'' とは、限定なし (unqualified) である名前を参照 した際に、その名前空間から名前を見つけようと試みることを意味します。
スコープは静的に決定されますが、動的に使用されます。 実行中はいつでも、直接名前空間にアクセス可能な、少なくとも三つの 入れ子になったスコープがあります: 最初に検索される最も内側のスコープには、ローカルな名前が入っています; あるいは、最も内側のスコープを囲んでいる関数群のスコープで、最も 近傍のスコープから検索を始めます; 中間のスコープが次に検索され、 このスコープには現在のモジュールのグローバルな名前が入っています; (最後に検索される) 最も外側のスコープは、組み込みの名前が入った 名前空間です。
名前がグローバルであると宣言されている場合、その名前に対する参照や 代入は全て、モジュールのグローバルな名前の入った中間のスコープに 対して直接行われます。そうでない場合、最も内側のスコープより外側に ある変数は全て読み出し専用となります。
通常、ローカルスコープは (プログラムテキスト上の) 現在の関数の ローカルな名前を参照します。関数の外側では、ローカルスコープは グローバルな名前空間と同じ名前空間: モジュールの名前空間を参照します。 クラスを定義すると、ローカルスコープの中にもう一つ名前空間が置かれ ます。
スコープはテキスト上で決定されていると理解することが重要です: モジュール内で定義される関数のグローバルなスコープは、 関数がどこから呼び出されても、どんな別名をつけて呼び出されても、 そのモジュールの名前空間になります。反対に、実際の名前の検索は 実行時に動的に行われます -- とはいえ、言語の定義は、``コンパイル'' 時の静的な名前解決の方向に進化しているので、 動的な名前解決に頼ってはいけません! (事実、ローカルな変数は既に 静的に決定されています。)
Python 特有の癖として、代入を行うと名前がいつも最も内側のスコープに
入るというものがあります。代入はデータのコピーを行いません --
単に名前をオブジェクトに結びつける (bind) だけです。オブジェクトの削除
でも同じです: "del x" は、x
をローカルスコープが参照している
名前空間から削除します。実際、新たな名前を導入する操作は全てローカル
スコープを用います: とりわけ、 import 文や関数定義は、モジュールや
関数の名前をローカルスコープに結び付けます。(global 文を使えば、
特定の変数がグローバルスコープにあることを示せます。)
クラスでは、新しい構文を少しと、三つの新たなオブジェクト型、そして 新たな意味付けをいくつか取り入れています。
クラス定義の最も単純な形式は、以下のようになります:
class ClassName: <文-1> . . . <文-N>
関数定義 (def 文) と同様、クラス定義が効果をもつには まず実行しなければなりません。 (クラス定義を if 文の分岐先や関数内部に置くことも、 考え方としてはありえます。)
実際には、クラス定義の内側にある文は、通常は関数定義になりますが、 他の文を書くこともでき、それがそれが役に立つこともあります -- これについては後で述べます。クラス内の関数定義は通常、メソッドの 呼び出し規約で決められた独特の形式の引数リストを持ちます -- これについても後で述べます。
クラス定義に入ると、新たな名前空間が作成され、ローカルな 名前空間として使われます -- 従って、ローカルな変数に対する 全ての代入はこの新たな名前空間に名要ります。特に、関数定義を 行うと、新たな関数の名前はこの名前空間に結び付けられます。
クラス定義から普通に (定義の終端に到達して) 抜けると、 クラスオブジェクト (class object) が生成されます。 クラスオブジェクトは、基本的にはクラス定義で作成された名前空間の 内容をくるむラッパ (wrapper) です; クラスオブジェクトについては 次の節で詳しく学ぶことにします。(クラス定義に入る前に有効だった) 元のローカルスコープが復帰し、生成されたクラスオブジェクトは 復帰したローカルスコープにクラス定義のヘッダで指定した名前 (上の例では ClassName) で結び付けられます。
クラス・オブジェクトでは2種類の演算: 属性参照とインスタンス生成を サポートしています。
属性参照 (attribute reference) は、Python におけるすべての
属性参照で使われている標準的な構文、 obj.name
を使います。
クラスオブジェクトが生成された際にクラスの名前空間にあった名前すべてが
有効な属性名です。従って、以下のようなクラス定義:
class MyClass: "A simple example class" i = 12345 def f(self): return 'hello world'
では、MyClass.i
と MyClass.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)
ところで、インスタンスオブジェクトを使うと何ができるのでしょうか? インスタンスオブジェクトが理解できる唯一の操作は、属性の参照です。 有効な属性の名前には二種類あります。
一つ目の属性の種類は データ属性 (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 などといった メソッドがあります。とはいえ、以下では特に明記しない限り、クラスの インスタンスオブジェクトのメソッドだけを意味するものとして使うことに します。)
インスタンスオブジェクトで有効なメソッドは、そのクラスによって決まります。
定義により、クラスの全ての属性は、(ユーザが定義した) 関数オブジェクト
インスタンスオブジェクトの妥当なメソッド名は、そのクラスによって
決まります。
定義により、(利用者定義の) 関数オブジェクトになっているクラス属性は全て、
そのインスタンスの対応するメソッドとなります。従って、例では、
MyClass.f
は関数なので、 x.f
はメソッドの参照として有効です。
しかし、MyClass.i
は関数ではないので、 x.i
はメソッドの参照
として有効ではありません。x.f
は MyClass.f
と同じものでは
ありません -- 関数オブジェクトではなく、
メソッドオブジェクト (method object) です。
普通、メソッドはその場ですぐに呼び出されます:
x.f()
私たちの例では、上のコードは文字列 '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) され、新たな引数リストがインスタンスオブジェクト とオリジナルの引数リストから新たな引数リストが構成され、新たな引数 リストを使って関数オブジェクトを呼び出します。
データ属性は同じ名前のメソッド属性を上書きしてしまいます; 大規模なプログラムでみつけにくいバグを引き起こすことがある この偶然的な名前の衝突を避けるには、衝突の可能性を最小限にするような 規約を使うのが賢明です。 可能な規約としては、メソッド名を大文字で始める、データ属性名の先頭に 短い一意的な文字列 (あるいはただの下線) をつける、またメソッドには動詞、 データ属性には名詞を用いる、などがあります。
データ属性は、メソッドから参照できると同時に、通常のオブジェクトの ユーザ (``クライアント'') からも参照できます。言い換えると、 クラスは純粋な抽象データ型として使うことができません。実際、 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
これで、f
、 g
、および h
は、すべて
C の属性であり関数オブジェクトを参照しています。
従って、これら全ては、C のインスタンスのメソッドとなります --
h
は g
と全く等価です。これを実践しても、大抵は
単にプログラムの読者に混乱をもたらすだけなので注意してください。
メソッドは、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 された関数やモジュールや、 その中で定義された関数やクラスを使うことができます。 通常、メソッドの入っているクラス自体はグローバルなスコープ内で 定義されています。次の章では、メソッドが自分のクラスを参照する理由 として正当なものを見てみましょう!
言うまでもなく、継承の概念をサポートしない言語機能は ``クラス'' と呼ぶに 値しません。導出クラス (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 では、限られた形式の多重継承 (multiple inheritance) も サポートしています。複数の基底クラスをもつクラス定義は以下のように なります:
class DerivedClassName(Base1, Base2, Base3): <文-1> . . . <文-N>
多重継承への意味付けを説明する上で必要な唯一の規則は、クラス属性の 参照を行うときに用いられる名前解決の規則 (resolution rule) です。 解決規則は深さ優先 (depth-first)、左から右へ (left-to-right) と なっています。従って、ある属性が DerivedClassName で 見つからなければ Base1 で検索され、次に Base1 の 基底クラスで (再帰的に) 検索されます。それでも見つからなければ はじめて Base2 で検索される、といった具合です。
(人によっては、幅優先 (breadth first) -- Base2 と Base3 を検索してから Base1 の基底クラスで検索する -- のほうが自然のように見えます。しかしながら、幅優先の検索では、 Base1 の特定の属性のうち、実際に定義されているのが Base1 なのか、その基底クラスなのかを知らなければ、 Base2 の属性との名前衝突がどんな結果をもたらすのか 分からないことになります。深さ優先規則では、 Base1 の直接の 属性と継承された属性とを区別しません。)
Python では偶然的な名前の衝突を慣習に頼って回避しているので、 見境なく多重継承の使用すると、メンテナンスの悪夢に陥ることは明らかです。 多重継承に関するよく知られた問題は、二つのクラスから導出された クラスがたまたま共通の基底クラスを持つ場合です。 この場合になにが起こるかを結論することは簡単です (インスタンスは 共通の基底クラスで使われている ``インスタンス変数'' の単一の コピーを持つことになります) が、この意味付けが何の役に立つのかは 明らかではありません。
クラスプライベート (class-private) の識別子に関して限定的なサポート
がなされています。__spam
(先頭に二個以上の下線文字、末尾に
高々一個の下線文字) という形式の識別子、テキスト上では
_classname__spam
へと置換されるようになりました。
ここで classname
は、現在のクラス名から先頭の下線文字を
はぎとった名前になります。このような難号化 (mangle) は、識別子の
文法的な位置にかかわらず行われるので、クラスプライベートな
インスタンス変数やクラス変数、メソッド、そしてグローバル変数を
定義するための利用できます。また、このクラスにとってプライベートな
インスタンス変数を 他の クラスのインスタンスに格納するために
使うことさえできます。難号化した名前が 255 文字より長くなるときは、
切り詰めが起こるかもしれません。
クラスの外側や、クラス名が下線文字だけからできているときには、
難号化加工は起こりません。
名前の難号化は、クラスにおいて、 ``プライベートな'' インスタンス変数や メソッドを定義する際に、導出クラスで定義されるインスタンス変数を気に したり、クラスの外側のコードからインスタンス変数をいじりまわすことが ないように簡単に定義できるようにするためのものです。 難号化の規則は主に不慮の事故を防ぐためのものだということに注意して ください; 確信犯的な方法で、プライベートとされている変数にアクセス したり変更することは依然として可能なのです。デバッガのような特殊な 状況では、この仕様は便利ですらあります。そのため、この抜け穴は 塞がれていません。 (些細なバグ: 基底クラスと同じ名前のクラスを導出すると、基底クラスの プライベート変数を使えるようになります。)
exec
や eval()
や evalfile()
へ渡されたコードでは、
呼出し元のクラス名を現在のクラスと見なさないことに注意してください;
この仕様は global
文の効果と似ており、その効果もまた同様に、
バイトコンパイルされたコードに制限されています。
同じ制約が getattr()
と setattr()
と delattr()
にも適用されます。また、__dict__
を直接参照するときにも適用されます。
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() を持つクラス を定義して、ファイルではなく文字列バッファからデータを書式するように しておき、引数として渡すことができます。
インスタンスメソッドオブジェクトにもまた、属性があります:
m.im_self
はメソッドの属しているインスタンスオブジェクトで、
m.im_func
はメソッドに対応する関数オブジェクトです。
ユーザ定義の例外をクラスとして識別することもできます。このメカニズムを 使って、拡張可能な階層化された例外を作成することができます。
新しく二つの (意味付け的な) 形式の raise 文ができました:
raise Class, instance raise instance
第一の形式では、instance
は Class またはその導出クラスの
インスタンスでなければなりません。
第二の形式は以下の表記:
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() を使って文字列に変換したインスタンスが出力されます。
すでに気づいているでしょうが、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 "<pyshell#6>", line 1, in -toplevel- 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
ジェネレータは、イテレータを作成するための簡潔で強力なツールです。 ジェネレータは通常の関数のように書かれますが、何らかのデータを返すときには 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.index
や
self.data
といったクラス変数を使ったアプローチよりも簡単に
関数を書くことができるようになります。
メソッドを自動生成したりプログラムの実行状態を自動保存するほかに、 ジェネレータは終了時に自動的に StopIteration を送出します。 これらの機能を組み合わせると、通常の関数を書くのに比べ、全く苦労する ことなく簡単にイテレータを生成できます。
単純なジェネレータなら、式を使って簡潔にコードする方法があります。 リスト内包に似た構文の式ですが、各括弧ではなく丸括弧を使います。 ジェネレータ式は、関数の中でジェネレータをすぐに使いたいような状況 のために用意されています。ジェネレータ式はコンパクトですが、 完全なジェネレータに比べてちょっと融通の効かないところがあります。 同じ内容を返すリスト内包よりはメモリに優しいことが多いという利点も あります。
例:
>>> 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']