Pythonのクラスに関する備忘録

f:id:monozukuri-bu:20190816165831j:plain

ふっちーです。
今回はクラスについての備忘録を残します。
今までクラスに触れてきた中で、理解するのに時間が掛かったものや印象に残ったものを、自分の言葉にしてみたいと思います。
クラスの基本的な部分や用語の説明は省略しています。

4つの疑問

・そもそもクラスはどういうときに使えばいいの?
・クラスの関数にあるselfって?
・継承とは?superってどうやって使うの?
・クラスを含んだモジュールは関数だけのモジュールと違う部分はあるの?

今回は上記4つについて書いてみたいと思います。

そもそもクラスはどういうときに使えばいいの?

クラスはデータ(属性)や関数(メソッド)を1つにまとめて、データの保持と処理を一括で管理できます。
そして、同じクラスでも異なるインスタンスを設定すれば、異なるデータをインスタンスごとに別々で処理できます。
確かに便利そうな機能だとは思うのですが、実際にクラスはどんな時に使われるのでしょうか。

結論から言うと、
テンプレートを作成し、似たようなものを手軽に生産できる
という点があげられます。

例を挙げると、ゲーム制作が分かりやすいと思います。
キャラクターには(ゲームにもよりますが)名前や性別など必ず持っているデータ(属性)がいくつか存在します。
レーシングゲームだったら車体の名前や色だったり、アクセルやブレーキなどのデータ処理(メソッド)が該当します。

そして、ゲームではこれらのデータを持つオブジェクト(キャラクター、車など)をたくさん作成する場合があります。
普通に変数1つ1つでこれを作るとなるととんでもない労力がかかります。
この時、クラスの存在がこのような作業をできるだけ少ない労力でコードを書く手助けをしてくれるのです。

試しに名前、年齢、性別が設定されたキャラを作るために、実際にクラスを書いてみましょう。

class Chara:
  def __init__(self, name, age, sex):
    self.name = name
    self.age = age
    self.sex = sex
  
  def status(self):
    print("名前:"+self.name)
    print("年齢:"+str(self.age)+"歳")
    print("性別:"+self.sex)

このようにキャラの基本情報を設定することで、これを基にしたキャラの情報を多く作ることができます。

a = Chara("John", 14, "male")
b = Chara("Alice", 8, "female")
c = Chara("Robin", 40, "male")

これだけで、3人分のキャラを設定できました。
もしこれを変数だけで作ろうとすると、9個の変数が必要になることを考えると、クラスがいかにデータを楽に管理できるかが分かります。
statusメソッドでデータを確認してみましょう。

a.status()
出力結果:
名前:John
年齢:14歳
性別:male

このようにクラスで基となるデータやメソッドを用意することで、大量のデータを簡単に管理することができます。

また、キャラをPCキャラとNPCキャラにわけて、PC側には職業や体力を、NPC側にはセリフを設定することもできます。
この場合は継承を使うときれいに設定できます。
継承については後述します。

クラスの関数にあるselfって?

クラスで関数を使うときに疑問に思った方もいるかと思います。
中途半端に理解してしまい、混乱することが多かったので、まとめることにしました。

まず、selfの正体は何なのでしょうか。
selfはその名の通り自分自身を指します。
ここで言う自分自身とは、インスタンス化したクラスを示しています。
さっきの例で言うとa, b, cがそれに当てはまります。

a = Chara("John", 14, "male")
b = Chara("Alice", 8, "female")
c = Chara("Robin", 40, "male")

インスタンス化したクラスを格納するための変数名(a, b, c)は自分で勝手に決められますが、
それをクラス内部で使用する場合はselfで統一しています。
クラスは各インスタンス毎に異なる情報を持つため、selfでどのインスタンスなのか判断する必要があるのです。
つまりこの引数selfは、インスタンス化されたクラスを入れて識別する為の引数と言えます。

実際に書いてみましょう。

class Chara:
  def __init__(self, name, age, sex):
    self.name = name
    self.age = age
    self.sex = sex
    
  def get_old(self):
    self.age += 1
    print(self.age)  

先ほどのクラスに歳をとるget_oldメソッドを新しく追加しました。
このget_oldメソッドを使って以下のコードを実行してみましょう。

a.get_old()     #インスタンス関数を使う場合はこれが一般的
Chara.get_old(b)  #実はこれでも動く
出力結果:
15
9

