【Python】クラスという設計図を使ってコードをわかりやすくしよう
いきなりコードで失礼します…!!
a=[{
'name':'戸田',
'comment':'こんにちは'
}]
a.append({
'name':'Karry',
'comment':'Hi'
})
上のコードのように、辞書型でまとめるのも良いですが、
同じキーを何個
も作らなくてはいけません。おまけに作業中のモジュールどの関数からも使えてしまうため、ちいさなコードだったらまだしも、コードが巨大となってくると、
変数の管理
も大変になるため、バグの元となります。
そうしないためにも、
関数
変数
をグループ化して、整理整頓できると便利ですよね🤔Pythonにはこうした便利なしくみがあります。
それが
「クラス」
です。
それでは始めていきましょう。
クラスの基本を知る
そもそもクラスって何?
クラスは簡単に言うと、
「設計図」
です。なので、
class Animal:
<処理>
と書いただけでは、クラスは
「透明人間」=設計図
なので直接は呼び出すことができません。
設計図=透明人間
なのですから、
「実体化」する必要
があるのです。
どのようにするかというと、
[変数]=[クラス名]([引数],...)のように、
dog=Animal()
と書くことで、初めて
「クラスの中身が実体化」
して、ようやく使えるようになります。
なお、クラスの中身が実体化したものを
「インスタンス」
と呼びます。
なお、インスタンスは
「何個も作ること」
ができます。
なので、
a=[{
'name':'戸田',
'comment':'こんにちは'
}]
上記のような辞書型があっても、
a.append({
'name':'Karry',
'comment':'Hi'
})
のように二度も同じ辞書型の要素を書かなくてもいいので、クラスは便利ですよね。
話を戻しますが、
一度、インスタンスが作られると、
「__init__(self,...)」
というコンストラクタが呼ばれます。あえて、太字でselfと書いてある理由は
「selfが必ず入っていないと、エラーが出てしまう」
からです。
実際にコードを書いて例をあげてみましょう。
class Human:
def __init__(self):
self.name='John'
self.age=5
a=Human()
print('名前:{0},年齢:{1}'.format(a.name,a.age))
のように、
__init__にselfの仮引数を加える
ことで、エラーが出ずに動きますが、selfを抜くと、
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-1-6066cd4ccb5f> in <module>()
3 self.name='John'
4 self.age=5
----> 5 a=Human()
6 print('名前:{0},年齢:{1}'.format(a.name,a.age))
TypeError: __init__() takes 0 positional arguments but 1 was given
「selfがないと、エラーが出てしまう」
ので、注意しましょう。
さて、次のコードを見てみましょう。
class Human:
def __init__(self):
self.name='John'
self.age=5
a=Human()
print('名前:{0},年齢:{1}'.format(a.name,a.age))
のコードでは、
a=Human()
と書いて、一度オブジェクトを作れば、
名前:John,年齢:5
のようにインスタンス変数nameとageの内容に
「直接アクセス」
できてしまいます。
ちなみに、nameとageの変数を
「インスタンス変数」
と呼びます。
インスタンス変数とは、
クラスの「インスタンス(実体)」が作られたとき
にできる
「クラス内の変数」
のことです。
ですが、インスタンス変数を直接アクセスするのは
「推奨」されません
ので、
setとgetを作って
「クラスのインスタンス変数を直接操作ができない」
ようにしておきましょう。
クラスにはメソッド、変数も追加可能。
またクラスには、__init__(self)のようなコンストラクタの他にも、
メソッド(関数)
も入れることができます。
注意すべきところは、__init__(self)と同じで、
def [メソッド名](self):
︙
のように書き、
「selfを追加する必要」がある
ことを忘れないでください。
それ以外は
「関数の作り方」
と同じです。
呼び出すときは、
[インスタンス変数].メソッド()
で呼び出すことができます。
もっとクラスを深堀り🤔
インスタンスメソッドのselfって?
class Dog:
def write(self):
print(self.name)
cls=Dog()
cls.name='key'
cls.write()
とすれば、クラスのオブジェクトclsで、
「name」の中身
が見ることができることはさきほど、お伝えしました。
ですが、
selfって何を指す
のでしょうか?
実は、selfは
クラスの「オブジェクト自身」
を指しているのです。
なので、Pythonでクラスのメソッドを使うときは
「self」を必ず入れるようにしてください。
インスタンスに依存しない関数、変数もある
クラスのインスタンスが作られても、クラスを生成するときに存在している
・メソッド
・インスタンス変数
は
「インスタンスを生成してこそはじめて使える」
ということを説明しました。
ですが、クラスのオブジェクト(インスタンスの実体)を作らなくても、
・メソッド
・変数
が使える仕組みも用意されています。
それは、
メソッドなら→スタティックメソッド、クラスメソッド。
変数なら→クラス変数。
となります。
それでは、まず、スタティックメソッドを見ていきましょう。
スタティックメソッドの書き方は、
関数の上に、
@staticmethod
と書きます。
スタティックメソッドでは、
class Human:
def __init__(self):
self.name='John'
self.age=5
@staticmethod
def showver():
print('versionは1.1です。')
a=Human()
Human.showver()
上のコードのように、
[クラス名].メソッド
で直接アクセスしても、
versionは1.0です。
と無事にアクセスできていることがわかります。
次にクラスメソッドを見ていきましょう。
クラスメソッドをつかうと、
クラスメソッド(②)→__init__(self)(①)
の順序で、クラスのオブジェクトを生成することができます。
これにより、
コンストラクタを生成する前の処理
も書くことができます。
class Human:
version=1.0
def __init__(self):#・・・①
self.name='John'
self.age=5
@classmethod
def updateVersion(cls):#・・・②
cls.version+=0.1
return cls()
a=Human.updateVersion()
print('version={0}'.format(Human.version))
としたとき、
version=1.1
になっていることがわかります。
このように、
「コンストラクタに渡す処理」
をクラスメソッドで書くことができるのです。
それでは次に、クラス変数を見ていきましょう。クラス変数もスタティックメソッドと同じように、
「クラスを定義したときから存在する変数」
のことです。なので、
[クラス名].[メソッド]
でアクセスできます。それではやってみましょう。
class Human:
version=1.0
def __init__(self):#・・・①
self.name='John'
self.age=5
@classmethod
def updateVersion(cls):#・・・②
cls.version+=0.1
return cls()
print('version={0}'.format(Human.version))
でクラス変数Humanにアクセスしたとき、
version=1.0
とアクセスできていることがわかります。
このように、クラス変数は、
「スタティックメソッドの変数版」
だと思って間違いないでしょう。
実践編:たとえば、クラスってどんなときに使えばメリットがあるの?
クラスを使うのは、
「大規模な開発現場」で使う
イメージがある。
だから、
「個人開発」では使うことはないんじゃないの〜??
というイメージがあるかもしれませんが、ざっくり言うと、
関数の間で変数を共有し合うとき
などに使うとスッキリしていて便利です!!
えっ!?グローバル変数あるじゃ〜〜ん🥺
と言われるかもしれませんが、グローバル変数だと
「すべての関数」
での共有されるので、うっかりやってしまうと、バグの特定に時間がかかるかもしれません💦
クラスがあるときとないときの感じを図で表したとはいえ、わかりにくいと思うので、
「ストップウォッチ」を模した自作クラス
を例にあげて説明したいと思います。
クラスを作ったきっかけ:時間計測のコードを組むのがめんどくさい
そもそも、時間計測するのにコーディングがひたすらめんどくさい記憶があったように感じます。クラスを使わずに書くと、おおまかに
「2ステップ」
に分けられます。
開始時刻の部分
開始時刻を入れる変数を用意。
timeモジュールのtime関数で現在時刻を取得。(すなわち、time.time())
終了時刻
終了時刻を入れる変数を用意
終了時刻から開始時刻を引く
というそれぞれ2ステップを時間計測したい部分にコードを組み込まなくてはいけません🤔💦
コードに表すとこんな形になります。
import time
beginTime=time.perf_counter()
#<コードが入ります。>
endTime=time.perf_counter()
TotalTime=endTime-beginTime
print('合計時間:',TotalTime)
上のコードを挟むのに、こういったコードを挟まなければいけないので、帰って読みづらいのではないかと思うようになってきました🤔そこで、
関数
クラス
かの選択肢が生まれてくるというわけです。
では、関数はどうか?
じゃぁ、関数同士で変数を共有しない場合は、関数の方がかえってスッキリかもわかりません。ですが、
とある特定の変数をシェアして扱うとなると、
あらかじめ、変数を用意して、同じパラメータを扱う関数に対し同じ引数を用意する
グローバル変数を用いる
といった選択肢になっていきますが、グローバル変数も用いるとなると、グローバル変数は
あらゆるところから使える変数
なので関数が多くなってきた時に、
あれ?この関数ってどの変数を共用していたんだったけ?
ともなりかねないですし、
'''
一つのモジュール(.py)において、1つの役割を持たせる関数で成り立っているならまだしも、たくさんいろんな関数が混ざると、
工夫を凝らさないと、見ずらいコードになるかもです。
変数が多くなると、混乱のもとになり、デバッグが大変になるかもです🥺
'''
g_beginTime,g_endTime=[None for i in range(2)]
g_totalTime=0
g_isStarted=False
def start():
global g_beginTime,g_isStarted
...
def stop():
global g_beginTime,g_endTime,g_totalTime,g_isStarted
...
def reset():
...
同じ引数を共用してとなると、
あれ?なぜこの引数をいちいち入れなければいけないんだろう?別に必要なくね?
となるわけです。なので、非効率なコーディングになります🤔
beginTime,endTime=[None for i in range(2)]
totalTime=0
isStarted=False
#パラメータを入れるといっても、関数において、参照渡しになっているかどうか確認する必要がある🤔
#値渡しはコピーになるので、意味がなくなる。
def start(beginTime,isStarted):
...
def stop(g_beginTime,g_endTime,g_totalTime,g_isStarted):
...
def reset():
...(以下省略)
なんとかして、スッキリしたい…!!
そこで、クラスの出番がやってくるわけです。もう一度、図を引っ張り出してみますね〜😉
関数がわからない人は以下のNoteを参照してくださいね〜😉↓
クラスにすると、コードが見やすくかつ使いやすくしてくれる
上がソースコードとなります。ソースコードをコピーしてぜひとも使ってみてください。
まず、①ですが、
変数,… = [変数の数の分の配列]
のコードは、
変数を複数代入しなさい
という意味があります。また、[変数の数の分の配列]の部分を内包表記で書くことによって、同時に1行で済ませるコードとなっています。内包表記の書き方はいろんなバリエーションがありますが、簡単に書くと、
[「配列になる値」for 「in内の要素」 in [配列] ]
となり、
[None for i in range(2)]
で、
Noneと呼ばれる値の要素を2個持つリスト
を作れと言う意味になります。(ここでは、iは捨て駒になりました💦)
まだ、配列と変数についてわからない方は以下のNoteを読んでみてくださいね〜!!↓
import time
class StopWatch:
def __init__(self):
self._beginTime,self._endTime=[None for i in range(2)]#・・・①
self._totalTime=0
self._isStarted=False
def start(self):
if not self._isStarted:
self._beginTime=time.perf_counter()#・・・②
self._isStarted=True
def stop(self):#合計時間を返す。
if self._isStarted:
self._endTime=time.perf_counter()
self._isStarted=False
self._totalTime=self._endTime-self._beginTime
return self._totalTime
def reset(self):
self._beginTime,self._endTime=[None for i in range(2)]#・・・①
self._totalTime=0
self._isStarted=False
次に②ですが、timeモジュールのperf_counter関数ですが、sleep時間を含んだクロック時間を返す関数となります。処理の
始まり
終わり
のクロック時間を記録しておくことによって、
「経過時間(秒)」
を計算することができます。
こうして、一つ一つのパーツがそれぞれ
スタート(開始▶️)
ストップ(終了⏸)
リセット(停止⏹)
の役割を担っていて、それぞれお互いにクラス内の変数を活用することによって、
ストップウォッチの機能
として担えるようになっています。今までのコードよりも
「グン💪」 と
わかりやすくなりましたね☺️(StopWatchクラスを継承して、Timerクラスを作るとおもしろいかもしれませんね🤔 From:心の声)
クラスは 「カプセル化」するために使うよく言いますが、一つのちょっとしたアプリを作ろうと思ったときにクラスを作っておくと便利だと思えば、クラスをどんどん活用してみようと思うのではないでしょうか?
クラスを理解しておくことで、
モジュールの整理整頓を行う
欠かせないものになることでしょう🤔
いかがでしたでしょうか?
クラスにしておくことで、
コードの「整理整頓」
に一役買うことができます。
実はクラスは奥が深くって、
「継承とオーバーライド」
もできます。
「クラスの継承」のnoteはこちら↓
ここまでお読みいただきありがとうございました。
この記事が気に入ったらサポートをしてみませんか?