S-JIS[2018-01-08] 変更履歴
Python3.6.4のクラスのメモ。
クラスはclass文によって定義する。
class クラス名〔(親クラス名, …)〕: 複文
クラス名はCamelCaseにするのが慣例らしい。
親クラス(継承元クラス)を書かない場合はobjectを継承したことになる。
多重継承可。
PythonにはJavaのインターフェースやScalaのトレイトに当たるものは無い。
概念的にはプロトコルがそれに相当する。
クラスの本体に(フィールド定義や)メソッド定義等の文を書いていく。
この「文」はクラス定義時に実行される。
例えばprintを書いておくと、(インスタンス生成時ではなく)定義時に表示される。
(Scalaの場合はクラス本体がコンストラクターなので、printlnを書いておくとインスタンス生成時に実行される)
最小のクラス定義は以下のようになる。
class MyClass: pass
メソッド名やフィールド名にはPythonで定められた特殊な名前(「__」で始まり「__」で終わる)がある。
例えば__init__メソッドはコンストラクター、__dict__でフィールド一覧など。
クラスやメソッド・フィールドの可視性は、基本的にpublic。
「_」を付けるとpublicでない扱いにする、という慣例があるらしい。
フィールド名やメソッド名で「__」で始まる(「__」で終わらない)名前にするとprivateになる。
(内部的には「_クラス名__名前」という名前に変換して扱われるらしい)
インスタンスの生成方法は「クラス名(引数, …)」という形になる。(Javaのnewのようなキーワードは不要)
クラス本体の先頭の文字列リテラルを置いておく(Pythonの構文的には式文になる)と、ドキュメンテーション文字列として認識される。
ここにはクラスの説明を書く。(JavaのJavadoc相当)
class MyClass: "Example." pass
複数行にわたって書く場合は"""が便利。
class MyClass2: """Example1. example2 example3 """ pass
ドキュメンテーション文字列は、クラスの__doc__というフィールドに設定される。
print(MyClass2.__doc__)
↓実行結果
Example1. example2 example3
help関数でも表示される。
ヘルプの表示では、文字列内のインデントが処理される。
help(MyClass2)
↓実行結果
Help on class MyClass2 in module __main__: class MyClass2(builtins.object) | Example1. | | example2 | example3 | | Data descriptors defined here: 〜
メソッドはdef文で定義する。
メソッド名はsnake_caseにするのが慣例。
基本的にはメソッドの可視性はpublic。
慣例としてメソッド名を「_」で始めるとpublic以外という扱いにするらしい。
メソッド名を「__」で始める(「__」で終わらない)とprivateになる。
インスタンスメソッドの場合、第1引数に自分のクラスのインスタンスが渡ってくる。
これを受ける引数は、引数名をselfにする。(慣例でそうなっているだけで実際は何でもいい)
class MyClass: def f1(self): print("f") def f2(self, arg): print(f"f2 {arg}")
メソッドの中でフィールドアクセスする場合は、必ず「self.
」を使う。
(Javaのthisのような暗黙の変数は無いし、「self.
」を省略した場合にフィールドアクセスになってくれるような仕組みも無い)
def f1(self): self.value = 123 def f2(self): print(self.value)
インスタンスメソッドを呼び出すときはselfに当たる部分を自分で書く必要は無い。(暗黙に指定される)
c = MyClass() # インスタンス生成 c.f1() c.f2(123)
クラスのドキュメンテーション文字列と同様に、メソッド本体の最初の文字列 リテラルはドキュメンテーション文字列として扱われる。
class MyClass: def f(): """Example1. example2 example3 """ print("f")
print(MyClass.f.__doc__)
↓実行結果
Example1. example2 example3
help(MyClass.f)
↓実行結果
Help on function f in module __main__: f() Example1. example2 example3
静的メソッド(staticメソッド)を定義する場合は、@staticmethodデコレーターを付ける。(デコレーターの後ろで改行は必須)
class MyClass: @staticmethod def f(): print("static method")
静的メソッドは以下のようにして実行する。
MyClass.f()
クラスに定義されているメソッド名の一覧(リスト)はdir関数で取得できる。
dir(MyClass)
↓実行結果
['__class__', '__delattr__', 〜 '__weakref__', 'f']
※Pythonではメソッドをメソッド名でしか識別しない(引数の種類は関係ない)ので、メソッドのオーバーロードは出来ない。
(複数の同名メソッドを定義した場合、一番最後のメソッドが有効になる)
__init__メソッドを定義しておくと、インスタンス生成時に呼ばれる。(C++やJavaのコンストラクター相当)
インスタンス生成時に指定する引数は、__init__メソッドに渡される。
パターン | 例 | インスタンス生成の例 | 説明 |
---|---|---|---|
引数0個 |
class MyClass0: def __init__(self): print("init") |
c = MyClass0() |
|
引数1個 |
class MyClass1: def __init__(self, arg): print(f"init {arg}") |
c = MyClass1("abc") |
|
親コンストラクター呼び出し |
class A: def __init__(self, arg): print(f"a {arg}") class B(A): def __init__(self, arg): super().__init__(arg) print(f"b {arg}") |
c = B(123) ↓実行結果 a 123 b 123 |
親クラスを継承しているとき、 自動的には親クラスの__init__を呼んでくれない。 自分で呼び出す必要がある。 |
自分の__init__が無い場合 |
class A: def __init__(self, arg): print(f"a {arg}") class C(A): pass |
c = C(123) ↓実行結果 a 123 |
自分のクラスに__init__が無い場合、 親クラスの__init__は呼ばれる。 |
__new__という特殊メソッドもある。これはインスタンスを生成するものらしい。
__new__でインスタンスを生成した後、__init__で初期化されるということだろう。
(Javaのバイトコードでも、インスタンス生成(new)とコンストラクター呼び出し(invokespecial)は分かれている)
__del__メソッドを定義しておくと、インスタンスが消滅させられるときに呼ばれる。
class MyClass: def __del__(self): print("del")
c = MyClass() # インスタンス生成 del c
インスタンスが消滅するというのは主にdel文のことだと思われるが、delを使うと必ず__del__が呼ばれるというわけでもないらしい。
GCによって消滅するときに呼ばれることがあるっぽいので、Pythonのドキュメントには「デストラクターとも呼ばれる」と書かれているけれども、Javaのファイナライザー(finalizeメソッド)の方が近いかもしれない。
Pythonでは、JavaやScalaの様なフィールド定義構文は存在しない。
インスタンス自身がフィールドを保持するdictのようなものを持っており、いつでも自由にセットできる。
フィールド名はsnake_caseにするのが慣例。
基本的にはフィールドの可視性はpublic。
慣例としてフィールド名を「_」で始めるとpublic以外という扱いにするらしい。
フィールド名を「__」で始める(「__」で終わらない)とprivateになる。
例 | 説明 |
---|---|
class MyClass: def __init__(self, arg): self.value = arg c = MyClass(123) print(c.value) # 123 |
コンストラクター(__init__メソッド)の中でフィールドを初期化 する。 |
class MyClass: def f(self): print(self.value) c = MyClass() c.value = 123 c.f() |
インスタンス生成後に外からフィールドを初期化することも出来る。 |
class MyClass: def f(self): print(self.value) c = MyClass() c.f() # 'value'が無いというエラー |
フィールドを使う場合、存在していないと例外(AttributeError)が発生する。 |
class MyClass: value = 123 c = MyClass() print(c.value) # 123 c.value = 456 print(c.value) # 456 |
クラス本体に変数への代入文を書くと、フィールドの初期化になる。 が、基本的には定数定義くらいに使うのが良さそう。 |
class MyClass: value = [] c1 = MyClass() c1.value.append(1) c2 = MyClass() c2.value.append(2) print(c1.value) print(c2.value) ↓実行結果 [1, 2] [1, 2] |
クラス本体で初期化する場合、そのオブジェクトは他のインスタンスと共通になる。 個別の値はコンストラクター(__init__メソッド)で初期化すべき。 class MyClass: def __init__(self): self.value = [] ↓実行結果 [1] [2] |
インスタンスに保持されているフィールドの一覧(dict)はvars関数(もしくは__dict__)で取得できる。
class MyClass: value1 = 123 def __init__(self, arg): self.value2 = arg
c = MyClass(456) # インスタンス生成 c.value3 = 789 print(vars(c)) print(c.__dict__)
↓実行結果
{'value2': 456, 'value3': 789} {'value2': 456, 'value3': 789}
value1が表示されねぇorz
「print(c.value1)
」だとちゃんと表示されるのだが…。
やはりフィールドの初期化はコンストラクター(__init__メソッド)の中で行うのが無難ということか。
__dict__には値を書き込むことも出来るようだ。(必ず変更可能とは限らないらしいが)
c.__dict__['value4'] = 100 print(c.value4)
クラスを継承する場合、クラス名の後ろに丸括弧で囲み、親クラス名を書く。
(親クラスを指定しなかった場合はobjectを継承した扱いになる)
class クラス名(親クラス名〔, …〕)
親クラスのコンストラクター(__init__メソッド)は自動的(暗黙的)には呼び出されないので、自分で呼び出す必要がある。
super()を使うと親インスタンスが取得できる。
class SuperExample: def __init__(self): print("super") class Example(SuperExample): def __init__(self): super().__init__() print("sub")
Pythonでは多重継承できる。(複数のクラスを親として指定できる)
多重継承のあるプログラミング言語では、ダイアモンド継承と呼ばれる問題が知られている。
B1, B2がAを継承し、CがB1, B2を継承すると、Aが持っているフィールドは、Cから見て1つなのか?B1, B2がそれぞれ持っていて2つなのか?
Pythonの場合、同名のフィールドは1つしか持たない。
(Javaの「親クラスのフィールドの隠蔽」のような事は起きない)
class A: def __init__(self): self.x = 0 class B1(A): def b1(self): print(f"b1.x={self.x}") self.x = 1 class B2(A): def b2(self): print(f"b2.x={self.x}") self.x = 2 class C(B1, B2): def c(self): print(f"c.x={self.x}") self.b1() print(f"c.x={self.x}") self.b2() print(f"c.x={self.x}") self.b1() print(f"c.x={self.x}")
C().c()
↓実行結果
c.x=0 b1.x=0 c.x=1 b2.x=1 c.x=2 b1.x=2 c.x=1
super()による親インスタンスの取得は、左に書かれているクラスが優先される。
class A: def f(self): print("a") class B1(A): def f(self): print("b1") super().f() class B2(A): def f(self): print("b2") super().f() class C(B1, B2): def f(self): print("c") super().f() C().f()
↓実行結果
c b1 b2 a
クラスのインスタンスを生成するには、クラス名の後ろに丸括弧を付ける。引数があれば、丸括弧内に書く。
要するに関数呼び出しと同じ形式。
クラス名(〔引数, …〕)
# 例 s = MyClass()
(Javaだと「new クラス名(引数, …)
」となるが、そのnewが無い感じ)
引数の個数はコンストラクターによる。
typeコンストラクターを使うと、オブジェクトの型が分かる。
print(type(MyClass)) c = MyClass() # インスタンス生成 print(type(c))
↓実行結果
<class 'type'> <class '__main__.MyClass'>
あるオブジェクトがあるクラスのものかどうかはisinstance関数で判定する。(Javaのinstanceof相当)
c = MyClass() print(isinstance(c, MyClass)) # True print(isinstance(c, str)) # False print(isinstance("a", MyClass)) # False print(isinstance("a", str)) # True
あるクラスがクラスを継承しているかどうか(サブクラスかどうか)はissubclass関数で判定する。
class A: pass class B(A): pass print(issubclass(B, A)) # True print(issubclass(A, B)) # False
同一のインスタンスかどうかはis演算子で判定する。
c1 = MyClass() # インスタンス生成 c2 = MyClass() # インスタンス生成 c3 = c1 print(c1 is c2) # False print(c1 is c3) # True
==演算子でオブジェクトが等しいかどうか判定したい場合は、__eq__メソッドを実装しておく。(Javaのequals相当)
class MyClass: def __init__(self, arg): self.value = arg def __eq__(self, other): return self.value == other.value
c1 = MyClass(123) # インスタンス生成 c2 = MyClass(123) # インスタンス生成 print(c1 == c2) # True print(c1 is c2) # False
他の比較演算子についても同様。→演算子に関する特殊メソッド名
オブジェクトのハッシュ値を取得するにはhash関数を使用する。(JavaのhashCode相当)
print(hash(MyClass()))
ハッシュ関数を定義したい場合は__hash__メソッドを実装する。
class MyClass: def __init__(self, arg): self.value = arg def __hash__(self): return hash(self.value)
id関数でオブジェクトの識別値を取得できる。
識別値は、オブジェクトが存在している間は、異なるオブジェクトは必ず異なる値になる。
(一度オブジェクトが消滅した後だと、異なるオブジェクトが同じ識別値になることはありうる)
(CPythonの場合、識別値はオブジェクトのアドレスらしい)
print(id(MyClass()))
PythonにはJavaのインターフェースに当たるものは無い。
Javaでは、「何らかの操作を行えること」を表す為にインターフェースを定義し、クラスはそのインターフェースを実装する。
例えばAutoCloseableインターフェースはcloseメソッドを持っており、AutoCloseableを実装したクラスに対してcloseメソッドを呼び出すことが出来る。また、AutoCloseableを実装したクラスはtry-with-resources構文で使用できる。
例えばIterableインターフェースはiteratorメソッドを持っており、Iterableを実装したクラスはそれを呼び出してイテレーターを取得することが出来る。また、Iterableを実装したクラスはfor-each構文で使用できる。
Pythonではインターフェースの構文自体は存在しないが、似た概念を持っており、そういったルールの事をプロトコルと呼ぶ。
例えばwith文を使うには、そのクラスは__enter__メソッドと__exit__メソッドを持っていなければならない。これをコンテキストマネージャプロトコルと呼ぶ。
例えばfor文のジェネレーターに使うには、そのクラスは__iter__メソッドを持ち、イテレーターオブジェクトを返さなければならない。これをイテレータプロトコルと呼ぶ。