オブジェクト指向とかテンプレート等の言葉は一度忘れましょう
- Google先生やAI先生にプログラミング言語のクラスについて教えてもらうと、オブジェクト指向の...テンプレート化した...例えばanimalというクラスは...等と説明されますが、まぁ「よくわからない」(*1)ですよね。
- そのため本レポートでは、少し違う視点でクラスの説明を試みたいと思います。
あるタイプのデータでやりたいことは大体同じ
-
例えば下記のような表のデータが2種類あるとします。

- このようなデータを見たときにやりたいことは、多くの場合下記でしょう。
- 該当データファイルのRead
- 文字コード(utf-8/shift-jis)を処理する
- デリミタを処理する
- データの構造化
- 2次元のリストデータにする
- ある列(属性)を基準にした辞書ツリーデータにする
- 同じようなデータが複数ありますから、これらの処理を関数にすれば記述が楽になりますね。似たようなスクリプトを作る際も、前のスクリプトから関数をコピペすれば再利用もできます。
クラス:データと関数を名前空間(name space)でまとめたもの
- でも関数化した処理はデータのタイプで...この場合だと表データ(テーブル)に対して決まるということは、データと処理関数には元々密接な関係...つまりデータのタイプが処理を決めていると言えます。
- それなら、データ種別に対して決めた名前でデータ自身と頻出関数をまとめれば再利用が楽になる...これがクラスです。つまりデータと関数を名前空間(name space)でまとめたものです。
- ちなみに関数だけを外部ファイルにまとめたものがモジュールです。このモジュールとクラスの違いは、データ自身にも名前空間(クラス名)を紐づけしているか否かです。このクラス名(名前空間)が紐づけられたデータをインスタンスと呼びます。
__init__(self, ...)メソッドでデータとクラス名を紐づけ
- 実際にクラス名が紐づけられたデータを作成するのが __init__(self, 引数)メソッドです。メソッド引数中のselfは、クラス名と紐づけを行うデータ本体(の参照)です(*2)。
-
例えば下記を見て下さい。クラスのメソッドには
- インスタンス名.メソッド(引数)
- クラス名.メソッド(インスタンス名,引数)
の両方でアクセスできていることがわかるでしょうか。変なことをしていますが、怒られずに実行できますね。
#===========================================================
class Sample:
# __init__()メソッドは、クラスのインスタンス定義時に実行される(一般にコンストラクタと呼ばれる)
def __init__(self): # クラスのメソッド第1引数はself
self.data = {} # 空の辞書データをself.dataに割当(初期化)
self.data['val'] = None # キー'val'にも空データ(None)設定(初期化)
# データ取得メソッド
def get_data(self):
return self.data['val']
# データ設定メソッド
def set_data(self, val=None): # val=Noneはデフォルト引数
self.data['val'] = val;
return self.data['val']
#===========================================================
ins0 = Sample() # Sampleクラスのインスタンスins0定義
ins1 = Sample() # Sampleクラスのインスタンスins1定義
print("ins0: set_data:", ins0.set_data('abc')) # ins0のデータに'abc'設定
print("ins1: set_data:", ins1.set_data()) # ins1のデータは未定義(None)とする
print("ins0: get_data:", ins0.get_data()) # インスタンス名.メソッド()
print("ins1: get_data:", ins1.get_data()) # インスタンス名.メソッド()
print("ins0: get_data:", Sample.get_data(ins0)) # クラス名.メソッド(インスタンス名)
print("ins1: get_data:", Sample.get_data(ins1)) # クラス名.メソッド(インスタンス名)
>test.py
ins0: set_data: abc # ins0に'abc'を設定
ins1: set_data: None # ins1は未定義
ins0: get_data: abc # ins0.get_data()で値取得
ins1: get_data: None # ins1.get_data()で値取得
ins0: get_data: abc # Sample.get_data(ins0)で値取得
ins1: get_data: None # Sample.get_data(ins1)で値取得
クラスのインスタンスは、自分がどのクラス(名前空間)に所属しているの情報をもっているので
となっている記述をPythonインタープリターが下記のように解釈しているわけです。ここはPerlと同じですね。
関数/メソッド自身が多重に定義されているわけでは無いということです。実はモジュールと同じ。クラスメソッド先頭引数をselfとするルールも、これが理由であることがわかるかと思います。
すると当然、クラス変数とインスタンス変数の違いもわかりますね。
- クラス変数: クラス名.var: クラス名の名前空間で定義された変数(インスタンスに依存しない)
- インスタンス変数: self.var: インスタンスの参照オブジェクト(self)に紐づく変数
データそのものはインスタンス変数に紐づく(記述例のself.data等)のですが、これは結局「参照」なので、インスタンスが参照されなくなるとガベージコレクションで消えます。
ちなみにファイルを開き続ける処理などがある場合、インスタンスが無くなるとファイルが宙に浮いてしまいますね。こういった終了時に自動実行したい処理は __del__(self)メソッド に記述します。一般にデストラクタと呼ばれる処理です。
今回Pythonとして特別な話はありませんでした
- 今回レポートした内容は全てクラス一般の話になります。Pythonについて特別な事柄は一切ありません。今どきの言語は全てクラスの機能を持っています。Javaのようにクラスで縛る言語もあるくらいです。
- そのため、クラスの書き方がピンと来ない原因の多くは、そのプログラミング言語の知識ではなく「クラスを何に使うのか」への理解不足です。
- 更に「理解不足」が起きてしまいやすい原因は「書籍やセミナーの説明が欲張りすぎている」ことだと考えています。
- クラスの1番目の目的は「再利用しやすいコード管理」です。しかし、この「再利用性」を上げるため、クラスにはいろいろな機能が追加されてます。それが演算子オーバーロード/関数オーバーライド/継承/多態性です。
- この後半の機能説明に圧倒されて、よくわからないうちにクラスの説明が終わります。初中級者には物量的に無理です。しかし中級プログラミングで「追加機能を使う機会はありません」。インターフェースをダイナミックに変更するようなGUIを持つアプリケーション作成機会は、プロとしてソフトの仕事をしない限り、ほとんど訪れません(*3)。
- クラスは、コンストラクタとデストラクタまでわかっていれば、使える機会は非常に多いです。積極的に利用していきたいと考えています。
|