上でも示したようにクラス変数は「class クラス名:」の直下のブロックに記述する。名前から分かるように、これはクラスレベルの変数であり、複数のインスタンスで共有される。クラスメソッドも同様にクラスレベルのメソッドとなる。クラス変数とクラスメソッドを持つクラスの例を以下に示す。これはインスタンスが生成されるごとに、クラス変数_countをカウントアップしていき、クラスメソッドcountを呼び出すとそのカウントを表示するクラスだ。
class Bar:
_count = 0
def __init__(self):
Bar._count += 1
@classmethod
def count(cls):
print(cls._count)
Bar.count()
b = Bar()
b.count()
Bar.count()
b = Bar()
Bar.count()
クラス変数「_count」の「_」はクラスメソッドcountの名前との重複を避けるという意味もあるが、Pythonでは「_」を1つだけ前置した識別子は「内部で使用する」ことを意味するというスタイルガイドに合わせたものだ。PythonにはC#などの言語ではよく使われている「プライベート」という概念が存在せず、全てのメンバは基本的に外部に対して公開されているが、コードを記述する側が「これは内部で使用するもの」と他者に伝えるためにこのような使い方が推奨されている。
__init__メソッドでは「クラス名.クラス変数名」という形式でクラス変数_countにアクセスし、カウントアップをしている。クラスメソッドcountはクラスオブジェクト(クラスを表すオブジェクト)を第1パラメーターに受け取るので、「cls._count」のような形でクラス変数にアクセスをしている。
クラス定義の下では、インスタンスを生成して、クラスメソッドcountを呼び出している。見れば分かるように、クラスメソッドは「クラス名.クラスメソッド名(...)」と「インスタンス名.クラスメソッド名(...)」のどちらの方法でも呼び出せる(ただし、インスタンス経由でクラスメソッドを呼び出しても、呼び出しで重要なのはそのインスタンスの型だけであり、インスタンスが保持する情報はクラスメソッドでは利用されない)。
以下に実行結果を示す。
>>> class Bar:
... _count = 0
...
... def __init__(self):
... Bar._count += 1
...
... @classmethod
... def count(cls):
... print(cls._count)
...
>>> Bar.count()
0
>>> b = Bar()
>>> b.count()
1
>>> Bar.count()
1
>>> b = Bar()
>>> Bar.count()
2
今見たように、クラスメソッドはクラスオブジェクトを受け取り、その情報を利用できたが、スタティックメソッドは第1パラメーターにクラスオブジェクトを受け取らないのが大きな違いだ。
class Bar:
_count = 0
def __init__(self):
Bar._count += 1
@classmethod
def count(cls):
print(cls._count)
@staticmethod
def static_method():
print("static method")
Bar.static_method()
b = Bar()
b.static_method()
第1パラメーターにクラスオブジェクトを受け取らないことから、スタティックメソッドはクラスをモジュール/名前空間とした何らかのユーティリティー関数を実装したりするのに使えるだろう(実行例は割愛)。
setterとgetterを伴うプロパティも定義可能だ。これには組み込み関数propertyを使用する。以下に例を示す。
class Baz:
def __init__(self, name):
self._name = name
def getname(self): # getter
return self._name
def setname(self, value): # setter
self._name = value
name = property(getname, setname) # プロパティの設定
b = Baz("hogehoge")
print(b.name)
b.name = "insider.net"
print(b.name)
このようにgetter/setterとなるメソッドを定義して、それを組み込み関数propertyに渡してやればよい。setterを指定しなければ、それは読み取り専用のプロパティとなる。実際には組み込み関数propertyはパラメーターを4つ取る。
property(fget=None, fset=None, fdel=None, doc=None)
パラメーターfdelにはプロパティを削除するメソッドを、docにはそのプロパティについてのドキュメントを指定する。
あるいは@propertyデコレータとして各メソッドを修飾してもよい。先ほどのコードを@propertyデコレータを用いて書き直したものを以下に示す。
class Baz:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
この場合はgetterとなるメソッドに「@property」で修飾をしてメソッド名をプロパティ名にする(この場合は「getname」ではなく「name」)。また、setterとなるメソッドには「@プロパティ名.setter」と修飾する。メソッド名はプロパティ名と同じものにする(この場合は「setname」ではなく「name」)。
先ほども述べたが、Pythonには「プライベート」という概念がなく、全てのメンバが基本的には外部に公開される。さらに言えば、以下のようにクラス定義の段階では存在しないメンバ(インスタンス変数/データ属性)をインスタンスに付加することも可能だ。このコードではnameプロパティが読み取り専用になっていることに注意(@name.setter修飾されたメソッドがない)。
class Baz:
def __init__(self, name, addr):
self._name = name
self.addr = addr
@property
def name(self): # name.setter修飾されたメソッドがないので、
return self._name # nameは読み取り専用プロパティ
b = Baz("insider.net", "tokyo")
b.addr = "yokohama"
b.prop = "build insider" # 変数bが参照するオブジェクトにインスタンス変数を追加
print(b.addr)
b.method = lambda x: print("b.method:", x) # メソッドも追加できる
b.method("hoge")
b.name = "windows server insider" # エラー:nameプロパティは読み取り専用
この例では、Bazクラスのインスタンスに対して、インスタンス変数を追加したり(b.prop)、インスタンスメソッドを追加したり(b.method)、クラス定義の時点で存在しているインスタンス変数の値を自由に変更したりしている。その一方で、b.nameは読み取り専用のプロパティとなっているので、これを変更はできない。プロパティを使うことで、クラスのメンバを読み取り/更新するための適切なインタフェースを提供できるようになる。
Pythonのコーディングスタイルガイド(日本語訳はこちら)では、クラス名は「CapWords」形式とある。これは「単語の先頭を大文字にして、複数の単語はそのまま連結する」ことを意味する。
関数名は「小文字だけを使い、可読性を高める必要があれば、複数の単語はアンダースコアでつなぐ」ことが基本的には推奨されている。インスタンスメソッドの最初のパラメーターには既に見たように「self」を、同じくクラスメソッドの最初のパラメーターには「cls」を使うことも推奨されている。
この他にも上で述べた「_」の使い方をはじめとするPythonで推奨されている命名規約、コーディングスタイルが掲載されているので、ざっと目を通しておいた方がよいだろう。
次にクラスの継承について簡単に見ておこう。
Copyright© Digital Advantage Corp. All Rights Reserved.