注記: 本稿は、東大AI研究会における勉強用資料として執筆されたものです。
クラスの意義
クラスを使わないと実装できないアルゴリズムは存在しない。理論上は、どのようなプログラミング言語であっても(チューリング完全であれば)同じ計算を行うことが可能であり、極論を言えばすべてを関数や手続きだけで記述することも、あるいは速度を求めて低水準なアセンブラで記述することも不可能ではない。プログラミングの記法は「人間にとってわかりやすく」(あるいはオートコンプリートや型チェックを行うエディタにとっても解釈しやすく)アルゴリズムを実装するために存在している。クラスもその例に漏れない。
プログラムがやっていることをわかりやすく、扱いやすくするための方法としてデータの持つ性質が伝わるようにするという方法がある。例えばAPIキーなどプログラム内で変更されることを想定されていない変数はAPI_KEYのように大文字で表記する文化があるがこのように変数名を工夫したりするのが一例である。また性質とはその意味だけではない。
型もデータの性質を示している。コンピュータのメモリ上の視点では、文字の「a」も数値の「97」も、同じバイナリデータ(2進数)として扱われ得る(※Pythonの内部ではこれらは単なる生データではなく、型情報を含んだオブジェクトとして扱われているのでその限りではない)。ある値が数値としての意味を持ち演算が意味を成すか否かを型はわかりやすく示してくれる。
また配列もその一種である。数値の集合があったとき、それぞれに変数を置いてもよいが、それを順序などで整数と対応付けて配列にすることでn番目の要素などの直観的な概念に従って要素を持ってこられる。
このようにすでにデータの様々な性質を表現可能であるが、プログラミングをしているとより複雑なデータ同士の関係性を表現したくなる時がある。そのような時に役に立つのがクラスである。またこれまでデータと述べてきたが意味的にはメモリ上にあるすべてのもの、プログラミングにおいて対象となり得るすべての物程度の広さで考えることができ、これはオブジェクトと呼ばれる。
用法
ある何かしらの対象が複数存在し、それらがそれぞれ独立した状態を持っていて、互いに影響しあって状態を更新するといった状況がある。さらにその対象が生まれたり影響しあったりというのがバラバラのタイミングに起きるのであればその構造をうまくわかりやすいプログラムに落とし込むことは難しい。このような時に使われるのがクラスである。その対象の持つルールをクラスの定義として実装し、対象が生まれるタイミングで名前を付けてその設計図に従って作った実体(実際にメモリを確保しデータと演算を保持するもの)を生み出してあげればよい。
このように書くと対象は生物的なもののように思えてしまうかもしれないが、そのほかにも様々なものが考えられ、たとえば対象をデータ処理のアルゴリズムだとすれば、全体として複数のデータ処理が組み合わされた複雑なデータ処理を可能にするものとなる。機械学習ライブラリのscikit-learnやPyTorchはまさにこのような機能を提供している。
クラスの仕様(コード内コメント)
実例および具体的なクラスの仕様の説明として、個人間の金銭のやりとりを表現するクラスを以下に示す。
注意点として、クラスという構造自体が処理として優れているわけではなくここでの実装は実用的でない。
"""
【スコープ(Scope)】
プログラム内で定義した変数の名前が通用する(参照・操作できる)有効範囲のことである。
クラスの外側で定義された変数はグローバルスコープを持ち、プログラムのどこからでも
参照できる(グローバル変数)。しかし、どこからでも値が書き換えられるため、
規模が大きくなるとバグの原因になりやすい。
そこで、データとその処理を安全な範囲(クラスの内部)に閉じ込めるのがクラスの重要な役割である。
"""
class Person:
"""
個人間の金銭のやりとりを表現するクラス。
ここに書かれているのはあくまで設計図であり、実際にデータを保持する実体を作成するには、
末尾の「--- 実行例 ---」にある `person_a = Person("Alice", 5000)` のように、
設計図を呼び出して実体を生成する処理が必要となる。
【インスタンス(Instance)】
プログラムにおいて操作対象となるものを総称してオブジェクトと呼ぶが、
クラス(設計図)を元にして、実際にメモリ上に生成された個別のオブジェクトのことを
特に「インスタンス(実体)」と呼ぶ。
"""
"""
==========================================
1. 初期化処理とメソッド、インスタンス変数
==========================================
"""
def __init__(self, name: str, initial_balance: int = 0):
"""
【メソッド(Method)】
クラスの内部で `def` を用いて定義された関数のことを「メソッド」と呼ぶ。
単なる関数とは異なり、オブジェクト自身が持つデータ(変数)を操作するための処理である。
この `__init__` メソッドは、新しいインスタンスが生成される瞬間に自動で実行される。
(__init__ は後述する特殊メソッドの一種である)
【self と インスタンス変数】
メソッドの第一引数 `self` には、「今まさに生成・操作されているインスタンス自身」が代入される。
`self.name = name` のように定義された変数を「インスタンス変数」と呼び、
インスタンスごとに独立したメモリ領域に保存される。
(末尾の実行例において、person_a と person_b がそれぞれ "Alice" や "Bob" という
別々の名前を保持し、混ざり合わないのはこの仕組みのおかげである)。
"""
self.name = name
"""
変数名の先頭のアンダースコア1つ(_)は「外部から直接アクセスすべきではない」ということを示す慣習である。
先頭をアンダースコア2つ(__balance)にすると、Pythonの仕様
により内部的な変数名が_ClassName__balanceに変更され疑似的に外部からアクセスできなくなるが、
通常のデータ保護には1つ(_)と後述するプロパティの組み合わせが好まれるらしい。
"""
self._balance = initial_balance
"""
インスタンス生成時、後述するクラス変数を更新している。
"""
Person.total_population += 1
"""
==========================================
2. 通常のインスタンスメソッド
==========================================
"""
def transfer(self, to_person: 'Person', amount: int):
"""
インスタンス自身のデータ(self)を用いた処理を定義する。
末尾の実行例のように `person_a.transfer(person_b, 2000)` と呼び出されるが、
Pythonの内部仕様としては `Person.transfer(person_a, person_b, 2000)` と解釈されており、
呼び出し元の `person_a` が自動的に第一引数 `self` に割り当てられる。
したがって、クラス内でメソッドを定義する際は、引数の最初に `self` を書き忘れないよう注意すること。
"""
if self.balance < amount:
raise ValueError("残高が不足している。")
"""
後述するプロパティのセッターを経由して安全に値を更新する
"""
self.balance -= amount
to_person.balance += amount
"""
==========================================
3. クラス変数
==========================================
クラスの直下(メソッドの外)で定義された変数をクラス変数と呼ぶ。
各インスタンスが個別に持つインスタンス変数(self.nameなど)とは異なり、
ここで定義されたデータは、このクラスから作られるすべてのインスタンスで共有される。
メモリの無駄を省き、全体の共通設定(利用銀行名など)を管理するために用いる。
"""
bank_name = "Qiita Bank"
total_population = 0
"""
==========================================
4. プロパティとデコレータ
==========================================
【プロパティ(Property)】
英単語としては「特性」「属性」「所有物」を意味する。プログラミングにおいては、
オブジェクトが持つ状態(属性)に対して、安全にアクセスするための仕組みを指す。
外部から `self._balance` へ不正な値(マイナス等)が代入されるのを防ぐために用いる。
【デコレータ(Decorator)】
`@` 記号から始まる記述をデコレータと呼ぶ。これは直後に定義された関数やメソッドに対し、
Pythonが用意している特殊な振る舞いを上書き(修飾)するための構文である。
なお、標準で用意されているものだけでなく、独自の処理を行うデコレータを自作することも可能である。
"""
@property
def balance(self) -> int:
"""
【ゲッター(取得)の仕様】
`@property` デコレータをつけたメソッドは、呼び出し時にカッコ () が不要になる。
プログラム内で `print(person_a.balance)` のように変数(属性)としてアクセスされた際、
Pythonはこのメソッドを自動的に実行し、その return 値を返す仕様となる。
"""
return self._balance
@balance.setter
def balance(self, value: int):
"""
【セッター(更新)の仕様】
`@プロパティ名.setter` デコレータは、代入演算子(=)を上書きする。
`person_a.balance = 1000` のように代入処理が書かれたとき、通常の変数代入は行われず、
代わりにこのメソッドが実行される。右辺の値(1000)は引数 `value` に渡される。
これにより、代入前に「マイナスではないか」などの検証を挟み込むことができる。
"""
if value < 0:
raise ValueError("残高をマイナスにすることはできない。")
self._balance = value
"""
==========================================
5. クラスメソッド
==========================================
"""
@classmethod
def create_billionaire(cls, name: str):
"""
第一引数にインスタンス(self)ではなく、クラス自体(cls)を受け取るメソッドである。
インスタンスを生成する前であっても、`Person.create_billionaire("名前")` のように
クラスから直接呼び出すことができる。
標準の初期化 (`__init__`) とは異なる手順でインスタンスを生成したい場合に利用される。
"""
return cls(name, initial_balance=1000000000)
"""
==========================================
6. スタティックメソッド
==========================================
"""
@staticmethod
def calculate_tax(amount: int) -> int:
"""
第一引数に `self` も `cls` も受け取らない特殊なメソッドである。
インスタンス変数にもクラス変数にもアクセスできないため、実態はただの関数と同じである。
しかし、税金計算のように論理的にこのクラスに関係の深い処理を、
クラスのスコープ内に整理して置きたい場合に用いられる。
"""
return int(amount * 0.1)
"""
==========================================
7. 特殊メソッド
==========================================
メソッド名の前後を2つのアンダースコア(__)で囲まれたものを特殊メソッドと呼ぶ。
これらは直接呼び出すためのものではなく、特定の組み込み関数や演算子が使われた際に、
Pythonによって背後で自動的に実行される仕様である。
"""
def __str__(self):
"""
`print(オブジェクト)` や文字列変換が実行された際に自動で呼び出される。
オブジェクトを人間が読みやすい文字列として表現した結果を return する必要がある。
"""
return f"{self.name} (残高: {self.balance}円)"
def __eq__(self, other):
"""
`==` 演算子による比較評価が行われた際に自動で呼び出される。
左辺が `self` に、右辺が `other` に代入される。
何を以て「2つのインスタンスが等しい」と判定するかの仕様をここで定義する。
ここでは二人の人の金額が一致しているかどうかを==で判定できるようにしている。
"""
if not isinstance(other, Person):
return NotImplemented
return self.balance == other.balance
"""
==========================================
8. 継承とオーバーライド
==========================================
"""
class VIPPerson(Person):
"""
【継承】
既存のクラス(親クラス)の仕様(変数やメソッド)をすべて引き継ぎつつ、
新しい機能を拡張した別のクラス(子クラス)を定義する構文である。
"""
def __init__(self, name: str, initial_balance: int = 0, rank: str = "Gold"):
"""
子クラスで `__init__` を定義した場合、親クラスの初期化処理は自動では実行されない。
親のインスタンス変数(nameや_balance)を正しくセットするためには、
`super()` という関数を用いて、明示的に親クラスのメソッドを呼び出す仕様となっている。
"""
super().__init__(name, initial_balance)
self.rank = rank
def transfer(self, to_person: 'Person', amount: int):
"""
【オーバーライド(上書き)】
親クラスに存在するメソッドと同じ名前のメソッドを子クラスで再定義すること。
子クラスのインスタンスから呼び出された場合は、こちらの新しい処理が優先して実行される。
これにより、既存の処理(送金)の一部を変更・拡張することができる。
"""
print(f"[VIP送金] {self.name}から{to_person.name}へ{amount}円の送金を実行する。")
super().transfer(to_person, amount)
"""
--- 実行例 ---
"""
if __name__ == "__main__":
"""
1. インスタンスの生成(__init__ によるインスタンス変数の確保)
ここで初めてAliceやBobという具体的なデータを持った実体が生まれる
"""
person_a = Person("Alice", 5000)
person_b = Person("Bob", 1000)
"""
2. メソッドによる相互作用。インスタンス変数の操作。
"""
person_a.transfer(person_b, 2000)
"""
3. クラス変数へのアクセス。すべてのインスタンスで共有されている。
"""
print(f"利用銀行: {Person.bank_name}")
"""
4. プロパティを通じた安全なアクセス
構文上は変数アクセスに見えるが、内部ではゲッターメソッドが実行されている
"""
print(f"現在の残高: {person_a.balance}")
"""
セッターメソッドを通じた代入。値の検証が行われる。
person_a.balance = -500 <- これを実行すると ValueError が発生する
"""
"""
8. 継承クラスのインスタンス生成と、オーバーライドされたメソッドの実行
"""
vip_person = VIPPerson("Charlie", 10000, "Platinum")
vip_person.transfer(person_a, 5000)
主要な特殊メソッド一覧
特殊メソッドは様々なものがあり、中にはマイナーなものもある。そういうものは公式のドキュメントを見るのがよい。
Python 公式ドキュメント: データモデル(特殊メソッド名)
1. 生成と破棄
インスタンスが生まれてから消えるまでに関わるメソッドである。
| メソッド | 呼ばれるタイミング・用途 |
|---|---|
__new__(cls, ...) |
インスタンスを生成する際に呼ばれる(__init__より先)。真のコンストラクタであり、シングルトンパターンの実装などに用いる。 |
__init__(self, ...) |
生成されたインスタンスの初期化を行う。インスタンス変数の定義などはここで行う。 |
__del__(self) |
インスタンスがメモリから破棄される際に呼ばれるデストラクタ。色々要注意。 |
2. 文字列表現
print()やログ出力など、オブジェクトを文字列として評価する際に使われる。
| メソッド | 呼ばれるタイミング・用途 |
|---|---|
__str__(self) |
str() や print() に渡された際に呼ばれる。人間にとって読みやすい文字列を返すのが目的。 |
__repr__(self) |
repr() に渡された際や、対話モードで評価された際に呼ばれる。開発者向けの詳細な表現であり、eval() でオブジェクトを復元できる文字列を返すことが望ましい。 |
3. 比較演算子
オブジェクト同士を比較する際に呼ばれる。データクラスのソート基準を定める際などに役立つ。
標準ライブラリのfunctools.total_orderingを使えば__eq__ともう一つ別の比較演算子を定義するだけで他を補完してくれる。
| メソッド | 呼ばれるタイミング・用途 |
|---|---|
__eq__(self, other) |
== (等しいか)。デフォルトではオブジェクトのIDを比較するが、値の比較に書き換えることが多い。 |
__ne__(self, other) |
!= (等しくないか)。通常は __eq__ の逆となる。 |
__lt__(self, other) |
< (より小さい)。ソート(sorted()等)の基準としてよく使われる。 |
__le__(self, other) |
<= (以下)。 |
__gt__(self, other) |
> (より大きい)。 |
__ge__(self, other) |
>= (以上)。 |
4. 算術演算子
足し算や掛け算などを独自定義できる。ベクトルクラスなどで重宝する。
なお左辺と右辺が逆の場合(__radd__など)や、+= の場合(__iadd__など)の専用メソッドも存在する。
| メソッド | 呼ばれるタイミング・用途 |
|---|---|
__add__(self, other) |
+ (加算)。 obj + other と書いた際に実行される。 |
__sub__(self, other) |
- (減算)。 |
__mul__(self, other) |
* (乗算)。 |
__truediv__(self, other) |
/ (真の除算)。Python3における通常の割り算。 |
__floordiv__(self, other) |
// (切り捨て除算)。 |
__mod__(self, other) |
% (剰余・あまり)。 |
__pow__(self, other) |
** (べき乗)。 |
5. コンテナ・コレクション(配列や辞書の模倣)
オブジェクトをリストや辞書のように振る舞わせるためのメソッドである。
| メソッド | 呼ばれるタイミング・用途 |
|---|---|
__len__(self) |
len() に渡された際に呼ばれる。要素数を整数で返す。 |
__getitem__(self, key) |
obj[key] のように、インデックスやキーで要素を取得しようとした際に呼ばれる。 |
__setitem__(self, key, value) |
obj[key] = value のように、要素を代入・更新しようとした際に呼ばれる。 |
__delitem__(self, key) |
del obj[key] のように、要素を削除しようとした際に呼ばれる。 |
__contains__(self, item) |
item in obj のように、in 演算子で要素が含まれているかを判定する際に呼ばれる。 |
6. イテレーション(反復処理)
for 文などで要素を順番に取り出せるようにする。
| メソッド | 呼ばれるタイミング・用途 |
|---|---|
__iter__(self) |
forループの開始時や iter() に渡された際に呼ばれる。反復処理を行うイテレータオブジェクト(通常は self)を返す。 |
__next__(self) |
__iter__ とセットで実装する。呼ばれるたびに次の要素を返し、要素が尽きたら StopIteration 例外を投げる仕様である。 |
7. 属性アクセス
インスタンス変数の取得や更新の挙動を根本から変更するメソッド群である。
| メソッド | 呼ばれるタイミング・用途 |
|---|---|
__getattr__(self, name) |
obj.name にアクセスされた際、その属性が存在しなかった場合のみ呼ばれる。動的な属性生成に便利。 |
__getattribute__(self, name) |
obj.name にアクセスされた際、属性が存在してもしなくても必ず呼ばれる。無限ループのバグを生みやすいため通常は触らないらしい。 |
__setattr__(self, name, value) |
obj.name = value のように、属性に値が代入される際に必ず呼ばれる。 |
8. その他(呼び出し・コンテキストマネージャ)
| メソッド | 呼ばれるタイミング・用途 |
|---|---|
__call__(self, *args, **kwargs) |
obj() のように、インスタンスを関数のように呼び出した際に実行される。PyTorchのモデル(model(x))などで頻繁に利用される。 |
__enter__(self) |
with 文ブロックに入る際に呼ばれる。ファイルを開く、DB接続を開くといった処理を記述する。 |
__exit__(self, exc_type, ...) |
with 文ブロックから出る際に呼ばれる(エラー発生時も必ず呼ばれる)。ファイルや接続を安全に閉じるクリーンアップ処理を記述する。 |
主要なデコレータ一覧
関数やメソッド、クラスの振る舞いを修飾する「デコレータ」は、自分で独自の処理を定義して自作することも可能である。ここでは、Pythonの組み込み機能や標準ライブラリに用意されている、クラス設計で頻出するデコレータを紹介する。
1. 組み込みデコレータ
インポート不要で標準で利用できる、クラスやメソッドの性質を変えるデコレータである。
公式ドキュメントでは以下で述べられている。
Python 公式ドキュメント: 組み込み関数
| デコレータ | 呼ばれるタイミング・用途 |
|---|---|
@property |
メソッドを変数(属性)のようにアクセス可能にする。ゲッターとして機能する。 |
@プロパティ名.setter |
@propertyで定義した属性に対し、値が代入された際の処理を定義する。セッターとして機能する。 |
@classmethod |
第一引数にクラス自身(cls)を受け取るクラスメソッドを定義する。インスタンスを別の方法でも作りたいとき(Factory Method パターン)等に用いる。 |
@staticmethod |
selfもclsも受け取らない静的メソッドを定義する。クラス内の独立した関数として機能する。 |
2. 標準ライブラリのデコレータ
高度な設計や最適化に用いられる、モジュールのインポートが必要なデコレータである。
| デコレータ | 呼ばれるタイミング・用途 |
|---|---|
@dataclass |
dataclassesモジュールからインポートする。データ保持用のクラスに付与し、__init__や__eq__などの特殊メソッドを自動生成する。 |
@abstractmethod |
abcモジュールからインポートする。抽象クラスにおいて、子クラスでの実装(オーバーライド)を強制するメソッドを定義する。 |
@cached_property |
functoolsモジュールからインポートする。プロパティの計算結果を初回にキャッシュし、2回目以降のアクセス時は計算を省略して値を返す。 |