9. クラス¶
Python は、他のプログラミング言語と比較して、最小限の構文と意味付けを使ってクラスを言語に追加しています。Python のクラスは、C++ と Modula-3 のクラスメカニズムを混ぜたものです。Python のクラス機構はオブジェクト指向プログラミングの標準的な機能を全て提供しています。クラスの継承メカニズムは、複数の基底クラスを持つことができ、派生クラスで基底クラスの任意のメソッドをオーバライドすることができます。メソッドでは、基底クラスのメソッドを同じ名前で呼び出すことができます。オブジェクトには任意の種類と数のデータを格納することができます。モジュールと同じく、クラス機構も Python の動的な性質に従うように設計されています。クラスは実行時に生成され、生成後に変更することができます。
C++ の用語で言えば、通常のクラスメンバ (データメンバも含む) は (プライベート変数 に書かれている例外を除いて) public であり、メンバ関数はすべて 仮想関数(virtual) です。 Modula-3 にあるような、オブジェクトのメンバをメソッドから参照するための短縮した記法は使えません: メソッド関数の宣言では、オブジェクト自体を表す第一引数を明示しなければなりません。第一引数のオブジェクトはメソッド呼び出しの際に暗黙の引数として渡されます。 Smalltalk に似て、クラスはそれ自体がオブジェクトです。そのため、 import や名前変更といった操作が可能です。 C++ や Modula-3 と違って、ユーザーは組込み型を基底クラスにして拡張を行えます。また、C++ とは同じで Modula-3 とは違う点として、特別な構文を伴うほとんどの組み込み演算子 (算術演算子 (arithmetic operator) や添字表記) はクラスインスタンスで使うために再定義できます。
(クラスに関して普遍的な用語定義がないので、 Smalltalk と C++ の用語を場合に応じて使っていくことにします。 C++ よりも Modula-3 の方がオブジェクト指向の意味論が Python に近いので、 Modula-3 の用語を使いたいのですが、ほとんどの読者は Modula-3 についてしらないでしょうから。)
9.1. 名前とオブジェクトについて¶
オブジェクトには個体性があり、同一のオブジェクトに(複数のスコープから) 複数の名前を割り当てることができます。この機能は他の言語では別名づけ(alias) として知られています。 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__
という名前のモジュールの一部分であるとみなされるので、独自の名前空間を持つことになります。 (組み込みの名前は実際にはモジュール内に存在します。そのモジュールは builtins
と呼ばれています。)
関数のローカルな名前空間は、関数が呼び出されたときに作成され、関数から戻ったときや、関数内で例外が送出され、かつ関数内で処理されなかった場合に削除されます。 (実際には、忘れられる、と言ったほうが起きていることをよく表しています。) もちろん、再帰呼出しのときには、各々の呼び出しで各自のローカルな名前空間があります。
スコープ (scope) とは、ある名前空間が直接アクセスできるような、 Python プログラムのテキスト上の領域です。 "直接アクセス可能" とは、修飾なしに (訳注: spam.egg
ではなく単に egg
のように) 名前を参照した際に、その名前空間から名前を見つけようと試みることを意味します。
スコープは静的に決定されますが、動的に使用されます。実行中はいつでも、直接名前空間にアクセス可能な、少なくとも三つの入れ子になったスコープがあります:
- 最初に探される、最も内側のスコープは、ローカルな名前を持っています。
- 外側の(enclosing)関数のスコープは、近いほうから順に探され、ローカルでもグローバルでもない名前を持っています。
- 次のスコープは、現在のモジュールのグローバルな名前を持っています。
- 一番外側の(最後に検索される)スコープはビルトイン名を持っています。
名前が global と宣言されている場合、その名前に対する参照や代入は全て、モジュールのグローバルな名前の入った中間のスコープに対して直接行われます。最内スコープの外側にある変数に再束縛するには、 nonlocal
文が使えます。nonlocal と宣言されなかった変数は、全て読み出し専用となります (そのような変数に対する書き込みは、単に 新しい ローカル変数をもっとも内側のスコープで作成し、外部のスコープの値は変化しません)。
通常、ローカルスコープは (プログラムテキスト上の) 現在の関数のローカルな名前を参照します。関数の外側では、ローカルスコープはグローバルな名前空間と同じ名前空間、モジュールの名前空間を参照します。クラス定義では、ローカルスコープの中にもう一つ名前空間が置かれます。
スコープはテキスト上で決定されていると理解することが重要です。モジュール内で定義される関数のグローバルなスコープは、関数がどこから呼び出されても、どんな別名をつけて呼び出されても、そのモジュールの名前空間になります。反対に、実際の名前の検索は実行時に動的に行われます --- とはいえ、言語の定義は、"コンパイル" 時の静的な名前解決の方向に進化しているので、動的な名前解決に頼ってはいけません! (事実、ローカルな変数は既に静的に決定されています。)
Python 特有の癖として、代入を行うと -- どの global
文も有効でない場合は -- 名前がいつも最も内側のスコープに入るというものがあります。代入はデータのコピーを行いません --- 単に名前をオブジェクトに結びつける (bind) だけです。オブジェクトの削除でも同じです: del x
は、 x
をローカルスコープが参照している名前空間から削除します。実際、新たな名前を導入する操作は全てローカルスコープを用います。とりわけ、 import
文や関数定義は、モジュールや関数の名前をローカルスコープに結び付けます。
global
文を使うと、特定の変数がグローバルスコープに存在し、そこで再束縛されることを指示できます。 nonlocal
文は、特定の変数が外側のスコープに存在し、そこで再束縛されることを指示します。
9.2.1. スコープと名前空間の例¶
異なるスコープと名前空間がどのように参照されるか、また global
および nonlocal
が変数の束縛にどう影響するか、この例で実演します:
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)
scope_test()
print("In global scope:", spam)
このコード例の出力は:
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
このとおり、(デフォルトの) ローカルな 代入は scope_test 上の spam への束縛を変更しませんでした。 nonlocal
代入は scope_test 上の spam への束縛を変更し、 global
代入はモジュールレベルの束縛を変更しました。
またここから、 global
代入の前には spam に何も束縛されていなかったことも分かります。
9.3. クラス初見¶
クラスでは、新しい構文を少しと、三つの新たなオブジェクト型、そして新たな意味付けをいくつか取り入れています。
9.3.1. クラス定義の構文¶
クラス定義の最も単純な形式は、次のようになります:
class ClassName:
<statement-1>
.
.
.
<statement-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.i
と MyClass.f
は妥当な属性参照であり、それぞれ整数と関数オブジェクトを返します。クラス属性に代入を行うこともできます。従って、 MyClass.i
の値を代入して変更できます。 __doc__
も有効な属性で、そのクラスに属している docstring、この場合は "A simple example class"
を返します。
クラスの インスタンス化 (instantiation) には関数のような表記法を使います。クラスオブジェクトのことを、単にクラスの新しいインスタンスを返す引数がない関数のように振る舞います。例えば (上記のクラスでいえば):
x = MyClass()
は、クラスの新しい インスタンス (instance) を生成し、そのオブジェクトをローカル変数 x
へ代入します。
このクラスのインスタンス生成操作 (クラスオブジェクトの "呼出し") を行うと、空のオブジェクトを生成します。多くのクラスは、オブジェクトを作成する際に、カスタマイズされた特定の初期状態になってほしいと望んでいます。そのために、クラスには __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. インスタンスオブジェクト¶
Now what can we do with instance objects? The only operations understood by instance objects are attribute references. There are two kinds of valid attribute names, data attributes and methods.
データ属性 (data attribute) は、これは Smalltalk の "インスタンス変数" や C++の "データメンバ" に相当します。データ属性を宣言する必要はありません。ローカルな変数と同様に、これらの属性は最初に代入された時点で湧き出てきます。例えば、上で生成した 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) です。
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 個の引数リストもったメソッドの呼出しは、そのメソッドのインスタンスオブジェクトを最初の引数の前に挿入した引数リストで、メソッドに対応する関数を呼び出すことと等価です。
If you still don't understand how methods work, a look at the implementation can perhaps clarify matters. When an instance attribute is referenced that isn't a data attribute, its class is searched. If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object. When the method object is called with an argument list, a new argument list is constructed from the instance object and the argument list, and the function object is called with this new argument list.
9.3.5. クラスとインスタンス変数¶
一般的に、インスタンス変数はそれぞれのインスタンスについて固有のデータのためのもので、クラス変数はそのクラスのすべてのインスタンスによって共有される属性やメソッドのためのものです:
class Dog:
kind = 'canine' # class variable shared by all instances
def __init__(self, name):
self.name = name # instance variable unique to each instance
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind # shared by all dogs
'canine'
>>> e.kind # shared by all dogs
'canine'
>>> d.name # unique to d
'Fido'
>>> e.name # unique to e
'Buddy'
名前とオブジェクトについて で議論したように、共有データはリストや辞書のような mutable オブジェクトが関与すると驚くべき効果を持ち得ます。例えば、以下のコードの tricks リストはクラス変数として使われるべきではありません、なぜならたった一つのリストがすべての Dog インスタンスによって共有されることになり得るからです:
class Dog:
tricks = [] # mistaken use of a class variable
def __init__(self, name):
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks # unexpectedly shared by all dogs
['roll over', 'play dead']
このクラスの正しい設計ではインスタンス変数を代わりに使用するべきです:
class Dog:
def __init__(self, name):
self.name = name
self.tricks = [] # creates a new empty list for each dog
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']
9.4. いろいろな注意点¶
データ属性は同じ名前のメソッド属性を上書きしてしまいます。大規模なプログラムでみつけにくいバグを引き起こすことがあるこの偶然的な名前の衝突を避けるには、衝突の可能性を最小限にするような規約を使うのが賢明です。可能な規約としては、メソッド名を大文字で始める、データ属性名の先頭に短い一意な文字列 (あるいはただの下線) をつける、またメソッドには動詞、データ属性には名詞を用いる、などがあります。
データ属性は、メソッドから参照できると同時に、通常のオブジェクトのユーザ ("クライアント") からも参照できます。言い換えると、クラスは純粋な抽象データ型として使うことができません。実際、 Python では、データ隠蔽を補強するための機構はなにもありません --- データの隠蔽はすべて規約に基づいています。 (逆に、C 言語で書かれた Python の実装では実装の詳細を完全に隠蔽し、必要に応じてオブジェクトへのアクセスを制御できます。この機構は C 言語で書かれた Python 拡張で使うことができます。)
クライアントはデータ属性を注意深く扱うべきです --- クライアントは、メソッドが維持しているデータ属性の不変式を踏みにじり、台無しにするかもしれません。クライアントは、名前の衝突が回避されている限り、メソッドの有効性に影響を及ぼすことなくインスタンスに独自の属性を追加することができる、ということに注意してください --- ここでも、名前付けの規約は頭痛の種を無くしてくれます。
メソッドの中から、データ属性を (または別のメソッドも!) 参照するための短縮された記法はありません。私は、この仕様がメソッドの可読性を高めていると感じています。あるメソッドを眺めているときにローカルな変数とインスタンス変数をはっきり区別できるからです。
よく、メソッドの最初の引数を self
と呼びます。この名前付けは単なる慣習でしかありません。 self
という名前は、 Python では何ら特殊な意味を持ちません。とはいえ、この慣行に従わないと、コードは他の Python プログラマにとってやや読みにくいものとなります。また、 クラスブラウザ (class browser) プログラムがこの慣行をあてにして書かれているかもしれません。
クラス属性である関数オブジェクトはいずれも、そのクラスのインスタンスのためのメソッドを定義しています。関数定義は、テキスト上でクラス定義の中に入っている必要はありません。関数オブジェクトをクラスのローカルな変数の中に代入するのも OK です。例えば以下のコードのようにします:
# Function defined outside the class
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 された関数やモジュールや、そのモジュール中で定義された関数やクラスを使うことができます。通常、メソッドの入っているクラス自体はグローバルなスコープ内で定義されています。次の節では、メソッドが自分のクラスを参照する理由として正当なものを見てみましょう。
個々の値はオブジェクトなので、 クラス (型 とも言います) を持っています。それは object.__class__
に保持されています。
9.5. 継承¶
言うまでもなく、継承の概念をサポートしない言語機能は "クラス" と呼ぶに値しません。派生クラス (derived class) を定義する構文は次のようになります:
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
基底クラス (base class) の名前 BaseClassName
は、派生クラス定義の入っているスコープで定義されていなければなりません。基底クラス名のかわりに任意の式を入れることもできます。これは次の例のように、基底クラスが別モジュールで定義されているときに便利なことがあります:
class DerivedClassName(modname.BaseClassName):
派生クラス定義の実行は、基底クラスの場合と同じように進められます。クラスオブジェクトが構築される時、基底クラスが記憶されます。記憶された基底クラスは、属性参照を解決するために使われます。要求された属性がクラスに見つからなかった場合、基底クラスに検索が進みます。この規則は、基底クラスが他の何らかのクラスから派生したものであった場合、再帰的に適用されます。
派生クラスのインスタンス化では、特別なことは何もありません。 DerivedClassName()
はクラスの新たなインスタンスを生成します。メソッドの参照は次のようにして解決されます。まず対応するクラス属性が検索されます。検索は、必要に応じ、基底クラス連鎖を下って行われ、検索の結果として何らかの関数オブジェクトがもたらされた場合、メソッド参照は有効なものとなります。
派生クラスは基底クラスのメソッドを上書き (override) することができます。メソッドは同じオブジェクトの別のメソッドを呼び出す際に何ら特殊な権限を持ちません。このため、ある基底クラスのメソッドが、同じ基底クラスで定義されているもう一つのメソッド呼び出しを行っている場合、派生クラスで上書きされた何らかのメソッドが呼び出されることになるかもしれません。 (C++ プログラマへ: Python では、すべてのメソッドは事実上 virtual
です。)
派生クラスで上書きしているメソッドでは、基底クラスの同名のメソッドを置き換えるのではなく、拡張したいのかもしれません。基底クラスのメソッドを直接呼び出す簡単な方法があります。単に BaseClassName.methodname(self, arguments)
を呼び出すだけです。この仕様は、場合によってはクライアントでも役に立ちます。 (この呼び出し方が動作するのは、基底クラスがグローバルスコープの BaseClassName
という名前でアクセスできるときだけです。)
Python には継承に関係する 2 つの組み込み関数があります:
isinstance()
を使うとインスタンスの型が調べられます。isinstance(obj, int)
はobj.__class__
がint
やint
の派生クラスの場合に限りTrue
になります。issubclass()
を使うとクラスの継承関係が調べられます。bool
はint
のサブクラスなのでissubclass(bool, int)
はTrue
です。しかし、float
はint
のサブクラスではないのでissubclass(float, int)
はFalse
です。
9.5.1. 多重継承¶
Python では、多重継承 (multiple inheritance) の形式もサポートしています。複数の基底クラスをもつクラス定義は次のようになります:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
ほとんどのシンプルな多重継承において、親クラスから継承される属性の検索は、深さ優先で、左から右に、そして継承の階層の中で同じクラスが複数出てくる(訳注: ダイアモンド継承と呼ばれます)場合に2度探索をしない、と考えることができます。なので、 DerivedClassName
にある属性が存在しない場合、まず Base1
から検索され、そして(再帰的に) Base1
の基底クラスから検索され、それでも見つからなかった場合は Base2
から検索される、といった具合になります。
実際には、それよりもう少しだけ複雑です。協調的な super()
の呼び出しのためにメソッドの解決順序は動的に変更されます。このアプローチは他の多重継承のある言語で call-next-method として知られており、単一継承しかない言語の super 呼び出しよりも強力です。
多重継承の全ての場合に 1 つかそれ以上のダイヤモンド継承 (少なくとも 1 つの祖先クラスに対し最も下のクラスから到達する経路が複数ある状態) があるので、動的順序付けが必要です。例えば、全ての新形式のクラスは object
を継承しているので、どの多重継承でも object
へ到達するための道は複数存在します。基底クラスが複数回アクセスされないようにするために、動的アルゴリズムで検索順序を直列化し、各クラスで指定されている祖先クラスどうしの左から右への順序は崩さず、各祖先クラスを一度だけ呼び出し、かつ一様になる (つまり祖先クラスの順序に影響を与えずにサブクラス化できる) ようにします。まとめると、これらの特徴のおかげで信頼性と拡張性のある多重継承したクラスを設計することができるのです。さらに詳細を知りたければ、 https://www.python.org/download/releases/2.3/mro/ を見てください。
9.6. プライベート変数¶
オブジェクトの中からしかアクセス出来ない "プライベート" インスタンス変数は、 Python にはありません。しかし、ほとんどの Python コードが従っている慣習があります。アンダースコアで始まる名前 (例えば _spam
) は、 (関数であれメソッドであれデータメンバであれ) 非 public なAPIとして扱います。これらは、予告なく変更されるかもしれない実装の詳細として扱われるべきです。
クラスのプライベートメンバについて適切なユースケース(特にサブクラスで定義された名前との衝突を避ける場合)があるので、名前マングリング (name mangling) と呼ばれる、限定されたサポート機構があります。 __spam
(先頭に二個以上の下線文字、末尾に一個以下の下線文字) という形式の識別子は、 _classname__spam
へとテキスト置換されるようになりました。ここで classname
は、現在のクラス名から先頭の下線文字をはぎとった名前になります。このような難号化 (mangle) は、識別子の文法的な位置にかかわらず行われるので、クラス定義内に現れた識別子全てに対して実行されます。
名前マングリングは、サブクラスが内部のメソッド呼び出しを壊さずにメソッドをオーバーライドするのに便利です。例えば:
class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
def update(self, iterable):
for item in iterable:
self.items_list.append(item)
__update = update # private copy of original update() method
class MappingSubclass(Mapping):
def update(self, keys, values):
# provides new signature for update()
# but does not break __init__()
for item in zip(keys, values):
self.items_list.append(item)
難号化の規則は主に不慮の事故を防ぐためのものだということに注意してください; 確信犯的な方法で、プライベートとされている変数にアクセスしたり変更することは依然として可能なのです。デバッガのような特殊な状況では、この仕様は便利ですらあります。
exec()
や eval()
へ渡されたコードでは、呼出し元のクラス名を現在のクラスと見なさないことに注意してください。この仕様は global
文の効果と似ており、その効果もまた同様に、バイトコンパイルされたコードに制限されています。同じ制約が getattr()
と setattr()
と delattr()
にも適用されます。また、__dict__
を直接参照するときにも適用されます。
9.7. 残りのはしばし¶
Pascal の "レコード (record)" や、C 言語の "構造体 (struct)" のような、名前つきのデータ要素を一まとめにするデータ型があると便利なことがあります。空のクラス定義を使うとうまくできます:
class Employee:
pass
john = Employee() # Create an empty employee record
# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000
ある特定の抽象データ型を要求する Python コードの断片に、そのデータ型のメソッドをエミュレーションするクラスを代わりに渡すことができます。例えば、ファイルオブジェクトから何らかのデータを構築する関数がある場合、 read()
と readline()
を持つクラスを定義して、ファイルではなく文字列バッファからデータを取得するようにしておき、引数として渡すことができます。
インスタンスメソッドオブジェクトにも属性があります。 m.__self__
はメソッド m()
の属しているインスタンスオブジェクトで、 m.__func__
はメソッドに対応する関数オブジェクトです。
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, end='')
こういう要素へのアクセス方法は明確で簡潔で使い易いものです。イテレータの活用は Python へ広く行き渡り、統一感を持たせています。裏では for
文はコンテナオブジェクトに対して iter()
関数を呼んでいます。関数は、コンテナの中の要素に1つずつアクセスする __next__()
メソッドが定義されているイテレータオブジェクトを返します。これ以上要素が無い場合は、 __next__()
メソッドは StopIteration
例外を送出し、その通知を受け for
ループは終了します。組み込みの next()
関数を使って __next__()
メソッドを直接呼ぶこともできます; この例は関数がどう働くのかを示しています:
>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
next(it)
StopIteration
イテレータプロトコルの裏にある仕組みを観察していれば、自作のクラスにイテレータとしての振舞いを追加するのは簡単です。 __next__()
メソッドを持つオブジェクトを返す __iter__()
メソッドを定義するのです。クラスが __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]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
... 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.index
や self.data
といったインスタンス変数を使ったアプローチよりも簡単に関数を書くことができるようになります。
メソッドを自動生成したりプログラムの実行状態を自動保存するほかに、ジェネレータは終了時に自動的に StopIteration
を送出します。これらの機能を組み合わせると、通常の関数を書くのと同じ労力で、簡単にイテレータを生成できます。
9.10. ジェネレータ式¶
Some simple generators can be coded succinctly as expressions using a syntax similar to list comprehensions but with parentheses instead of brackets. These expressions are designed for situations where the generator is used right away by an enclosing function. Generator expressions are more compact but less versatile than full generator definitions and tend to be more memory friendly than equivalent list comprehensions.
例:
>>> sum(i*i for i in range(10)) # sum of squares
285
>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec)) # dot product
260
>>> from math import pi, sin
>>> sine_table = {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']
脚注
[1] | 例外が一つあります。モジュールオブジェクトには、秘密の読取り専用の属性 __dict__ があり、モジュールの名前空間を実装するために使われている辞書を返します; __dict__ という名前は属性ですが、グローバルな名前ではありません。この属性を利用すると名前空間の実装に対する抽象化を侵すことになるので、プログラムを検死するデバッガのような用途に限るべきです。 |