9.9。 クラス
クラスは、データと機能を一緒にバンドルする手段を提供します。 新しいクラスを作成すると、オブジェクトの新しいタイプが作成され、そのタイプの新しいインスタンスを作成できるようになります。 各クラスインスタンスには、その状態を維持するための属性を付加できます。 クラスインスタンスは、その状態を変更するためのメソッド(クラスによって定義される)を持つこともできます。
他のプログラミング言語と比較して、Pythonのクラスメカニズムは、最小限の新しい構文とセマンティクスでクラスを追加します。 これは、C ++とModula-3に見られるクラスメカニズムの混合物です。 Pythonクラスは、オブジェクト指向プログラミングのすべての標準機能を提供します。クラス継承メカニズムにより、複数の基本クラスが可能になり、派生クラスはその基本クラスの任意のメソッドをオーバーライドでき、メソッドは同じ名前の基本クラスのメソッドを呼び出すことができます。 。 オブジェクトには、任意の量と種類のデータを含めることができます。 モジュールの場合と同様に、クラスはPythonの動的な性質に関与します。クラスは実行時に作成され、作成後にさらに変更できます。
C ++の用語では、通常、クラスメンバー(データメンバーを含む)は public (以下の Private Variables を参照)であり、すべてのメンバー関数は virtual です。 Modula-3と同様に、メソッドからオブジェクトのメンバーを参照するための省略形はありません。メソッド関数は、オブジェクトを表す明示的な最初の引数で宣言されます。これは、呼び出しによって暗黙的に提供されます。 Smalltalkと同様に、クラス自体はオブジェクトです。 これにより、インポートと名前変更のセマンティクスが提供されます。 C ++やModula-3とは異なり、組み込み型はユーザーによる拡張の基本クラスとして使用できます。 また、C ++と同様に、特別な構文(算術演算子、添え字など)を持つほとんどの組み込み演算子は、クラスインスタンスに対して再定義できます。
(クラスについて話すための一般的に受け入れられている用語がないため、SmalltalkおよびC ++の用語をときどき使用します。 オブジェクト指向のセマンティクスはC ++よりもPythonのセマンティクスに近いため、Modula-3の用語を使用しますが、聞いたことのある読者はほとんどいないと思います。)
9.1。 名前とオブジェクトについての一言
オブジェクトには個性があり、(複数のスコープ内の)複数の名前を同じオブジェクトにバインドできます。 これは、他の言語ではエイリアシングとして知られています。 これは通常、Pythonを一目見ただけでは理解できず、不変の基本型(数値、文字列、タプル)を扱う場合は無視しても問題ありません。 ただし、エイリアシングは、リスト、辞書、その他のほとんどのタイプなどの可変オブジェクトを含むPythonコードのセマンティクスに驚くべき影響を与える可能性があります。 エイリアスはいくつかの点でポインタのように動作するため、これは通常、プログラムの利益のために使用されます。 たとえば、オブジェクトの受け渡しは、ポインタのみが実装によって渡されるため、安価です。 また、関数が引数として渡されたオブジェクトを変更すると、呼び出し元に変更が表示されます。これにより、Pascalのように2つの異なる引数を渡すメカニズムが不要になります。
9.2。 Pythonのスコープと名前空間
クラスを紹介する前に、まずPythonのスコープルールについて説明する必要があります。 クラス定義は名前空間でいくつかの巧妙なトリックを果たします。何が起こっているのかを完全に理解するには、スコープと名前空間がどのように機能するかを知る必要があります。 ちなみに、この主題に関する知識は、高度なPythonプログラマーにとって役立ちます。
いくつかの定義から始めましょう。
名前空間は、名前からオブジェクトへのマッピングです。 現在、ほとんどの名前空間はPython辞書として実装されていますが、通常は(パフォーマンスを除いて)目立たないため、将来変更される可能性があります。 名前空間の例は次のとおりです。組み込み名のセット( abs()などの関数と組み込みの例外名を含む)。 モジュール内のグローバル名。 関数呼び出しのローカル名。 ある意味で、オブジェクトの属性のセットも名前空間を形成します。 名前空間について知っておくべき重要なことは、異なる名前空間の名前の間にはまったく関係がないということです。 たとえば、2つの異なるモジュールの両方で、混乱することなく関数maximize
を定義できます。モジュールのユーザーは、その前にモジュール名を付ける必要があります。
ちなみに、ドットに続く名前には attribute という単語を使用します。たとえば、式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プログラムのテキスト領域です。 ここでの「直接アクセス可能」とは、名前への修飾されていない参照が名前空間で名前を見つけようとすることを意味します。
スコープは静的に決定されますが、動的に使用されます。 実行中はいつでも、名前空間に直接アクセスできる3つまたは4つのネストされたスコープがあります。
- 最初に検索される最も内側のスコープには、ローカル名が含まれます
- 最も近い囲みスコープから検索される囲み関数のスコープには、非ローカル名だけでなく非グローバル名も含まれます
- 最後から2番目のスコープには、現在のモジュールのグローバル名が含まれます
- 最も外側のスコープ(最後に検索)は、組み込み名を含む名前空間です
名前がグローバルとして宣言されている場合、すべての参照と割り当ては、モジュールのグローバル名を含む中央のスコープに直接移動します。 最も内側のスコープ外で見つかった変数を再バインドするには、 nonlocal ステートメントを使用できます。 非ローカルとして宣言されていない場合、これらの変数は読み取り専用です(このような変数に書き込もうとすると、最も内側のスコープに new ローカル変数が作成され、同じ名前の外側変数は変更されません)。
通常、ローカルスコープは(テキストで)現在の関数のローカル名を参照します。 関数の外部では、ローカルスコープはグローバルスコープと同じ名前空間、つまりモジュールの名前空間を参照します。 クラス定義は、ローカルスコープにさらに別の名前空間を配置します。
スコープはテキストで決定されることを理解することが重要です。モジュールで定義された関数のグローバルスコープは、関数がどこから、またはどのエイリアスによって呼び出されたかに関係なく、そのモジュールの名前空間です。 一方、名前の実際の検索は実行時に動的に実行されますが、言語定義は「コンパイル」時に静的な名前解決に向かって進化しているため、動的な名前解決に依存しないでください。 (実際、ローカル変数はすでに静的に決定されています。)
Pythonの特別な癖は、 global または nonlocal ステートメントが有効になっていない場合、名前への割り当てが常に最も内側のスコープに入ることです。 割り当てはデータをコピーしません—名前をオブジェクトにバインドするだけです。 削除についても同じことが言えます。ステートメントdel x
は、ローカルスコープによって参照される名前空間からx
のバインディングを削除します。 実際、新しい名前を導入するすべての操作はローカルスコープを使用します。特に、 import ステートメントと関数定義は、ローカルスコープ内のモジュールまたは関数名をバインドします。
global ステートメントを使用して、特定の変数がグローバルスコープに存在し、そこでリバウンドする必要があることを示すことができます。 nonlocal ステートメントは、特定の変数が囲んでいるスコープに存在し、そこでリバウンドする必要があることを示しています。
9.2.1。 スコープと名前空間の例
これは、さまざまなスコープと名前空間を参照する方法、およびグローバルと非ローカルが変数バインディングにどのように影響するかを示す例です。
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
local 割り当て(デフォルト)が scope_test 'の spam のバインディングを変更しなかったことに注意してください。 nonlocal 割り当ては scope_test 'の spam のバインディングを変更し、 global 割り当てはモジュールレベルのバインディングを変更しました。
グローバル割り当ての前にスパムの以前のバインディングがなかったこともわかります。
9.3。 クラスの初見
クラスは、少しの新しい構文、3つの新しいオブジェクト型、およびいくつかの新しいセマンティクスを導入します。
9.3.1。 クラス定義構文
クラス定義の最も単純な形式は次のようになります。
class ClassName:
<statement-1>
.
.
.
<statement-N>
関数定義( def ステートメント)のようなクラス定義は、効果を発揮する前に実行する必要があります。 (おそらく、クラス定義を if ステートメントのブランチまたは関数内に配置できます。)
実際には、クラス定義内のステートメントは通常関数定義ですが、他のステートメントも許可されており、場合によっては便利です。後でこれに戻ります。 クラス内の関数定義には通常、メソッドの呼び出し規約によって決定される、独特の形式の引数リストがあります。これについても、後で説明します。
クラス定義が入力されると、新しい名前空間が作成され、ローカルスコープとして使用されます。したがって、ローカル変数へのすべての割り当ては、この新しい名前空間に入ります。 特に、関数定義はここで新しい関数の名前をバインドします。
クラス定義が(最後から)正常に残されると、クラスオブジェクトが作成されます。 これは基本的に、クラス定義によって作成された名前空間のコンテンツのラッパーです。 次のセクションでは、クラスオブジェクトについて詳しく学習します。 元のローカルスコープ(クラス定義が入力される直前に有効だったスコープ)が復元され、クラスオブジェクトはここでクラス定義ヘッダー(例ではClassName
)で指定されたクラス名にバインドされます。
9.3.2。 クラスオブジェクト
クラスオブジェクトは、属性参照とインスタンス化の2種類の操作をサポートします。
属性参照は、Pythonのすべての属性参照に使用される標準構文obj.name
を使用します。 有効な属性名は、クラスオブジェクトが作成されたときにクラスの名前空間にあったすべての名前です。 したがって、クラス定義が次のようになっている場合:
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
その場合、MyClass.i
とMyClass.f
は有効な属性参照であり、それぞれ整数と関数オブジェクトを返します。 クラス属性も割り当てることができるので、割り当てによってMyClass.i
の値を変更できます。 __doc__
も有効な属性であり、クラス"A simple example class"
に属するdocstringを返します。
クラスインスタンス化は関数表記を使用します。 クラスオブジェクトが、クラスの新しいインスタンスを返すパラメータのない関数であると偽ってください。 例(上記のクラスを想定):
x = MyClass()
クラスの新しいインスタンスを作成し、このオブジェクトをローカル変数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。 インスタンスオブジェクト
では、インスタンスオブジェクトで何ができるでしょうか。 インスタンスオブジェクトによって理解される唯一の操作は、属性参照です。 有効な属性名には、データ属性とメソッドの2種類があります。
データ属性は、Smalltalkの「インスタンス変数」、およびC ++の「データメンバー」に対応します。 データ属性を宣言する必要はありません。 ローカル変数のように、それらは最初に割り当てられたときに存在します。 たとえば、x
が上記で作成したMyClass
のインスタンスである場合、次のコードはトレースを残さずに値16
を出力します。
x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print(x.counter)
del x.counter
他の種類のインスタンス属性参照は、メソッドです。 メソッドは、オブジェクトに「属する」関数です。 (Pythonでは、メソッドという用語はクラスインスタンスに固有ではありません。他のオブジェクトタイプにもメソッドを含めることができます。 たとえば、リストオブジェクトには、append、insert、remove、sortなどのメソッドがあります。 ただし、以下の説明では、特に明記されていない限り、メソッドという用語は、クラスインスタンスオブジェクトのメソッドを意味するためにのみ使用します。)
インスタンスオブジェクトの有効なメソッド名は、そのクラスによって異なります。 定義上、関数オブジェクトであるクラスのすべての属性は、そのインスタンスの対応するメソッドを定義します。 したがって、この例では、MyClass.f
は関数であるため、x.f
は有効なメソッド参照ですが、MyClass.i
は関数ではないため、x.i
はそうではありません。 ただし、x.f
はMyClass.f
と同じものではなく、メソッドオブジェクトであり、関数オブジェクトではありません。
9.3.4。 メソッドオブジェクト
通常、メソッドはバインドされた直後に呼び出されます。
x.f()
MyClass
の例では、これは文字列'hello world'
を返します。 ただし、メソッドをすぐに呼び出す必要はありません。x.f
はメソッドオブジェクトであり、保存して後で呼び出すことができます。 例えば:
xf = x.f
while True:
print(xf())
時間の終わりまでhello world
を印刷し続けます。
メソッドが呼び出されると、正確にはどうなりますか? f()
の関数定義で引数が指定されていても、x.f()
が上記の引数なしで呼び出されたことにお気づきかもしれません。 議論はどうなりましたか? 確かに、引数を必要とする関数が何もなしで呼び出されると、Pythonは例外を発生させます—引数が実際に使用されていなくても…
実際、あなたは答えを推測したかもしれません:メソッドの特別なことは、インスタンスオブジェクトが関数の最初の引数として渡されることです。 この例では、呼び出し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'
名前とオブジェクトについての単語で説明されているように、共有データは、リストや辞書などの可変オブジェクトを含むことで驚くべき効果をもたらす可能性があります。 たとえば、次のコードの tricks リストは、すべての Dog インスタンスで共有されるリストが1つだけであるため、クラス変数として使用しないでください。
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プログラマーがコードを読みにくくなる可能性があり、そのような規則に依存するクラスブラウザープログラムが作成される可能性があることに注意してください。
クラス属性である関数オブジェクトは、そのクラスのインスタンスのメソッドを定義します。 関数定義がテキストでクラス定義に含まれている必要はありません。関数オブジェクトをクラス内のローカル変数に割り当てることもできます。 例えば:
# 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)
メソッドは、通常の関数と同じ方法でグローバル名を参照できます。 メソッドに関連付けられたグローバルスコープは、その定義を含むモジュールです。 (クラスがグローバルスコープとして使用されることはありません。)メソッドでグローバルデータを使用する正当な理由に遭遇することはめったにありませんが、グローバルスコープの正当な使用法はたくさんあります。たとえば、グローバルスコープにインポートされた関数とモジュールは次のことができます。メソッド、およびメソッドで定義されている関数とクラスによって使用されます。 通常、メソッドを含むクラス自体はこのグローバルスコープで定義されます。次のセクションでは、メソッドが独自のクラスを参照する理由をいくつか説明します。
各値はオブジェクトであるため、クラス(タイプとも呼ばれます)があります。 object.__class__
として保存されます。
9.5。 継承
もちろん、言語機能は、継承をサポートしない限り、「クラス」という名前に値するものではありません。 派生クラス定義の構文は次のようになります。
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
名前BaseClassName
は、派生クラス定義を含むスコープで定義する必要があります。 基本クラス名の代わりに、他の任意の式も使用できます。 これは、たとえば、基本クラスが別のモジュールで定義されている場合に役立ちます。
class DerivedClassName(modname.BaseClassName):
派生クラス定義の実行は、基本クラスの場合と同じように進行します。 クラスオブジェクトが作成されると、基本クラスが記憶されます。 これは、属性参照を解決するために使用されます。要求された属性がクラスで見つからない場合、検索は基本クラスの検索に進みます。 このルールは、基本クラス自体が他のクラスから派生している場合に再帰的に適用されます。
派生クラスのインスタンス化について特別なことは何もありません。DerivedClassName()
はクラスの新しいインスタンスを作成します。 メソッド参照は次のように解決されます。対応するクラス属性が検索され、必要に応じて基本クラスのチェーンを下っていきます。メソッド参照は、関数オブジェクトが生成される場合に有効です。
派生クラスは、基本クラスのメソッドをオーバーライドする場合があります。 同じオブジェクトの他のメソッドを呼び出す場合、メソッドには特別な特権がないため、同じ基本クラスで定義された別のメソッドを呼び出す基本クラスのメソッドは、それをオーバーライドする派生クラスのメソッドを呼び出すことになります。 (C ++プログラマーの場合:Pythonのすべてのメソッドは事実上virtual
です。)
派生クラスのオーバーライドメソッドは、実際には、同じ名前の基本クラスメソッドを単に置き換えるのではなく、拡張したい場合があります。 基本クラスのメソッドを直接呼び出す簡単な方法があります。BaseClassName.methodname(self, arguments)
を呼び出すだけです。 これは、クライアントにも役立つ場合があります。 (これは、基本クラスがグローバルスコープでBaseClassName
としてアクセスできる場合にのみ機能することに注意してください。)
Pythonには、継承で機能する2つの組み込み関数があります。
- isinstance()を使用して、インスタンスのタイプを確認します。
obj.__class__
が int または派生したクラスの場合にのみ、isinstance(obj, int)
はTrue
になります。 int から。 - issubclass()を使用して、クラスの継承を確認します。 bool は int のサブクラスであるため、
issubclass(bool, int)
はTrue
です。 ただし、 float は int のサブクラスではないため、issubclass(float, int)
はFalse
です。
9.5.1。 多重継承
Pythonは、多重継承の形式もサポートしています。 複数の基本クラスを持つクラス定義は次のようになります。
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
ほとんどの場合、最も単純なケースでは、親クラスから継承された属性の検索は、階層に重複がある同じクラスで2回検索するのではなく、深さ優先、左から右と考えることができます。 したがって、属性がDerivedClassName
で見つからない場合は、Base1
で検索され、次に(再帰的に)Base1
の基本クラスで検索され、見つからなかった場合は検索されます。そこでは、Base2
などで検索されました。
実際、それはそれよりも少し複雑です。 メソッドの解決順序は動的に変更され、 super()への協調呼び出しをサポートします。 このアプローチは、他のいくつかの多重継承言語ではcall-next-methodとして知られており、単一継承言語で見られるスーパーコールよりも強力です。
多重継承のすべてのケースが1つ以上のひし形の関係を示すため、動的な順序付けが必要です(親クラスの少なくとも1つは、最下位のクラスから複数のパスを介してアクセスできます)。 たとえば、すべてのクラスはオブジェクトから継承するため、多重継承の場合は、オブジェクトに到達するための複数のパスが提供されます。 基本クラスが複数回アクセスされないようにするために、動的アルゴリズムは、各クラスで指定された左から右への順序を保持する方法で検索順序を線形化し、各親を1回だけ呼び出し、単調です(つまり、クラスは、その親の優先順位に影響を与えることなくサブクラス化できます)。 これらのプロパティを総合すると、多重継承を備えた信頼性が高く拡張可能なクラスを設計できます。 詳細については、 https://www.python.org/download/releases/2.3/mro/を参照してください。
9.6。 プライベート変数
オブジェクトの内部以外からアクセスできない「プライベート」インスタンス変数は、Pythonには存在しません。 ただし、ほとんどのPythonコードが従う規則があります。名前の前にアンダースコアが付いています(例: _spam
)は、APIの非公開部分として扱う必要があります(関数、メソッド、またはデータメンバーのいずれであっても)。 これは実装の詳細と見なされ、予告なしに変更される場合があります。
クラスプライベートメンバーには有効なユースケースがあるため(つまり、サブクラスによって定義された名前と名前の名前の衝突を回避するため)、 name mangling と呼ばれるそのようなメカニズムのサポートは制限されています。 __spam
の形式の識別子(少なくとも2つの先頭のアンダースコア、最大で1つの末尾のアンダースコア)は、テキストで_classname__spam
に置き換えられます。ここで、classname
は先頭のアンダースコアを持つ現在のクラス名です。 (s)剥ぎ取られた。 このマングリングは、クラスの定義内で発生する限り、識別子の構文上の位置に関係なく実行されます。
名前マングリングは、クラス内メソッド呼び出しを中断せずにサブクラスがメソッドをオーバーライドできるようにするのに役立ちます。 例えば:
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
がMapping
クラスおよび_MappingSubclass__update
で_Mapping__update
に置き換えられているため、__update
識別子を導入した場合でも機能します。 ]それぞれMappingSubclass
クラスにあります。
マングリングルールは主に事故を避けるために設計されていることに注意してください。 プライベートと見なされる変数にアクセスしたり、変更したりすることは引き続き可能です。 これは、デバッガーなどの特別な状況でも役立ちます。
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。 イテレータ
これまでに、ほとんどのコンテナオブジェクトが 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。 発電機
Generators は、イテレーターを作成するためのシンプルで強力なツールです。 これらは通常の関数のように記述されていますが、データを返したいときはいつでも 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 __()メソッドが自動的に作成されることです。
もう1つの重要な機能は、ローカル変数と実行状態が呼び出し間で自動的に保存されることです。 これにより、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']
脚注
- 1
- 1つを除いて。 モジュールオブジェクトには、 __ dict __ と呼ばれる秘密の読み取り専用属性があり、モジュールの名前空間を実装するために使用されるディクショナリを返します。 名前 __ dict __ は属性ですが、グローバル名ではありません。 明らかに、これを使用すると名前空間の実装の抽象化に違反するため、事後デバッガーなどに制限する必要があります。