オブジェクト(class)の作成-その3

記事の内容

 この記事では、クラスメソッドの作成方法について説明するとともに、呼び出して利用する方法を説明します。

***わからない用語があるときは索引ページへ***

この記事のサンプルプログラムは、Python3系をベースとしています。Python2系は、クラス宣言の記述ルールが異なるのでご注意ください。

1.クラスメソッドとは

 クラスメソッドは、インスタンスを生成していなくても、クラス名で呼び出して利用できるメソッドです。インスタンスを生成することなく呼び出すことができますので、コンストラクタが必要な場合、インスタンスメソッドである__init__コンストラクタでは初期化できません。
  以前の記事で、インスタンス変数とインスタンスメソッドで作成したKagensanクラスを、クラスメソッドとクラス変数で使えるように書き換えてみます。

2.クラスメソッドを使ったクラスを作成する

 前の記事で作成したKagensanクラスを、クラスメソッドとクラス変数で使えるように書き換えてみましょう。

##class_ex2.py


##class宣言
class Kagensan:
   
   ##コンストラクタの作成
   def __new__(cls, x, y):
       cls.x = x
       cls.y = y
       
       return cls
   
   ##クラスメソッドの作成
   @classmethod
   def tashizan(cls):
       wa = cls.x + cls.y
       
       return wa
   
   @classmethod
   def hikizan(cls):
       sa = cls.x - cls.y
       
       return sa


##プログラム実行開始位置
a = 5
b = 3
print("a =", a, "/ b =", b)
print("\n")

##クラスメソッドの呼び出し
print("クラスメソッドの呼び出し")
wa = Kagensan(a, b).tashizan()
print("a + b =", wa)
sa = Kagensan(a, b).hikizan()
print("a - b =", sa)
print("\n")

##クラス変数を呼び出してみる
print("クラス変数を呼び出してみる")
cls_var1 = Kagensan.x
cls_var2 = Kagensan.y
print("Kagensan.x =", cls_var1)
print("Kagensan.y =", cls_var2)
print("\n")

##clsはインスタンスではないので、メモリを共有する
##クラス変数を書き換えると、別々の変数にしても書き換わってしまう
print("clsはインスタンスではないので、メモリを共有する")
print("クラス変数を書き換えると、別々の変数にしても書き換わってしまう")

cls1 = Kagensan(5, 3)
print("元のcls1.x =", cls1.x)

cls2 = Kagensan(500, 300)
print("cls2を書き換えた後のcls1.x =", cls1.x)
print("\n")

実行結果

a = 5 / b = 3


クラスメソッドの呼び出し
a + b = 8
a - b = 2


クラス変数を呼び出してみる
Kagensan.x = 5
Kagensan.y = 3


clsはインスタンスではないので、メモリを共有する
クラス変数を書き換えると、別々の変数にしても書き換わってしまう
元のcls1.x = 5
cls2を書き換えた後のcls1.x = 500

3.クラス作成方法の解説

3.1.クラス宣言

 class宣言文で、Kagensanという型のオブジェクトを定義することを意味しています。関数の定義同様、定義の中身はclass文のプログラムブロックの中で書いていきます。

class Kagensan:

3.2.コンストラクタの作成

 つづいて、コンストラクタを作成します。
 __new__は、クラスメソッドのコンストラクタです。__init__がインスタンスを生成するときにはじめて実行されるのに対し、__new__はクラスが呼び出されたときに自動的に実行されます
 "class_ex2.py"の__new__コンストラクタの中身をみてみましょう。

   ##コンストラクタの作成
   def __new__(cls, x, y):
       cls.x = x
       cls.y = y
       
       return cls

 __new__コンストラクタの第1引数は、自身のクラスです。変数名は何でも良いのですが、慣例としてclsという変数名を使います。
 ここで、clsはクラス変数です。したがって、cls.xとcls.yもクラス変数となります以前の記事でも説明したとおり、クラス変数とインスタンス変数は呼び出されたときのふるまいが変わってきますので注意が必要です。
 class文のプログラムブロックの中で、自身のクラス変数を読み書きする際は

cls.<クラス変数名>

の記述規則で呼び出します。

