Pythonのclass/定義
PythonのClassの書き方
Pythonでは、classを定義してオブジェクト指向的にプログラミングが可能です。
参考)9. クラス — Python 3.10.4 ドキュメント
クラスの定義の書き方はこんな感じ。
class MyClass: """A simple example class""" i = 12345 def f(self): return 'hello world'
MyClassというクラスを定義し、クラス定義の中でクラス変数iに12345を代入し、関数f()を定義しています。
このとき、クラス内で定義している関数をメソッドと呼びます。
以下のように記述して、インスタンスを生成できます。
x = MyClass()
Pythonのオブジェクト指向の基本
Pythonでclassが役に立つのは大規模なプログラムを作成する場合です。
すでに作ってある関数を、ほんのちょっとだけ変えて使いまわしたいな…というときに役立つんですね。
クラス定義を利用するプログラミングをオブジェクト指向プログラミングと呼びます。
クラスって何?どんなときに役立つの?って考えたとき、動画なんかをみても、私はイマイチよくわからなかったんですよね。
いやいや、もっとこう言葉じゃなくて、人形劇的なやつで概念を説明してくれないかなって思いました。
私が初見のときに、こういう解説あったら助かったな…というものを書いておきます。オブジェクト指向のざっくりした考え方はこんな感じ。
まず、「私」をコードで記述できるとしましょう。
クラス定義なんか使わなくても、単純に「私」をコードで記述すればいいですよね。
- 私のコード
では、「彼女」ができたとしたら?
「私」と「彼女」をそれぞれコード記述すればいいですね。
- 私のコード
- 彼女のコード
さらに二人が結婚して、娘と息子が生まれたとしたら?
「私」「妻」「息子」「娘」をそれぞれ別々にコーディングすればOKです。
- 私のコード
- 妻のコード
- 息子のコード
- 娘のコード
しかし、ちょっと待って下さい。
こうなってくると、この先孫やひ孫が生まれることが想定されますし、世代が進むごとに新たに別コードを記述するのは効率が悪い…と思いませんか?
さかのぼって、「私」だけの場合にこの先のことを考えて、共通で使いまわせそうな単位に分割しておきます。このとき、クラスという考えが役立つんですね。
- 人間
- 性別…男
- 世代…10代
- 役割…なし
将来を考えて、こんな風に私をクラス定義してみました。
続いて、彼女ができた状態を、できるだけ共通化して使い回すことを考えてクラス定義してみます。
私…
- 人間
- 性別…男
- 世代…20代
- 役割…なし
彼女…
- 人間
- 性別…女
- 世代…20代
- 役割…なし
さらにこの状態をクラス定義するとこうなります。
私…
- 人間
- 性別…男
- 世代…30代
- 役割…夫
妻…
- 人間
- 性別…女
- 世代…30代
- 役割…妻
息子…
- 人間
- 性別…男
- 世代…10歳以下
- 役割…息子
娘…
- 人間
- 性別…女
- 世代…10歳以下
- 役割…娘
このさき、孫やひ孫ができても、同じような感じで使い回しができそうですよね。
PythonのClassの使い方
pythonのclassの使い方
上記の例のようにベースになるオブジェクト(変数と関数のかたまりみたいなもの)をクラス定義しておき、コンストラクタ(値の初期化)や、オーバーライド(コードの上書き)を使うと、コードの使い回しがスッキリ記述できるんですね。
クラス定義を利用するプログラミングをオブジェクト指向プログラミングと呼びますが、まずは関連用語をざっと理解してとりかかるのが良いでしょう。
- インスタンス…クラス定義を実体化する。インスタンス化しないとクラス定義したコードは実行されない。
- オーバーライド…クラス定義したコードの一部を書き換えて使い回す
- コンストラクタ…インスタンス生成時に値を初期化
- デストラクタ…インスタンス削除時に呼ばれる処理
クラス定義を一から記述するのは、相当オブジェクト指向になれていないと無理な気がします。
pythonのメソッド(method)
クラス内で定義した関数をメソッドと呼びます。
下記の例は、クラスningen内でメソッドfunction()を定義している例です。
class ningen: def function(self): print('ningen') obj = ningen() obj.function()
上記のコードに、pymethod.pyというファイル名をつけて実行するとこうなります。
$ python3 pymethod.py ningen
pythonのsuper class(スーパークラス)とオーバーライド
pythonはsuper()を使って、親クラスの内容をクラス定義に使うことができます。また、親クラスから派生して定義したクラスにて、メソッドを書き換え(追加)することができます。これをオーバーライドと言います。
下記のコードは、ningenというクラスから派生したningen1というクラスを定義している例です。ningen1内のメソッドにfunction1を追加し、親クラス(ningen)のfunction()をsuper()で再利用しています。
class ningen: def function(self): print('ningen') class ningen1(ningen): def function1(self): super().function() print('ningen1') obj = ningen1() obj.function1()
上記のコードに、pykeishou.pyというファイル名をつけて実行するとこうなります。
$ python3 pykeishou.py ningen ningen1
pythonの継承
pythonの継承は、複数のクラスを継承(多重継承)できます。
以下の例は、ningenとotokoという2つのクラスを継承したwatashiを定義した例です。
class ningen: def function1(self): print('ningen') class otoko: def function2(self): print('otoko') class watashi(ningen, otoko): def function(self): super().function1() super().function2()
上記のコードに、pyclasstest.pyというファイル名をつけ、実行するとこうなります。
$ python3 pyclasstest.py ningen otoko
pythonのinit(コンストラクタ)とデストラクタ
pythonのクラス定義内で、__init__または__new__というメソッドを作ると、インスタンス生成時や初期化時に実行されます。__init__と__new__をコンストラクタと呼び、インスタンス生成時に指定された引数を使って変数の初期化などができます。
参考)__new__と__init__ — Python 3.10.4 ドキュメント
じゃあ、__init__と__new__の違いは?好きな方使えばいいの?
実は、この2つは微妙に違います。
- __new__ … インスタンス生成時に呼ばれるメソッド
- __init__ …インスタンスを生成後に呼ばれるメソッド
多くの場合、どっちでも良いのですが、インスタンスを生成しようとしたときに条件次第でインスタンスを生成せずに終了する
というような処理を記述したい場合に、__new__と__init__を使い分けるんですね。__new__は生成時に呼ばれる、__init__はインスタンスを生成した後に呼ばれるという微妙な違いがあります。
また、インスタンス削除時に呼ばれる__del__というメソッドをデストラクタと呼ばれます。
- __del__…デストラクタは、インスタンスを削除時に呼ばれるメソッド。
参考)デストラクタ — Python 3.10.4 ドキュメント
pythonのclass変数とインスタンス変数
pythonのクラス定義で、class変数を定義できます。クラス変数は同じクラスから生成したインスタンス同士で共有できる値です。インスタンスごとに異なる値を持たせるにはインスタンス変数を使用します。
以下は、クラスningenから生成したobj1とobj2でクラス変数とインスタンス変数の値を変更し、値を表示するコードです。
class ningen: classvalue=1 def __init__(self): self.instancevalue=1 def function(self): print('classvalue=%d' % self.classvalue ) print('instancevalue=%d' % self.instancevalue ) print('ningen') obj1 = ningen() obj2 = ningen() print('### obj1') obj1.function() print('### obj2') obj2.function() print('### クラス変数とインスタンス変数を変更') ningen.classvalue=3 obj1.instancevalue=5 print('### obj1') obj1.function() print('### obj2') obj2.function() print('### クラス変数と同名のインスタンス変数を新たに作ることになる') obj1.classvalue=10 obj1.instancevalue=20 print('### obj1') obj1.function() print('### obj2') obj2.function() print('### ningen.classvalue=%d' % ningen.classvalue ) print('### obj1.classvalue=%d' % obj1.classvalue ) print('### obj2.classvalue=%d' % obj2.classvalue )
上記を実行するとこうなります。
### obj1 classvalue=1 instancevalue=1 ningen ### obj2 classvalue=1 instancevalue=1 ningen ### クラス変数とインスタンス変数を変更 ### obj1 classvalue=3 instancevalue=5 ningen ### obj2 classvalue=3 instancevalue=1 ningen ### クラス変数と同名のインスタンス変数を新たに作ることになる ### obj1 classvalue=10 instancevalue=20 ningen ### obj2 classvalue=3 instancevalue=1 ningen ### ningen.classvalue=3 ### obj1.classvalue=10 ### obj2.classvalue=3
クラス変数にアクセスするには、クラス名.クラス変数名(上記の例ではningen.classvalue)を使用します。クラス変数名を変更するつもりで、インスタンス名.クラス変数名(上記の例ではobj1.classvalue)に値を代入してしまうと、クラス変数名と同名のインスタンス変数が生成されて、そのインスタンスからは同名のクラス変数にアクセスできなくなってしまうんですね。
また、値を代入する前なら、インスタンス名.クラス変数名(上記例でobj1.classvalue)という記述でクラス変数の値の参照はできてしまうのもややこしいポイント。
pythonは変数の宣言なしでいきなり変数が利用できてしまう、という点が悪い方向に効いてしまっている感じですね。言語としての仕様なので、当然警告なども出ません。
ここでけっこうハマる人が多いです。クラス変数の動きがおかしい、と思ったら、意図せず同名のインスタンス変数を生成してしまっていないかチェックしましょう。