S-JIS[2018-01-08] 変更履歴

Pythonのクラス定義

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__メソッドを持ち、イテレーターオブジェクトを返さなければならない。これをイテレータプロトコルと呼ぶ。


Pythonへ戻る / 技術メモへ戻る
メールの送信先:ひしだま