まとめ:
__init__コンストラクタの第1引数で渡されるselfは自身のインスタンスを表すインスタンス変数ですが、__new__コンストラクタの第1引数で渡されるclsはクラス自身を表すクラス変数です。

 __new__コンストラクタの第2引数以降の引数は、クラス呼び出しの際にコンストラクタに渡される引数です。第一引数のclsを除き、引数の渡し方は関数作成のルールと同じです。class_ex2.pyのクラスの呼び出し部分をみてみましょう。

##クラスメソッドを実行
wa = Kagensan(a, b).tashizan()

 呼び出しの際の第1引数aと第2引数bが、__new__コンストラクタのclsを除く、第2引数以降に順番に渡されます。__new__コンストラクタの第1引数clsは、クラス呼び出しの際には指定しません。
 また、__new__コンストラクタは、必ずreturn文でメソッドやクラスを操作できるオブジェクト(クラス自身clsか、もしくは自身のインスタンスself)を返す必要があります。__init__コンストラクタがselfを自動的に返し、return文を書くと例外が発生するのとは、書き方が変わってくるので注意が必要です。
 "class_ex2.py"では、自分自身のクラスオブジェクトであるclsを返しています。

補足1
 __new__コンストラクタでは、クラス自身ではなく、自身のインスタンスselfを返すこともできるのですが、クラスの継承や、Pythonのclass定義の構造に関する知識も必要になってきますので、ここでは説明を省略します。

補足2
 __new__コンストラクタが呼び出されるのは、
 <クラス名>(引数, ・・・)
の形式で呼び出されたときです。<クラス名>のみで、後ろの引数の()を省略したときは、関数同様、呼び出しの意味がが変わってしまいます。<クラス名>のみでクラスを呼び出した場合は、クラス自身のプログラムが格納されたメモリ領域の開始位置を参照渡しで返します。したがって、
 result = <クラス名>.<クラスメソッド名>(引数, ・・・)
の形式でクラスメソッドを呼び出したときは、クラス名の後ろの()が省略されてますので、__new__コンストラクタは呼び出されず、直接クラスメソッドを呼び出しています。

3.3.クラス変数の生成と呼び出し

 もう一度__new__コンストラクタの説明に戻ります。
 __new__コンストラクタの第1引数clsは、class自身のオブジェクトを表してます。第2引数と第3引数で、cls.x、cls.yの2つのクラス変数を作成しています。

   def __new__(cls, x, y):
       cls.x = x
       cls.y = y
       
       return cls

 クラス変数は、クラスを呼び出して読み書きすることができます。class_ex2.pyの、クラス変数の読み込み部分をみてみましょう。

cls_var1 = Kagensan.x
cls_var2 = Kagensan.y

クラス変数は、

<クラス名>.<クラス変数名>

で呼び出します。インスタンス変数がインスタンスを生成してからでないと読み書きできないのとは違いますね。

3.4.クラス変数はインスタンス変数のように互いに独立していない

 クラス変数は、インスタンス変数のように独立していません。class_ex2.pyでクラス変数を書き換えたときの動きを見てみましょう。
 最初に、Kagensan(5, 3)でKagensanクラスを呼び出したときに、__new__コンストラクタでcls.xには5が、cls.yには3が代入されます。
 次に、Kagensan(500, 300)でKagensanクラスを呼び出したときに、cls.xには500が、cls.yには300が代入されます。

cls1 = Kagensan(5, 3)
print("元のcls1.x =", cls1.x)

cls2 = Kagensan(500, 300)
print("cls2を書き換えた後のcls1.x =", cls1.x)

 ここで、最初にKagensanクラスを最初に呼び出したときのcls1のクラス変数cls1.xとcls1.yを、次にKagensanクラスを呼び出した後にみてみると、クラス変数の値が書き換わってしまっています。

元のcls1.x = 5
cls2を書き換えた後のcls1.x = 500

 このように、クラス変数は、インスタンス変数と違って互いに独立した別のものではなく、同じデータ(メモリ領域)を共有しています。

