15
30

More than 3 years have passed since last update.

初学者なりにPythonのクラス定義を調べてみた

Last updated at Posted at 2021-05-13

Pythonのクラス定義の特徴


自分はJavaからプログラム言語の世界に入ったため、静的型付け言語のJavaやC#の記述方法が好き。
そのため、Pythonの勉強を始めたときも、クラス定義はJavaのように、クラス宣言、フィールド、コンストラクタ、メソッドのように作ったりしていた。

ただ、当然ながらPythonはJavaとは違うので、適当に作るとうまく動かなかったり、ネットに転がっている既存のPythonのクラスを見てもいまいちよく分からない記述があったりで、理解するのになかなか苦労した。

というわけで本記事は、Pythonのクラス定義に関する自分向けもしくは初学者用の備忘録だ。
自分もPythonに触れ始めてまだ間もないため、誤っている点がいくつかあると思う...

Pythonのクラス定義の基本

Pythonのクラス宣言は以下のようにやるらしい

python
class TestClass:
    pass

tc = TestClass()

passは何もしないということ
要するにTestClassクラスは具体的な処理のない空っぽのクラスだ
tc = TestClass() は、Javaでいう

java
TestClass tc = new TestClass();

のようなことをしている。

インスタンス変数を定義することもできる
ただし

python
class TestClass:
    val1 = "変数"

tc = TestClass()
print(tc.val1)
実行結果
変数

ちなみに注意が必要で
このTestClassクラス直下に宣言されている変数val1はインスタンス変数ではない

python
class TestClass:
    val1 = "変数"

tc = TestClass()
print(tc.val1)
tc.val1 = "変更してやったぜ"
# tcオブジェクトのval1は変わったが、TestClassのクラス変数val1は変わらない
print(tc.val1)
print(TestClass.val1)
実行結果
変数
変更してやったぜ
変数

この変数はあくまでクラス変数であるため、定義する際には注意が必要だ。
Javaで例えるとstaticがつく感じだろうか

java
public class TestClass{
    public static String val1 = "変数";
}


Pythonのインスタンス変数、コンストラクタの定義

Pythonのクラスでインスタンス変数を使うには、基本的にはコンストラクタの中で定義してやる必要がある。
そこでPythonのコンストラクタの定義方法についてだが、これもまたJavaとはだいぶ違う。

python
class TestClass:
    def __init__(self):
        self.val1 = "変数"

tc = TestClass()
print(tc.val1)
実行結果
変数

Pythonでは、コンストラクタは

python
def __init__(self):
    # 処理

のように定義する。
Javaではコンストラクタは大文字から始まるクラス名と同じ名称である必要があったため、ここも毛色が違う。

この__init__と名前の付いたメソッドは、クラスのインスタンスが作られたときにそのオブジェクト当たり一回だけ実行される(つまりコンストラクタ)

このメソッドの引数にあるselfは、このクラスのインスタンス自身を指している。
Pythonのクラス内でメソッドを定義する際に、必ず引数が1つは必要というPythonの仕様がある。
この引数に渡されるものの正体はインスタンス自身であり、メソッドを呼び出すときに自動的に第一引数として渡される。
そのため定義側に引数を用意してやらないと
{メソッド名} takes 0 positional arguments but 1 was given
というエラーが発生する。このエラーの内容は

メソッドに引数が定義されていないのに1つ渡されたぞオイ!

みたいな感じ、自動的に渡されるため定義してやる必要があるね。
selfという引数名を付けるのは慣習だとかなんとか。
整合性が取れればhogeでもhugaでもargでもなんでもいいらしい。
また

python
def __init__(self):
    self.val1 = "変数"

のように、インスタンス変数にもselfをつけてあげる必要がある
これによって、val1というのがこのクラスのインスタンスの変数だよという事を表している。
なのでインスタンスが別のものであれば、このval1という変数もまた別に用意される。



コンストラクタの中で定義された変数は、インスタンス変数として使うことが出来る。
インスタンス変数を宣言するときに、代入なしで宣言できないものか?と思った

java
public class TestClass{
    String val1;
    int val2;
}

Javaでは、上記のように変数を宣言だけして用意しておくことが出来るが、Pythonでは、変数の宣言は必ず代入をセットで行わないとならないようだ。
なので、これと似たような宣言方法をとりたい場合、Pythonではインスタンス変数にNoneを代入して初期化しながら用意しておくと良い(らしい)

Pythonで使われるNoneは、Javaにおけるnullのように存在しないという意味合いだ
厳密には他にも意味があるのだが、nullみたいなものと思えばいいらしい。(初学者だし)

python
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を学ぶ上ではコンストラクタとメソッドって結構しっかり分けられてた印象があったため、こちらでもそれに従った。

python
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文を用意するだけで良い。

python
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と違う点は、メソッドの戻り値をカンマ区切りで複数用意することが出来る。

python
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でいうと

java
public static void main(String[] args){}

おなじみのメインメソッドのようなものと思ってもらえればいい。

class_test1.py
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__ とはそもそも何か

下記の結果を見ればわかりやすいと思う

class_test1.py
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_test2.py
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の勉強を始めたばかりのため、色々と誤った認識のままでいる点等あると思う
今後新たな発見があったり、修正点を見つけたりしたら、その都度またまとめていこうと思う。

15
30
7

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
30