Pythonのクラス定義の特徴
自分はJavaからプログラム言語の世界に入ったため、静的型付け言語のJavaやC#の記述方法が好き。
そのため、Pythonの勉強を始めたときも、クラス定義はJavaのように、クラス宣言、フィールド、コンストラクタ、メソッドのように作ったりしていた。
ただ、当然ながらPythonはJavaとは違うので、適当に作るとうまく動かなかったり、ネットに転がっている既存のPythonのクラスを見てもいまいちよく分からない記述があったりで、理解するのになかなか苦労した。
というわけで本記事は、Pythonのクラス定義に関する自分向けもしくは初学者用の備忘録だ。
自分もPythonに触れ始めてまだ間もないため、誤っている点がいくつかあると思う...
Pythonのクラス定義の基本
Pythonのクラス宣言は以下のようにやるらしい
class TestClass:
pass
tc = TestClass()
passは何もしないということ
要するにTestClassクラスは具体的な処理のない空っぽのクラスだ
tc = TestClass() は、Javaでいう
TestClass tc = new TestClass();
のようなことをしている。
インスタンス変数を定義することもできる
ただし
class TestClass:
val1 = "変数"
tc = TestClass()
print(tc.val1)
変数
ちなみに注意が必要で
このTestClassクラス直下に宣言されている変数val1はインスタンス変数ではない
class TestClass:
val1 = "変数"
tc = TestClass()
print(tc.val1)
tc.val1 = "変更してやったぜ"
# tcオブジェクトのval1は変わったが、TestClassのクラス変数val1は変わらない
print(tc.val1)
print(TestClass.val1)
変数
変更してやったぜ
変数
この変数はあくまでクラス変数であるため、定義する際には注意が必要だ。
Javaで例えるとstaticがつく感じだろうか
public class TestClass{
public static String val1 = "変数";
}
Pythonのインスタンス変数、コンストラクタの定義
Pythonのクラスでインスタンス変数を使うには、基本的にはコンストラクタの中で定義してやる必要がある。
そこでPythonのコンストラクタの定義方法についてだが、これもまたJavaとはだいぶ違う。
class TestClass:
def __init__(self):
self.val1 = "変数"
tc = TestClass()
print(tc.val1)
変数
Pythonでは、コンストラクタは
def __init__(self):
# 処理
のように定義する。
Javaではコンストラクタは大文字から始まるクラス名と同じ名称である必要があったため、ここも毛色が違う。
この__init__と名前の付いたメソッドは、クラスのインスタンスが作られたときにそのオブジェクト当たり一回だけ実行される(つまりコンストラクタ)
このメソッドの引数にあるselfは、このクラスのインスタンス自身を指している。
Pythonのクラス内でメソッドを定義する際に、必ず引数が1つは必要というPythonの仕様がある。
この引数に渡されるものの正体はインスタンス自身であり、メソッドを呼び出すときに自動的に第一引数として渡される。
そのため定義側に引数を用意してやらないと
{メソッド名} takes 0 positional arguments but 1 was given
というエラーが発生する。このエラーの内容は
メソッドに引数が定義されていないのに1つ渡されたぞオイ!
みたいな感じ、自動的に渡されるため定義してやる必要があるね。
selfという引数名を付けるのは慣習だとかなんとか。
整合性が取れればhogeでもhugaでもargでもなんでもいいらしい。
また
def __init__(self):
self.val1 = "変数"
のように、インスタンス変数にもselfをつけてあげる必要がある
これによって、val1というのがこのクラスのインスタンスの変数だよという事を表している。
なのでインスタンスが別のものであれば、このval1という変数もまた別に用意される。
コンストラクタの中で定義された変数は、インスタンス変数として使うことが出来る。
インスタンス変数を宣言するときに、代入なしで宣言できないものか?と思った
public class TestClass{
String val1;
int val2;
}
Javaでは、上記のように変数を宣言だけして用意しておくことが出来るが、Pythonでは、変数の宣言は必ず代入をセットで行わないとならないようだ。
なので、これと似たような宣言方法をとりたい場合、Pythonではインスタンス変数にNoneを代入して初期化しながら用意しておくと良い(らしい)
Pythonで使われるNoneは、Javaにおけるnullのように存在しないという意味合いだ
厳密には他にも意味があるのだが、nullみたいなものと思えばいいらしい。(初学者だし)
class TestClass:
def __init__(self):
self.val1 = None
self.val2 = None
tc = TestClass()
print(tc.val1)
tc.val1 = "変数1"
tc.val2 = "変数2"
print(tc.val1 + "と" + tc.val2)
None
変数1と変数2
Pythonのメソッドの定義
もう既にコンストラクタの定義の部分で触れてはいるが、あえて分けた。
といってもPythonではあくまでどちらもメソッドとして構造は全く同じため、あまり気にする必要はない。
ただ、Javaを学ぶ上ではコンストラクタとメソッドって結構しっかり分けられてた印象があったため、こちらでもそれに従った。
class TestClass:
def __init__(self):
self.val1 = None
self.val2 = None
def method1(self):
print("method1が呼び出されました")
def method2(self):
print("val1 = " + self.val1 + "\nval2 = " + self.val2)
tc = TestClass()
tc.val1 = "変数1"
tc.val2 = "変数2"
tc.method1()
tc.method2()
method1が呼び出されました
val1 = 変数1
val2 = 変数2
このように、コンストラクタと同じように定義する。
違う点は、コンストラクタはインスタンスが生成されたときに自動的に一度だけ呼び出されるのに対して、通常のメソッドは明示的に呼び出す必要がある。
また、戻り値のあるメソッドに関しては、return文を用意するだけで良い。
class TestClass:
def __init__(self):
self.val1 = None
self.val2 = None
def method1(self):
return self.val1 + self.val2
tc = TestClass()
# 文字列を渡した場合
tc.val1 = "変数1"
tc.val2 = "変数2"
print(tc.method1())
# 数値を渡した場合
tc.val1 = 2
tc.val2 = 5
print(tc.method1())
変数1変数2
7
ここでJavaと違う点は、メソッドの戻り値をカンマ区切りで複数用意することが出来る。
class TestClass:
def __init__(self):
self.val1 = None
self.val2 = None
def method1(self):
return self.val1, self.val2, self.val1 + self.val2
tc = TestClass()
tc.val1 = 3
tc.val2 = 5
result = tc.method1()
# この状態だとtuple型のデータが返される
print(result)
# tuple型として返された複数の戻り値は要素番号で個別に呼び出すことが出来る
print(result[0])
print(result[1])
print(result[2])
(3, 5, 8)
3
5
8
カンマで区切られた戻り値は、タプル型として返される。
Javaでは配列やリスト、マップで返していたのに比較すると、直感的でわかりやすいと思う。
if __name__ == "__main__": とは
Pythonで書かれたクラスのコードを見ると度々出会うこの謎の条件文
この条件文のブロックの中にメインの処理が書かれている場合が多い。
Pythonは実行させたいメインの処理をグローバルな場所に記述しても動く。
しかし、グローバルな場所に記述した場合、そのクラスをimportした時も勝手に処理が呼び出されてしまう。
if __name__ == "__main__":
という条件文のブロック内にメインの処理を記述すると、そのクラスを実行したときのみ処理が呼び出されるようにできる。
Javaでいうと
public static void main(String[] args){}
おなじみのメインメソッドのようなものと思ってもらえればいい。
class TestClass:
def __init__(self):
self.val1 = None
self.val2 = None
def pow_numbers(self):
return self.val1 ** self.val2
if __name__ == "__main__":
tc = TestClass()
tc.val1 = 5
tc.val2 = 3
print(tc.pow_numbers())
125
多くのサイトやブログでは、この条件文をおまじないのようなものとして説明していることが多い。
実際Javaのメインメソッドも、しばらくはおまじないとして書かされがちだ。
しかしPythonのこれは、あくまで条件文
__name__ と "__main__" さえ分かれば意味も理解できそうだ。
__name__ とはそもそも何か
下記の結果を見ればわかりやすいと思う
import numpy as np
print(__name__)
print(np.__name__)
__main__
numpy
__name__の正体は、Pythonがコードを実行するときに自動的に生成するグローバル変数の一つ
中には、モジュール名が格納される。つまり、importされたモジュールの__name__には、そのモジュール名が入る
しかし、プログラムを実行しているPythonスクリプトは、自動的に__main__というモジュール名で識別される。
要するに、if __name__ == "__main__":という条件文は
このスクリプトが実行元である場合、という意味合いになる。
別のスクリプトファイルでclass_test1.pyをインポートしてモジュールとして使う時は、この条件文のブロック内の処理は行われない。
Pythonのクラス定義のおさらい
今回はPythonのクラスを構成するインスタンス変数、コンストラクタ、メソッドについてまとめてみた。
これらを理解すると、下記のようにある程度自由にクラスを定義することが出来る。
class TestClass1:
def __init__(self):
self.array = []
self.num1 = None
self.num2 = None
self.str1 = None
self.str2 = None
def set_numbers(self, num1, num2):
self.num1 = num1
self.num2 = num2
def pow_numbers(self):
return self.num1 ** self.num2
def set_strings(self, str1, str2):
self.str1 = str1
self.str2 = str2
# このclass_test.pyが実行されたときに動く
if __name__ == '__main__':
# 上に定義したTestClass1クラスをインスタンス化
tc1 = TestClass1()
# set_numbers()メソッドで数値をインスタンス変数にセット
# sum_numbers()メソッドがインスタンス変数の数値同士を冪乗して戻す
tc1.set_numbers(2, 6)
sum_num = tc1.pow_numbers()
print(str(tc1.num1) + "の" + str(tc1.num2) + "乗は" + str(sum_num) + "です")
# set_strings()メソッドで文字列をインスタンス変数にセット
tc1.set_strings("こんにちは", "今日はいい天気ですね")
print(tc1.str1 + "、" + tc1.str2)
# インスタンス変数arrayに1から25までの奇数を順番に追加
for i in range(1, 27, 2):
tc1.array.append(i)
print(tc1.array)
# 一つ飛ばしで要素を削除
last_index = len(tc1.array)
count = int(last_index / 2)
for i in range(count):
tc1.array.pop(i + 1)
print(tc1.array)
# arrayの中身をクリア
tc1.array.clear()
print(tc1.array)
2の6乗は64です
こんにちは、今日はいい天気ですね
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25]
[1, 5, 9, 13, 17, 21, 25]
[]
自分がまだまだPythonの勉強を始めたばかりのため、色々と誤った認識のままでいる点等あると思う
今後新たな発見があったり、修正点を見つけたりしたら、その都度またまとめていこうと思う。