GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing docs.python.org — 9. クラス 9. クラス¶クラスはデータと機能を組み合わせる方法を提供します。 新規にクラスを作成することで、新しいオブジェクトの 型 を作成し、その型を持つ新しい インスタンス が作れます。 クラスのそれぞれのインスタンスは自身の状態を保持する属性を持てます。 クラスのインスタンスは、その状態を変更するための (そのクラスが定義する) メソッドも持てます。 Python のクラス機構は、ほかのプログラミング言語に比べて、新しく覚える構文や規則を最小限に抑えています。 その仕組みは C++ と Modula-3 のクラス機構を組み合わせたものです。 Python のクラスは、オブジェクト指向プログラミングの標準的な機能をすべて備えています。 複数の基底クラスを継承でき、派生クラスでは基底クラスのどのメソッドもオーバーライドできます。 また、メソッドから基底クラスの同名のメソッドを呼び出せます。 オブジェクトに格納するデータの種類や数も自由です。 モジュールと同様に、クラスも Python の動的な性質を持っています。 クラスは実行時に作られ、作成後に変更することもできます。 C++ の用語で言うと、クラスメンバはデータメンバも含めて通常 public です (例外については プライベート変数 を参照してください)。 メンバ関数はすべて 仮想関数 (virtual) です。 Modula-3 と同様に、メソッド内からオブジェクトのメンバを参照するための短縮記法はありません。 メソッド関数の宣言では、オブジェクト自体を表す引数を最初に明示します。 メソッドを呼び出すと、この引数にオブジェクトが暗黙的に渡されます。 Smalltalk と同様に、クラス自体もオブジェクトなので、インポートしたり別の名前を付けたりできます。 C++ や Modula-3 と違って、組み込み型を基底クラスにして拡張することもできます。 また、C++ と同様に、算術演算子や添字表記など特別な構文を使う組み込み演算子の多くは、 クラスインスタンスに対する動作を再定義できます。 (クラスに関して普遍的な用語定義がないので、 Smalltalk と C++ の用語を場合に応じて使っていくことにします。 C++ よりも Modula-3 の方がオブジェクト指向の意味論が Python に近いので、 Modula-3 の用語を使いたいのですが、ほとんどの読者は Modula-3 についてしらないでしょうから。) 9.1. 名前とオブジェクトについて¶オブジェクトはそれぞれ固有の存在ですが、同じオブジェクトに複数のスコープから複数の名前を束縛できます。 ほかの言語では、これをエイリアス (別名) と呼びます。 Python を学び始めたばかりでは、この仕組みの重要性は分かりにくいかもしれません。 数値、文字列、タプルのようなイミュータブル (変更できない) な基本型を扱うときは、気にしなくても問題ありません。 一方、リストや辞書など多くの型のミュータブル (変更できる) なオブジェクトを扱うコードでは、 エイリアスによって意外な動作が起こることがあります。 エイリアスはいくつかの点でポインタのように振る舞うため、通常はその性質をプログラムに役立てられます。 例えば、オブジェクトを渡す際に実装上渡されるのはポインタだけなので、処理の負担は小さくなります。 また、関数が引数として受け取ったオブジェクトを変更すると、呼び出し側からもその変更が見えます。 このため、Pascal のように引数の渡し方を 2 種類用意する必要がありません。 9.2. Python のスコープと名前空間¶クラスを紹介する前に、Python のスコープの規則について説明する必要があります。 クラス定義は名前空間を巧みに利用するので、その動作を十分に理解するには、スコープと名前空間の仕組みを知っておく必要があります。 なお、この知識は高度なプログラミングを行う Python プログラマーなら誰にとっても役立ちます。 まず定義から始めましょう。 名前空間 (namespace) とは、名前からオブジェクトへの対応付け (mapping) です。ほとんどの名前空間は、現状では Python の辞書として実装されていますが、そのことは通常は (パフォーマンス以外では) 目立つことはないし、将来は変更されるかもしれません。名前空間の例には、組込み名の集合 ( ところで、 属性 という言葉は、ドットに続く名前すべてに対して使っています --- 例えば式 属性は読取り専用にも、書込み可能にもできます。書込み可能であれば、属性に代入することができます。モジュール属性は書込み可能で、 名前空間は様々な時点で作成され、その寿命も様々です。組み込みの名前が入った名前空間は Python インタプリタが起動するときに作成され、決して削除されることはありません。モジュールのグローバルな名前空間は、モジュール定義が読み込まれたときに作成されます。通常、モジュールの名前空間は、インタプリタが終了するまで残ります。インタプリタのトップレベルで実行された文は、スクリプトファイルから読み出されたものでも対話的に読み出されたものでも、 関数のローカルな名前空間は、関数が呼び出されたときに作成され、関数から戻ったときや、関数内で例外が送出され、かつ関数内で処理されなかった場合に削除されます。 (実際には、忘れられる、と言ったほうが起きていることをよく表しています。) もちろん、再帰呼出しのときには、各々の呼び出しで各自のローカルな名前空間があります。 スコープ (scope) とは、ある名前空間が直接アクセスできるような、 Python プログラムのテキスト上の領域です。 "直接アクセス可能" とは、修飾なしに (訳注: スコープは静的に決定されますが、動的に使用されます。実行中はいつでも、直接名前空間にアクセス可能な、3つまたは4つの入れ子になったスコープがあります:
名前を global と宣言すると、その名前への参照と代入はすべて、モジュールのグローバル名を持つ最後から 2 番目のスコープで直接行われます。
最も内側のスコープより外側にある変数を再束縛するには、 通常、ローカルスコープは (プログラムテキスト上の) 現在の関数のローカルな名前を参照します。関数の外側では、ローカルスコープはグローバルな名前空間と同じ名前空間、モジュールの名前空間を参照します。クラス定義では、ローカルスコープの中にもう一つ名前空間が置かれます。 スコープはテキスト上で決定されていると理解することが重要です。モジュール内で定義される関数のグローバルなスコープは、関数がどこから呼び出されても、どんな別名をつけて呼び出されても、そのモジュールの名前空間になります。反対に、実際の名前の検索は実行時に動的に行われます --- とはいえ、言語の定義は、"コンパイル" 時の静的な名前解決の方向に進化しているので、動的な名前解決に頼ってはいけません! (事実、ローカルな変数は既に静的に決定されています。)
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 このとおり、(デフォルトの) ローカルな 代入は scope_test 上の spam への束縛を変更しませんでした。 また、 9.3. クラス初見¶クラスでは、新しい構文を少しと、三つの新たなオブジェクト型、そして新たな意味付けをいくつか取り入れています。 9.3.1. クラス定義の構文¶クラス定義の最も単純な形式は、次のようになります: class ClassName: <statement-1> . . . <statement-N> 関数定義 ( 実際には、クラス定義の内側にある文は、通常は関数定義になりますが、他の文を書くこともでき、それが役に立つこともあります --- これについては後で述べます。クラス内の関数定義は通常、メソッドの呼び出し規約で決められた独特の形式の引数リストを持ちます --- これについても後で述べます。 クラス定義に入ると、新しい名前空間が作られ、ローカルスコープとして使われます。 したがって、ローカル変数への代入はすべてこの新しい名前空間に対して行われます。 特に、関数定義では新しい関数の名前がここに束縛されます。 クラス定義から普通に (定義の終端に到達して) 抜けると、 クラスオブジェクト (class object) が生成されます。クラスオブジェクトは、基本的にはクラス定義で作成された名前空間の内容をくるむラッパー (wrapper) です。クラスオブジェクトについては次の節で詳しく学ぶことにします。 (クラス定義に入る前に有効だった) 元のローカルスコープが復帰し、生成されたクラスオブジェクトは復帰したローカルスコープにクラス定義のヘッダで指定した名前 (上の例では 9.3.2. クラスオブジェクト¶クラスオブジェクトでは2種類の演算、属性参照とインスタンス生成をサポートしています。 属性参照 (attribute reference) は、Python におけるすべての属性参照で使われている標準的な構文、 class MyClass: """A simple example class""" i = 12345 def f(self): return 'hello world'
クラスの インスタンス化 (instantiation) には関数記法を使います。クラスオブジェクトのことを、クラスの新しいインスタンスを返す、引数のない関数のように扱ってください。上記クラスで例示すると: x = MyClass() は、クラスの新しい インスタンス (instance) を生成し、そのオブジェクトをローカル変数 インスタンス化操作 (クラスオブジェクトの "呼出し") では、空のオブジェクトが作られます。多くのクラスでは、特定の初期状態にカスタマイズされたオブジェクトを作りたいです。そのために、クラスには def __init__(self): self.data = [] クラスが x = MyClass() もちろん、より大きな柔軟性を持たせるために、 >>> 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++ の「データメンバ」に相当します。
データ属性を宣言する必要はありません。
ローカル変数と同様に、初めて値を代入したときに作られます。
例えば、 x.counter = 1 while x.counter < 10: x.counter = x.counter * 2 print(x.counter) del x.counter もう一つのインスタンス属性の参照は、 メソッド です。メソッドとは、オブジェクトに "属する" 関数のことです。 インスタンスオブジェクトで有効なメソッド名は、そのクラスによって決まります。
定義上、クラス属性が関数オブジェクトであれば、そのクラスのインスタンスに対応するメソッドが定義されます。
この例では 9.3.4. メソッドオブジェクト¶普通、メソッドはバインドされた直後に呼び出されます: x.f() もし xf = x.f while True: print(xf())
メソッドが呼び出されるときには実際には何が起きているのでしょうか? もう答は想像できているかもしれませんね:
メソッドについて特別なこととして、インスタンスオブジェクトが関数の第1引数として渡されます。
例では、 メソッドは一般に次のように動作します。 インスタンスの非データ属性を参照すると、そのインスタンスのクラスから属性を探します。 見つかった属性が関数オブジェクトなら、インスタンスと関数の両方への参照をまとめてメソッドオブジェクトにします。 そのメソッドを呼び出すと、渡された引数の先頭にインスタンスを加えた新しい引数リストが作られます。 そして、その引数リストで関数を呼び出します。 9.3.5. クラス変数とインスタンス変数¶一般的に、インスタンス変数はそれぞれのインスタンスについて固有のデータのためのもので、クラス変数はそのクラスのすべてのインスタンスによって共有される属性やメソッドのためのものです: class Dog: kind = 'canine' # 全インスタンスで共有されるクラス変数 def __init__(self, name): self.name = name # インスタンスごとに固有のインスタンス変数 >>> d = Dog('Fido') >>> e = Dog('Buddy') >>> d.kind # すべての犬で共有 'canine' >>> e.kind # すべての犬で共有 'canine' >>> d.name # d 固有 'Fido' >>> e.name # e 固有 'Buddy' 名前とオブジェクトについて で述べたように、共有データはリストや辞書のような mutable オブジェクトで予期せぬ作用を生じることがあります。例えば、以下のコードの tricks リストはクラス変数として使われるべきではありません、なぜなら単一のリストがすべての Dog インスタンスで共有されてしまうからです: class Dog: tricks = [] # クラス変数の間違った使用 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 # 意図せず すべての犬で共有 ['roll over', 'play dead'] このクラスの正しい設計ではインスタンス変数を代わりに使用するべきです: class Dog: def __init__(self, name): self.name = name self.tricks = [] # 犬ごとに新しい空リストを作る 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 の拡張ではこの仕組みを使えます。) クライアントはデータ属性を注意して使うべきです。 データ属性を書き換えると、メソッドが維持する不変条件を壊す可能性があります。 ただし、名前の衝突を避ければ、クライアントはメソッドの正常な動作に影響を与えずに、インスタンスオブジェクトへ独自のデータ属性を追加できます。 ここでも、名前付けの規約を使うと多くの問題を避けられます。 メソッドの中から、データ属性を (または別のメソッドも!) 参照するための短縮された記法はありません。私は、この仕様がメソッドの可読性を高めていると感じています。あるメソッドを眺めているときにローカルな変数とインスタンス変数をはっきり区別できるからです。 メソッドの最初の引数は クラス属性である関数オブジェクトはいずれも、そのクラスのインスタンスのためのメソッドを定義しています。関数定義は、テキスト上でクラス定義の中に入っている必要はありません。関数オブジェクトをクラスのローカルな変数の中に代入するのも OK です。例えば以下のコードのようにします: # クラス外で定義された関数 def f1(self, x, y): return min(x, x+y) class C: f = f1 def g(self): return 'hello world' h = g これで、 メソッドは、 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) メソッドは通常の関数と同様にグローバル名を参照できます。 メソッドのグローバルスコープは、その定義を含むモジュールです (クラスがグローバルスコープになることはありません)。 メソッドでグローバルデータを使う適切な理由はまれですが、グローバルスコープには妥当な用途が数多くあります。 例えば、グローバルスコープにインポートした関数やモジュール、そのスコープで定義した関数やクラスは、メソッドからも使えます。 通常、そのメソッドを含むクラス自体もこのグローバルスコープで定義されています。 次の節では、メソッドが自身のクラスを参照したくなる理由を見ていきます。 個々の値はオブジェクトなので、 クラス (型 とも言います) を持っています。それは 9.5. 継承¶言うまでもなく、継承の概念をサポートしない言語機能は "クラス" と呼ぶに値しません。派生クラス (derived class) を定義する構文は次のようになります: class DerivedClassName(BaseClassName): <statement-1> . . . <statement-N> 基底クラス (base class) の名前 class DerivedClassName(modname.BaseClassName): 派生クラス定義の実行は、基底クラスの場合と同じように進められます。クラスオブジェクトが構築される時、基底クラスが記憶されます。記憶された基底クラスは、属性参照を解決するために使われます。要求された属性がクラスに見つからなかった場合、基底クラスに検索が進みます。この規則は、基底クラスが他の何らかのクラスから派生したものであった場合、再帰的に適用されます。 派生クラスのインスタンス化では、特別なことは何もありません。 派生クラスは基底クラスのメソッドを上書き (override) することができます。メソッドは同じオブジェクトの別のメソッドを呼び出す際に何ら特殊な権限を持ちません。このため、ある基底クラスのメソッドが、同じ基底クラスで定義されているもう一つのメソッド呼び出しを行っている場合、派生クラスで上書きされた何らかのメソッドが呼び出されることになるかもしれません。 (C++ プログラマへ: Python では、すべてのメソッドは事実上 派生クラスで上書きしているメソッドでは、基底クラスの同名のメソッドを置き換えるのではなく、拡張したいのかもしれません。基底クラスのメソッドを直接呼び出す簡単な方法があります。単に Python には継承に関係する 2 つの組み込み関数があります:
9.5.1. 多重継承¶Python では、多重継承 (multiple inheritance) の形式もサポートしています。複数の基底クラスをもつクラス定義は次のようになります: class DerivedClassName(Base1, Base2, Base3): <statement-1> . . . <statement-N> ほとんどのシンプルな多重継承において、親クラスから継承される属性の検索は、深さ優先で、左から右に、そして継承の階層の中で同じクラスが複数出てくる(訳注: ダイアモンド継承と呼ばれます)場合に2度探索をしない、と考えることができます。なので、ある属性が 実際には、それよりもう少しだけ複雑です。協調的な 検索順序を動的に決める必要があるのは、どの多重継承にも 1 つ以上のダイヤモンド関係があるためです。
これは、最も下位のクラスから同じ祖先クラスに複数の経路でたどり着ける関係を指します。
例えば、すべてのクラスは いくつかの場合では多重継承が許されません。詳細は 多重継承 を見てください。 9.6. プライベート変数¶オブジェクトの中からしかアクセス出来ない "プライベート" インスタンス変数は、 Python にはありません。しかし、ほとんどの Python コードが従っている慣習があります。アンダースコアで始まる名前 (例えば クラスのプライベートメンバは、サブクラスで定義された名前との衝突を避けるのに役立ちます。
この用途のために、Python は名前マングリング (name mangling) という限定的な仕組みを備えています。
参考 詳細と特例は、 private name mangling specifications 。 名前マングリングは、サブクラスが内部のメソッド呼び出しを壊さずにメソッドをオーバーライドするのに便利です。例えば: 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 # 元の update() メソッドのプライベートコピー class MappingSubclass(Mapping): def update(self, keys, values): # update() への新シグネチャ導入 # しかし __init__() は壊さない for item in zip(keys, values): self.items_list.append(item) 上の例は、もし仮に 名前マングリングの主な目的は、名前の衝突などの事故を防ぐことです。 プライベートとされている変数にも、アクセスしたり値を変更したりすることはできます。 デバッガなどでは、この性質が役立つこともあります。
9.7. 残りのはしばし¶Pascal のレコードや C の構造体のように、名前付きのデータ項目をまとめる型があると便利なことがあります。
Python では、この用途に 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 コードには、その型と同じように動くメソッドを備えたクラスを代わりに使えることがよくあります。
例えば、ファイルオブジェクトから読み取ったデータをフォーマットする関数があるとします。
ファイルの代わりに文字列バッファからデータを読み取る
Instance method objects にも属性があります。 9.8. イテレータ (iterator)¶すでに気づいているでしょうが、 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 へ広く行き渡り、統一感を持たせています。裏では >>> 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 イテレータプロトコルの裏にある仕組みを観察していれば、自作のクラスにイテレータとしての振舞いを追加するのは簡単です。 class Reverse: """シーケンスを後ろからまわるイテレータ.""" 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)¶ジェネレータ は、イテレータを作成するための簡潔で強力なツールです。ジェネレータは通常の関数のように書かれますが、何らかのデータを返すときには 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 ジェネレータでできることは、前の節で解説したクラスを使ったイテレータでも実現できます。ジェネレータの定義がコンパクトになるのは もう一つの重要な機能は、呼び出しと呼び出しの間にローカル変数と実行状態が自動的に保存されることです。
このため、 メソッドを自動生成したりプログラムの実行状態を自動保存するほかに、ジェネレータは終了時に自動的に 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 >>> 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'] >>> x = [[1,2,3], [], [4, 5]] >>> g = (*i for i in x) >>> list(g) [1, 2, 3, 4, 5] 多くの場合、ジェネレータ式は括弧で囲む必要があります。 ただし特例として、唯一の実引数として関数に渡す場合(前述の >>> f(x for x in y) >>> f((x for x in y)) 以下も同様です: >>> f(*x for x in y) >>> f((*x for x in y)) 脚注 |
| You are visitor 01402503 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |