[Python]クラスってなんだろう
最近よく耳にするオブジェクト指向プログラミング
オブジェクトって何?それメリットあんの?
そう思う方も多いことでしょう。
Python3を利用する場合、どんな使い方ができるのかを例にあげながら解説します。
クラス(class)
オブジェクト指向プログラミングでは多くの言語でクラス(class)と言うものが利用されます。
Pythonでもオブジェクト指向の書き方をする際はclass
というキーワードが利用されます。
オブジェクト指向プログラミング
プログラムの中に登場する部品を現実世界における物
(オブジェクト)のように扱うプログラムの書き方のことです。
ここでは銀行を例にとって紹介しようと思います。
クラスの定義
オブジェクト指向プログラミングにおけるクラスとはいわば設計図のようなものです。
以下に、銀行の設計図class
を書いてみました。
class Bank:
pass
class Bank
って書いただけで中身は空っぽです。
pass
は何もしないという意味です。
これがPythonにおける最小のクラス定義の仕方です。
インスタンス
インスタンスというのもオブジェクト指向プログラミングではよく聞く単語です。
先ほどの、クラスが設計図というならばインスタンスとはその実体、先ほどの例で言うならば銀行の支店と言ったところでしょうか。
世の中に銀行っていっぱいありますよね。
それと同じように、先ほど定義したBank
もいっぱいあった方が便利。
なら、さっきの設計図を利用して銀行を量産しちゃえばいいじゃん!っていうのがインスタンス化です。
で、実際どうやってインスタンス化するの?
class Bank:
pass
bank1 = Bank()
先ほど定義したクラスBank
の後ろに()
をつけました。
これだけ。
関数呼び出しとそっくりですね。
これで、銀行はめでたくインスタンス化
(建設)されました。
クラスのインスタンスは一つだけでなく複数作ることができます。
bank2 = Bank()
bank3 = Bank()
bank4 = Bank()
理屈の上では、いくらでも作れます。
やったぁこれでお金預け放題だぁ〜(預ける金がねぇ!)
インスタンス変数
お金がないのは置いておいて、自分が銀行を利用するならそれぞれに支店ごとに口座情報がないとダメですよね。
そこで登場するのがインスタンス変数
先頭にインスタンスなんてついていますが、いわゆる変数と同じです。
普通の変数と少し異なるのは、その変数はインスタンス毎に独立という点です。
まずは、銀行に講座情報が保存できるようにバンククラスの定義を変更しましょう。
class Bank:
def __init__(self, amounts):
self._amounts = amounts.copy()
はい、変更しました。
何やら見慣れないものがいくつか混じっていますね。
それぞれ解説します。
__init__
これは、クラスをインスタンス化する際に自動的に呼ばれる関数です。
俗に、コンストラクタなんて呼ばれ方をします。
先ほどクラスをインスタンス化する際にクラス名の後ろに()
をつけましたよね?
あのタイミングで呼ばれるのが、__init__
関数です。
bank1 = Bank() # <- ここで__init__関数が呼ばれる
さっき
__init__
定義しなかったじゃん…__init__
を自分で定義しなかった場合は、自動的に
class Bank:
def __init__(self):
pass
になると思ってください。
__init__
を定義する際は、第一引数に必ずself
とかく必要があります。
これは決まりごとなので、そういうものだと思ってください。
第二引数以降は定義してもしなくても構いません。
初期状態として、値を渡したい場合に引数をとるようにします。
今回は、銀行の口座情報を辞書で表現したいと思いますのでamounts
という引数をとるようにしました。
辞書のキーがユーザー名でバリューが残高です。
続いて__init__
の中身についてです。
class Bank:
def __init__(self, amounts):
self._amounts = amounts.copy()
amounts.copy()
でわざわざcopy
を実行している理由は、pythonの辞書はただ代入するだけではコピー代入にならないからです。
詳しい話は、shallow copy
とかdeep copy
でググると出てきます。
第二引数で受け取ったamounts
をself._amounts
に代入しています。
amounts
は、わかるけどself._amounts
ってなんじゃい!
_amounts
こそがインスタンス変数です。
self
というのが、そのクラスがインスタンス化(建設)された時のインスタンス(銀行)自身を指します。
なのでself._amounts
というのは、インスタンス化されたBankクラスの口座情報ということになります。
そこにamounts
を代入しているので、インスタンス化した銀行に口座情報を持たせることができるという流れです。
聞いてもわからんの人も多いと思うので実際に動かしてみましょう。
class Bank:
def __init__(self, amounts):
self._amounts = amounts.copy()
bank1 = Bank({"太郎": 1000, "二郎": 2000})
おいどうした、さっきインスタンス化するときに__init__
関数が呼ばれるとか言ってたけど引数一個しか取ってねぇじゃねえか!!
はいそうです、ごめんなさい。
説明が遅れました。
__init__
は定義するときは、第一引数にself
を書く必要があるのですが、呼び出すときは必要ありません。
なんでかって、インスタンス化する前だと、どうやって第一引数にインスタンス化された自分自身を入れればいいの?っていう話になりますし、毎回書くのめんどくさいですから。
なので、第一引数は省略して、第二引数から始めています。
そんなわけで銀行に"太郎"
さんと"二郎"
さんの口座情報を持たせてインスタンス化してみました。
インスタンス化した銀行クラスは、変数bank1
に入ってもらったので本当に口座情報が正しいか確認してみます。
インスタンス.クラス変数名
インスタンスの後ろに.
ドットをつけてインスタンス変数名を書くことで、インスタンス変数にアクセスできます。
bank1 = Bank({"太郎": 1000, "二郎": 2000}) # Bankクラスのインスタンスを作成して変数`bank1`に代入
print(bank1._amounts) # `変数.クラス変数名`で中身を確認
# {'太郎': 1000, '二郎': 2000}
ちゃんと入ってました。
いろんな銀行作っちゃお
bank2 = Bank({"三郎": 100})
print(bank2._amounts)
# {'三郎':100}
bank3 = Bank({"四郎": 1400, "五郎": 198})
print(bank3._amounts)
# {'四郎':1400, '五郎':198}
さて、お気付きの方も多いとは思いますが、先ほどインスタンス変数はインスタンス毎に独立、と申し上げた通りそれぞれの銀行のインスタンスに同じ._amounts
で名前にアクセスしていますが、全て違う口座情報が入っています。
これは_amounts
という変数がそれぞれのインスタンス毎に別物であることを示しています。
bank2 = Bank({"三郎": 100})
print(bank2._amounts)
# {'三郎':100}
bank3 = Bank({"四郎": 1400, "五郎": 198})
print(bank3._amounts)
# {'四郎':1400, '五郎':198}
bank2._amounts = {"三郎": 500}
print(bank2._amounts)
# {'三郎':500}
print(bank3._amounts)
# {'四郎':1400, '五郎':198}
ちなみに、こんな感じでインスタンス化した後から口座情報を変更することもできます。
この際に、他の銀行の口座情報が変わっていないことからもインスタンス変数_amounts
は、インスタンス毎に独立していることがわかります。
インスタンスメソッド
さっきのインスタンス変数がインスタンスに紐づいた変数だとすれば、インスタンスメソッドは、インスタンスに紐づいた関数です。
早速、インスタンスメソッドを作ってみましょう。
class Bank:
def __init__(self, amounts):
self._amounts = amounts.copy()
def transfar(self, sender, receiver, yen):
self._amounts[sender] -= yen
self._amounts[reciver] += yen
新しくtransfar
という関数(インスタンスメソッド)を定義しました。
同一支店内で口座間のお金を移動する動作を定義しています。
__init__
関数 同様、第一引数には必ずself
をつける必要があります。
勘のいいみなさんはもうお気付きですね、transfar
の中でself._amounts
にアクセスしています。__init__
関数の説明で述べたように、self
はインスタンス化されたクラスを示すので、self._amounts
のなかに入っているのは、インスタンス化された銀行の口座情報です。
実際に実行してみると以下のようになります。
bank = Bank({"太郎": 1000, "二郎": 200)
bank.transfar("太郎", "二郎", 100)
print(bank._amounts)
# {'太郎':900, '二郎':300}
このことからわかるように、インスタンスメソッドは、そのインスタンスのインスタンス変数にアクセスできるという特性があります。
インスタンスメソッドの中で、インスタンス変数の書き換えや、インスタンス変数を増やすこともできます。
__init__
関数は呼び出されるタイミングが特殊なインスタンスメソッドである、と捉えることもできます。
インスタンスメソッドの定義の際、気をつけて欲しいのは、インデントを一つ下げて関数定義を行う、ということです。
これは先ほどの__init__
関数に対しても言えることなのですが、その関数がクラスに紐づいていることを示すためにインデントをひとつ下げる必要があります。
クラス変数
インスタンス変数がインスタンス毎に独立した変数であったようにクラスそのものにも変数を持たせることができます。
それがクラス変数です。
class Bank:
number = 0
def __init__(self, amounts):
self._amounts = amounts.copy()
Bunk.number += 1
def transfar(self, sender, receiver, yen):
self._amounts[sender] -= yen
self._amounts[reciver] += yen
インスタンスメソッドと同じインデントに変数number
(支店数)を置きました。
インスタンスメソッドと同じインデント置いた変数はクラス変数と呼ばれます。
クラス変数は、全てのインスタンスで同じ変数を共有する特性があります。
bank1 = Bank({"太郎": 1000, "二郎": 2000})
print(bank1.number)
# 0
bank2 = Bank({"三郎": 100})
print(bank2.number)
# 1
bank3 = Bank({"四郎": 1400, "五郎": 198})
print(bank3.number)
# 2
__init__
関数が実行されるたびに、つまりBankクラスのインスタンスが作られるたびに、Bankクラスのクラス変数number
が増えるように変更しました。
def __init__(self, amounts):
self._amounts = amounts.copy()
Bunk.number += 1 # <- この部分
つまり、number
は銀行の支店数と等しくなるわけです。
全てのBank
クラスのインスタンスが指すnumber
はBank
クラスのクラス変数number
です。
bank1 = Bank({"太郎": 1000, "二郎": 2000})
bank2 = Bank({"三郎": 100})
bank3 = Bank({"四郎": 1400, "五郎": 198})
Bank.number = 100 # ここでBankのnumber内容を書き換える
print(bank1.number) # すると他のインスタンスでも値が変わる
# 100
print(bank2.number) # なぜなら`number`はクラス変数で
# 100
print(bank3.number) # 全てのBankクラスで共有する変数だから
# 100
上記の例のようにクラス変数は、全てのインスタンスで共通のものなので、クラス変数がどこかで書き換わると他の全てのインスタンスでも値が変わってしまいます。
ちなみにクラス変数は、クラスをインスタンス化しなくとも参照することができます。
インスタンス変数はそういう訳にはいかない。
print(Bank.number)
# 0
print(Bank._amounts)
# AttributeError: type object 'Bank' has no attribute '_amounts'
# _amountsなんてものはないと怒られる
クラスメソッド
クラス変数が、クラスに紐づく変数であったり、インスタンスメソッドがインスタンスに紐づく関数であったのと同様、クラスメソッドはクラスに紐づく関数です。
class Bank:
number = 0
def __init__(self, amounts):
self._amounts = amounts.copy()
Bunk.number += 1
def transfar(self, sender, receiver, yen):
self._amounts[sender] -= yen
self._amounts[reciver] += yen
@classmethod
def reset_number(cls):
cls.number = 0
これだけは少し特殊で、定義の際に@classmethod
と言うおまじないを書く必要があります。
第一引数に渡されるcls
は、インスタンスではなくクラスになります。
今回の場合は、cls
はBank
のことを指します。
Bank.reset_number()
print(Bank.number)
# 0
クラスメソッドは、インスタンス化しなくても利用することができます。
もちろんインスタンスからも呼び出すことができます。
bank = Bank()
bank.reset_number()
print(bank.number)
# 0
クラスメソッドは、クラス変数へのアクセスはできますが、インスタンス変数へのアクセスはできないので注意してください。
class Bank:
number = 0
def __init__(self, amounts):
self._amounts = amounts.copy()
Bunk.number += 1
def transfar(self, sender, receiver, yen):
self._amounts[sender] -= yen
self._amounts[reciver] += yen
@classmethod
def reset_number(cls):
cls.number = 0
print(cls._amounts) # _amountsはインスタンス変数なのでクラスメソッドからはアクセスできない
まとめ
インスタンス変数
はインスタンス毎に独立
しているインスタンスメソッド
はインスタンスに紐づいた関数
でインスタンス変数にアクセス出来る
クラス変数
はクラスの中で共通
の変数クラスメソッド
はクラス変数にアクセスが出来て
、クラスをインスタンス化しなくとも利用できる
。
最後に
なんか久々に長々と書きましたが、オブジェクト指向プログラミングは奥が深いので、より複雑な話はまた機会があれば書こうと思います。
この記事が皆様の理解の助けになれば幸いです。