クラスとオブジェクト、クラスの定義、インスタンス変数、__init__メソッド、インスタンスメソッドというクラスの基礎知識を紹介する。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
* 本稿は2019年7月30日に公開された記事を、Python 3.12.0で動作確認したものです(確認日:2023年11月10日)。
前回は、クラスの概要とクラス定義、インスタンス変数とインスタンスメソッドなど、クラスの基本的な知識を取り上げた。今回は前回に触れなかった、クラス変数/クラスメソッド/スタティックメソッドという属性について見ていく。
前回は、クラスやそのインスタンスが持つ属性(メンバ)として以下のものを見た。
これらは個々のインスタンスが持つデータと、それらのデータを使用して何らかの計算処理を行うメソッドだ。その他に、特殊なメソッドとして、インスタンスの初期化を行う__init__メソッドについても簡単に見た。
Pythonのクラスには、これら以外にも以下に示す属性がある。
クラス変数は「クラスに結び付けられた変数」のことだ。同様に、クラスメソッドは「クラスに結び付けられた関数(メソッド)」といえる。最後のスタティックメソッドは「クラスを名前空間として、その中に定義された関数(メソッド)」のようなものだ。
以下では、これらについて少し詳しく見ていこう。
前回のおさらいも兼ねて、まずは簡単にインスタンス変数の特徴を見た後に、クラス変数の特徴を説明する。ただし、その前にクラス変数とインスタンス変数の違いを以下にまとめておこう。
変数の種類 | 説明 | クラス外部からのアクセス方法 | クラス内でのアクセス方法 |
---|---|---|---|
インスタンス変数 | インスタンスが持つ データを保存 |
インスタンス.インスタンス変数 | self.インスタンス変数 |
クラス変数 | クラスが持つデータ や複数のインスタンス で共有するデータを保存 |
クラス.クラス変数 インスタンス.クラス変数 |
self.クラス変数(値の書き換えに注意) クラス.クラス変数 self.__class__.クラス変数 type(self).クラス変数 |
インスタンス変数とクラス変数 |
前回は以下のようなクラスを最初に作っていた。
class Point:
pass
このクラスのインスタンスを生成して、そのインスタンスに属性(インスタンス変数)を与えるには、次のようにした。
point1 = Point()
point1.x = 1.0
point1.y = 1.0
通常の変数と同様に、インスタンスごとに固有のデータ(インスタンス変数)は宣言の必要なしに、必要なタイミングで必要なものを付加できる。余談となるが、これはメソッドでも同様だ。
point1.hello = lambda x: print('Hello', str(x))
point1.hello('world')
point2 = Point()
point2.hello('world') # エラー:Pointクラスの定義ではhelloメソッドはない
この場合、Pointクラスのインスタンスであるpoint1には独自の属性として、インスタンス変数x/yとhelloメソッドが与えられた。が、point2にはそうしたものはない。
そうではなく、point1やpoint2に限らず、Pointクラスの全てのインスタンスが特定の属性(例えば、X座標の位置とY座標の位置を示す値)を持つようにしたいというときには、__init__メソッドに属性を初期化するコードを記述するのだった。言い換えると、これは「座標という概念をPythonコードを使って表したもの(クラス定義)に、その具体的なデータ(インスタンス)が持つべき属性を記した」ということでもある。
このようにインスタンス変数がインスタンス固有のデータを保存するものであることに対して、クラス変数はクラス(クラスオブジェクト)に固有のデータを保存するためのものだ。クラス変数は(インスタンスではなく)クラスが独自に管理したいデータや、複数のインスタンスで共有したいデータを保存するために使える。
インスタンスが複数あれば、その数だけインスタンス変数も存在するが、クラス変数は1つだけ存在する。そして、複数のインスタンスは、クラスとクラス変数を共有する。
クラス変数を定義するには、class文でのクラス定義内で(メソッドなどに囲まずに)そのまま変数を定義する。以下に例を示す。ここではクラス名をMyClassとしよう。
class MyClass:
count = 0 # クラス変数countの定義
上のようにして定義したクラス変数には、基本的には「クラス.クラス変数」としてアクセスできる。
print(MyClass.count)
この場合は、「MyClass.count」でMyClassクラスのクラス変数countにアクセスしている。ただ、次のようにアクセスすることも可能ではある。
instance = MyClass()
print(instance.count)
実行結果を以下に示す。
どちらも「0」と表示された。ただし、これで正しくクラス変数にアクセスできるのは、「インスタンスに同名のインスタンス変数が存在していない間」であることに注意すること。例えば、次のようにしてみよう。
instance.count = 100
print(instance.count)
print(MyClass.count)
このコードを実行すると、意外な結果になる。
2つのprint関数呼び出しで異なる値が表示された。これは「instance.count = 100」という代入文により、「count」という名前の「インスタンス変数」が作成されて、クラス変数が見えなくなったことによる。
「インスタンス.属性」という形でアクセスをした場合、指定された属性がそのインスタンスになければ、クラスで定義されている属性が探される。しかし、インスタンスにその属性があると、そちらが見えるのでクラスで定義されているものが見えなくなってしまう。インスタンスを介してクラス変数にアクセスする際には注意が必要だ。また、特に理由がない限りは、クラスが持つ属性と同じ名前の属性をインスタンスに持たせるようにしないことも、分かりやすいコードを書く上では重要になる。
メソッドからインスタンス変数やクラス変数にアクセスする場合も同様に「インスタンス.クラス変数」「クラス.クラス変数」のようにしてアクセスできる。前回はインスタンスメソッドからインスタンス変数にアクセスするのに、メソッドの第1パラメーター(通常は「self」という名前にする)を介して「self.インスタンス変数」としていた。クラス変数にもこの方法でアクセスできる。が、値を変更しようとすると上で見たのと同じ問題が生じる。
以下に間違ったコード例を示す。
class MyClass:
count = 0
def __init__(self):
self.count += 1
print(f'you made {MyClass.count} instance(s)')
ここでは__init__メソッドの内部で、クラス変数のcountを1増やすつもりで「self.count += 1」としている(こうすることで、MyClassクラスのインスタンスがこれまでに何個生成されたかを記録しようとしている)。その一方で、print関数呼び出しの中では、「MyClass.count」としてその数を表示している。
上のMyClassクラスを定義して、以下のコードを実行してみよう。
instance1 = MyClass()
instance2 = MyClass()
実行結果を以下に示す。
「間違ったコード例」と既に述べているように、インスタンスを2つ生成したにもかかわらず、どちらも「you made 0 instance(s)」という表示になっている。この理由は、上と同様に「self.count += 1」によって、「count」という名前のインスタンス変数が作成され、クラス変数を隠してしまっているからだ。
正しいコードを以下に示す。
class MyClass:
count = 0
def __init__(self):
MyClass.count += 1
print(f'you made {MyClass.count} instance(s)')
このようにすれば、次のように正しく、クラス変数がカウントアップされて、その値が表示されるようになる。
インスタンスメソッドからクラス変数にアクセスするには、今見たような「self」と「クラス名」を使う方法以外にも「self.__class__.クラス変数」「type(self).クラス変数」のように書く方法がある。クラス名をそのまま記述するよりも、今述べた2つの書き方をすると柔軟性のある処理が可能になる(クラスの継承時に、動作をカスタマイズするなど)。どの書き方をするかは、プログラマー次第だが、「self.クラス変数」はその値を読み出すときだけにしておくようにしよう。
Copyright© Digital Advantage Corp. All Rights Reserved.