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
のように) 名前を参照した際に、その名前空間から名前を見つけようと試みることを意味します。
スコープは静的に決定されますが、動的に使用されます。実行中はいつでも、直接名前空間にアクセス可能な、3つまたは4つの入れ子になったスコープがあります:
最初に探される、最も内側のスコープは、ローカルな名前を持っています。
外側の(enclosing)関数のスコープは、近いほうから順に探され、ローカルでもグローバルでもない名前を持っています。
次のスコープは、現在のモジュールのグローバルな名前を持っています。
一番外側の(最後に検索される)スコープはビルトイン名を持っています。
名前が global と宣言されている場合、その名前に対する参照や代入は全て、モジュールのグローバルな名前の入った最後から2番目のスコープに対して直接行われます。最内スコープの外側にある変数に再束縛するには、 nonlocal
文が使えます。nonlocal と宣言されなかった変数は、全て読み出し専用となります (そのような変数に対する書き込みは、単に 新しい ローカル変数をもっとも内側のスコープで作成し、外部のスコープの値は変化しません)。
通常、ローカルスコープは (プログラムテキスト上の) 現在の関数のローカルな名前を参照します。関数の外側では、ローカルスコープはグローバルな名前空間と同じ名前空間、モジュールの名前空間を参照します。クラス定義では、ローカルスコープの中にもう一つ名前空間が置かれます。
スコープはテキスト上で決定されていると理解することが重要です。モジュール内で定義される関数のグローバルなスコープは、関数がどこから呼び出されても、どんな別名をつけて呼び出されても、そのモジュールの名前空間になります。反対に、実際の名前の検索は実行時に動的に行われます --- とはいえ、言語の定義は、"コンパイル" 時の静的な名前解決の方向に進化しているので、動的な名前解決に頼ってはいけません! (事実、ローカルな変数は既に静的に決定されています。)
Python の特徴として、global
や nonlocal
文が有効でない場合は、名前に対する参照は常に最も内側のスコープに対して有効になります。
代入はデータをコピーしません。オブジェクトを名前に束縛するだけです。削除も同様で、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'
then MyClass.i
and MyClass.f
are valid attribute references, returning
an integer and a function object, respectively. Class attributes can also be
assigned to, so you can change the value of MyClass.i
by assignment.
__doc__
is also a valid attribute, returning the docstring belonging to
the class: "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. インスタンスオブジェクト¶
ところで、インスタンスオブジェクトを使うと何ができるのでしょうか?インスタンスオブジェクトが理解できる唯一の操作は、属性の参照です。有効な属性名には (データ属性およびメソッドの) 二種類あります。
データ属性 (data attribute) は、 Smalltalk の "インスタンス変数" や C++の "データメンバ" に相当します。データ属性を宣言する必要はありません。ローカル変数と同様に、これらの属性は最初に代入された時点で湧き出てきます。例えば、上で生成した MyClass
のインスタンス x
に対して、次のコードを実行すると、跡を残さず値 16
を印字します:
x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print(x.counter)
del x.counter
The other kind of instance attribute reference is a method. A method is a function that "belongs to" an object. (In Python, the term method is not unique to class instances: other object types can have methods as well. For example, list objects have methods called append, insert, remove, sort, and so on. However, in the following discussion, we'll use the term method exclusively to mean methods of class instance objects, unless explicitly stated otherwise.)
インスタンスオブジェクトで有効なメソッド名は、そのクラスによります。定義により、クラスの全ての関数オブジェクトである属性がインスタンスオブジェクトの妥当なメソッド名に決まります。従って、例では、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 個の引数リストもったメソッドの呼出しは、そのメソッドのインスタンスオブジェクトを最初の引数の前に挿入した引数リストで、メソッドに対応する関数を呼び出すことと等価です。
一般的に、メソッドは以下のように動作します。インスタンスの非データ属性が参照されたときは、そのインスタンスのクラスが検索されます。その名前が有効なクラス属性を表している関数オブジェクトなら、インスタンスオブジェクトと関数オブジェクトの両方への参照がメソッドオブジェクトにパックされます。メソッドオブジェクトが引数リストと共に呼び出されるとき、インスタンスオブジェクトと渡された引数リストから新しい引数リストを作成して、元の関数オブジェクトを新しい引数リストで呼び出します。
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. いろいろな注意点¶
インスタンスとクラスの両方で同じ属性名が使用されている場合、属性検索はインスタンスが優先されます。
>>> class Warehouse:
... purpose = 'storage'
... region = 'west'
...
>>> w1 = Warehouse()
>>> print(w1.purpose, w1.region)
storage west
>>> w2 = Warehouse()
>>> w2.region = 'east'
>>> print(w2.purpose, w2.region)
storage east
データ属性は、メソッドから参照できると同時に、通常のオブジェクトのユーザ ("クライアント") からも参照できます。言い換えると、クラスは純粋な抽象データ型として使うことができません。実際、 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>
The name BaseClassName
must be defined in a scope containing the
derived class definition. In place of a base class name, other arbitrary
expressions are also allowed. This can be useful, for example, when the base
class is defined in another module:
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 呼び出しよりも強力です。
Dynamic ordering is necessary because all cases of multiple inheritance exhibit
one or more diamond relationships (where at least one of the parent classes
can be accessed through multiple paths from the bottommost class). For example,
all classes inherit from object
, so any case of multiple inheritance
provides more than one path to reach object
. To keep the base classes
from being accessed more than once, the dynamic algorithm linearizes the search
order in a way that preserves the left-to-right ordering specified in each
class, that calls each parent only once, and that is monotonic (meaning that a
class can be subclassed without affecting the precedence order of its parents).
Taken together, these properties make it possible to design reliable and
extensible classes with multiple inheritance. For more detail, see
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)
上の例は、もし仮に MappingSubclass
に __update
識別子を実装したとしてもきちんと動きます。
その理由は、 Mapping
クラスではその識別子を _Mapping__update
に、 MappingSubclass
クラスでは _MappingSubclass__update
にそれぞれ置き換えるからです。
難号化の規則は主に不慮の事故を防ぐためのものだということに注意してください; 確信犯的な方法で、プライベートとされている変数にアクセスしたり変更することは依然として可能なのです。デバッガのような特殊な状況では、この仕様は便利ですらあります。
exec()
や eval()
へ渡されたコードでは、呼出し元のクラス名を現在のクラスと見なさないことに注意してください。この仕様は global
文の効果と似ており、その効果もまた同様に、バイトコンパイルされたコードに制限されています。同じ制約が getattr()
と setattr()
と delattr()
にも適用されます。また、__dict__
を直接参照するときにも適用されます。
9.7. 残りのはしばし¶
Pascal の "レコード (record)" や、C 言語の "構造体 (struct)" のような、名前つきのデータ要素を一まとめにするデータ型があると便利なことがあります。慣用的な手法として、この目的のために dataclasses
を使用します。
from dataclasses import dataclass
@dataclass
class Employee:
name: str
dept: str
salary: int
>>> john = Employee('john', 'computer lab', 1000)
>>> john.dept
'computer lab'
>>> john.salary
1000
ある特定の抽象データ型を要求する Python コードの断片に、そのデータ型のメソッドをエミュレーションするクラスを代わりに渡すことができます。例えば、ファイルオブジェクトから何らかのデータを構築する関数がある場合、 read()
と readline()
を持つクラスを定義して、ファイルではなく文字列バッファからデータを取得するようにしておき、引数として渡すことができます。
Instance method objects にも属性があります。 m.__self__
はメソッド m()
の属しているインスタンスオブジェクトで、m.__func__
はそのメソッドに対応する function object です。
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
<str_iterator object at 0x10c90e650>
>>> 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)¶
ジェネレータ は、イテレータを作成するための簡潔で強力なツールです。ジェネレータは通常の関数のように書かれますが、何らかのデータを返すときには 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. ジェネレータ式¶
単純なジェネレータなら式として簡潔にコーディングできます。 その式はリスト内包表記に似た構文を使いますが、角括弧ではなく丸括弧で囲います。 ジェネレータ式は、関数の中でジェネレータをすぐに使いたいような状況のために用意されています。 ジェネレータ式は完全なジェネレータの定義よりコンパクトですが、ちょっと融通の効かないところがあります。 同じ内容を返すリスト内包表記よりはメモリに優しいことが多いという利点があります。
例:
>>> 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
>>> 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']
脚注