上の例にあるように、変数名.get_old()は、クラス名.get_old(x)と同じと言えます。
2つ目の例が本来の書き方といった感じですが、毎回クラス名を書く必要がない1つ目の方が効率的ですね。

継承とは? superってどうやって使うの?

継承はクラスの機能拡張として使われます。
継承を使うことでクラスに新しい属性やメソッドを追加したり、別のものに変更することができます。
これにより新しい機能を実装する際に一からクラスを作り直す手間を省くことができます。

実際に継承を使ったクラスを見てみましょう。

import random
class Sleep(Chara):
  def __init__(self, name, age, sex, sleep):
    super().__init__(name, age, sex)
    self.sleep = sleep
  
  def status(self):
    if self.sleep == 0:
      print("ZZZZZ...")
    
    else:
      print("Good morning!")
  
  def n_status(self):
    super().status()
      
  def wake_up(self, name, sleep):
    if self.sleep == 0:
      print("Wake up! "+self.name+"!")
      r = random.randint(0, 1)
      if r == 0:
        print("Oh, come on!")
      
      else:
        self.sleep = 1
        print("Good morning, "+self.name+".")
        
    else:
      print("Oh, sorry.")

最初のクラスを継承したSleepクラスを作りました。
早速キャラを設定し、以下のコードを実行します。

a = Sleep("John", 14, "male", 0)
a.status()
a.get_old()
出力結果:
ZZZZZ...
15

ここで注目してほしいのは2点です。
・Sleepクラスのstatusメソッドが実行されている
・Sleepクラスにないget_oldメソッドが使用できている

基にしたクラスにあるメソッドは継承したクラスでも使用することができます。
継承しなければ、当然使用することはできません。

b.wake_up()
AttributeError: 'Chara' object has no attribute 'wake_up'

継承の大まかな内容を掴んだところで、superに触れていきましょう。
先ほどstatusメソッドがSleepクラスのものに上書きされていました。
この場合Charaクラスのstatusメソッドは使えないのでしょうか。

そこでsuperが役に立ちます。
n_statusメソッドがsuperを使っているので、実行してみましょう。

a.n_status()
出力結果:
名前:John
年齢:15歳
性別:male

Charaクラスのstatusメソッドを使用することができました。

また、Sleepクラスのinitにもsuperは使われています。
これは継承がデータも引き継がれることを利用して、superで基のクラスに既にあるデータを保持するためです。

最後にwake_upメソッドで、Johnを起こしましょう。

a.wake_up()  #起きるまで何回もやろう!
出力結果:
Wake up! John!
Good morning, John.

何回でJohnを起こせましたか?
私は2回でした。

クラスを含んだモジュールは関数だけのモジュールと違う部分はあるの?

モジュールでよく使うのは関数の集まりなので、あまり気にした人はいないかもしれません。
しかし、私は関数を囲むクラスの姿を見て、これをモジュールとして使用する場合どんな動作になるだろうと思いました。

最初のCharaクラスをモジュール(character.py)としてみます。

class Chara:
  def __init__(self, name, age, sex):
    self.name = name
    self.age = age
    self.sex = sex
  
  def status(self):
    print("名前:"+self.name)
    print("年齢:"+str(self.age)+"歳")
    print("性別:"+self.sex)

次に以下のコードを記入します。

import character

d = character.Chara("Asbel", 18, "male")
d.status()
出力結果:
名前:Asbel
年齢:18歳
性別:male

これでモジュールのクラスが使えるようになりました。
関数モジュールを使うときのようにモジュール名.クラスとしてインスタンス化すれば、後は通常通りクラスを使用できるようです。

今回はここまでです。
お疲れさまでした。

まとめ

今回はクラスで疑問に思ったところをまとめてみました。
・クラスは似たようなものを大量に用意する場合に便利だと分かりました
・selfはどのインスタンスなのか見分けるために、変数の名前を受け取る引数です
・継承は元のクラスを拡張して多くのデータやメソッドを追加してバリエーションを持たせることができます
・superで親クラスのデータを記述せずに使用することができます
・クラスを含んだモジュールでもインスタンス化の際にモジュール名を付けるだけで大きな差はないです

今回の勉強でクラスについての理解が深まったように思えます。
しかし、クラスを活かすには個人だと限界があるので、何か面白いアイデアはないか探してみたいと思っています。