3.5.クラスメソッドの作成

 クラスメソッドは、class文のプログラムブロックで作成します。インスタンスメソッドとの違いは、インスタンスを生成しなくてもクラスから呼び出しができる点です。クラスメソッドを作成する際は、関数定義に"@classmethod"のデコレーションをして定義します
 その他の作成記述規則は、インスタンスメソッドと同じです。class_ex2.pyでは、tashizan、hikizanの2つのクラスメソッドを作成しています。作成部分をもう一度見てみましょう。

   @classmethod
   def tashizan(cls):
       wa = cls.x + cls.y
       
       return wa
   
   @classmethod
   def hikizan(cls):
       sa = cls.x - cls.y
       
       return sa

 第1引数のclsは、Pythonインタープリタから渡される自身のクラスです

クラスメソッドの呼び出しを見てみましょう。

wa = Kagensan(a, b).tashizan()

sa = Kagensan(a, b).hikizan()

 インスタンスを生成することなく、直接クラスからクラスメソッドを呼び出しています。

3.6.スタティックメソッド

 スタティックメソッドは、クラスメソッドと同じように、インスタンスを生成することなく、クラスから呼び出すことができます。
 クラスメソッドとの違いは、第一引数にclsやselfなどの、自身のクラスやインスタンスを受け取らないことです。スタティックメソッドはclsやselfが不要であるということは、クラスオブジェクトのメソッドではありますが、クラスとは独立した関数であるともいえます。便利なように、独立な関数をメソッドとしてくっつけたような感じでしょうか。
 スタティックメソッドの関数定義の記述ルールは、インスタンスメソッドやクラスメソッドと同じですが、関数宣言のdef宣言の際に、"@staticmethod"でデコレーションして定義します。

@staticmethod
def <スタティックメソッド名>(引数, ・・・)

繰り返しとなりますが、スタティックメソッドは第1引数にclsやselfを受け取りません。

3.7.サンプルプログラムをクラスメソッドで使いやすいように書き換えてみる

 上記で説明してきたサンプルプログラムclass_ex2.pyは、tashizanやhikizanのメソッドを呼び出す際に、

wa = Kagensan(a, b).tashizan()

sa = Kagensan(a, b).hikizan()

のように呼び出しました。tasizanメソッド、hikizanメソッドが、__new__コンストラクタで定義されたクラス変数を使っているため、コンストラクタを実行せずに呼び出すと例外が発生してしまいます。
 しかし、この呼び出し方自体、少しわかりづらくないでしょうか。クラスから直接呼び出すのであれば、

wa = Kagensan.tashizan(a, b)

のように、メソッドに引数を渡す形式の、

<クラス名>.メソッド名(引数, ・・・)

で呼び出した方がわかりやすいと個人的に思います。
 そこで、class_ex2_2.pyでは、class_ex2.pyのKagensanクラスを、クラスメソッドに引数を渡す形式のKagensan2クラスに書き換えました。
 上記呼び出し方(クラス名の後ろの()がない)であれば、メソッドの実行の際、コンストラクタを呼び出さないので、コンストラクタは不要になります。

##class_ex2_2.py

##class宣言
class Kagensan2:
   ##クラス変数を定義
   keisan_kaisu = 0
   
   ##クラスメソッドの作成
   @classmethod
   def tashizan(cls, x, y):
       wa = x + y
       cls.keisan_kaisu += 1
       
       return wa
   
   @classmethod
   def hikizan(cls, x, y):
       sa = x - y
       cls.keisan_kaisu += 1
       
       return sa
a = 5
b = 3
wa = Kagensan2.tashizan(a, b)
print("wa =", wa)
print("計算回数:", Kagensan2.keisan_kaisu)
print("\n")

a = 10
b = 5
sa = Kagensan2.hikizan(a, b)
print("sa =", sa)
print("計算回数:", Kagensan2.keisan_kaisu)

tasizan、hikizanメソッドだけだと、スタティックメソッドでも定義できてしまって、クラスにする意味が小さくなってしまうので、通算計算回数を記録するクラス変数keisan_kaisuを定義してみました。

出力結果

wa = 8
計算回数: 1

sa = 5
計算回数: 2

前の記事 次の記事







この記事が気に入ったらサポートをしてみませんか?