クラス

定義

Python のクラスを書くには、 class 文を使います。

>>> class Foo(object):
...     def __init__(self, a):
...         self.a = a
...     def double(self):
...         return self.a * 2
...
>>> foo = Foo(3)
>>> foo.a
3
>>> foo.double()
6

php の __construct にあたる関数が __init__ です。 属性へのアクセスには -> ではなく . を使います。

php の $this-> に当たるのが、 self. です。 php と違って、 self は明示的に第一引数として受け取らないといけません。 これは、 class 文の中でメソッドじゃないただの関数を定義できるので、 self を受け取る関数か そうじゃない関数かを構文で区別しようとすると複雑になってしまうからです。

関数と同じく、クラスもファーストクラスオブジェクトです。 上の例では、 class 文は Foo という変数にクラスオブジェクトを代入しています。 クラスは関数と同じく () をつけて呼び出すことができ、クラスを呼び出すとインスタンスができます。 なので、 new に当たる構文はありません。

クラス属性とインスタンス属性

php でいうメンバー変数を、 Python では属性と呼びます。

クラスの属性 (phpで言うstaticメンバー変数) とインスタンスの属性 (phpで言うメンバー変数) の違いに気をつけてください。 Python で class 定義内で変数を定義すると、それは自動的にクラスの属性になります。 インスタンスの属性を定義するには __init__ などで代入してやる必要があります。

オブジェクトの属性を a = x.y の用に参照する場合、まず x 自体の属性から y を探して、 なかったら x のクラスの属性を探します。

>>> class Foo(object):
...     x = 1   # Foo の属性
...     def __init__(self, a):
...         self.a = a   # self の属性
...
>>> foo = Foo(3)
>>> foo.a   # foo の属性 a を参照する
3
>>> foo.x   # foo には x が無いので、 Foo.x を参照する
1
>>> foo.x = 5  # foo.x に代入したので、 foo.x ができる
>>> foo.x   # foo の属性 x を参照する
5
>>> Foo.x   # Foo の属性 x はそのまま
1
>>> Foo.z = 'zzz'   # 後から Foo に属性を追加
>>> foo.z   # これも foo から参照できる
'zzz'

この特徴を活かして、クラス属性をインスタンス属性の初期値の用にして使うことができます。 ただし、クラス属性はどのインスタンスからアクセスしても同一のオブジェクトになるので、 関数の引数のデフォルト値と同じような問題に注意してください。

>>> class Bar(object):
...     x = []
...     def __init__(self):
...         self.x.append(1)
...
>>> bar = Bar()
>>> bar = Bar()
>>> Bar.x
[1, 1]

Python では関数も変数に入るただのオブジェクトだという話をしましたが、 メソッドの正体はクラスの属性に代入された関数オブジェクトです。

ただし、関数にはちょっとしたマジックがあって、インスタンスから属性アクセスされた時に 第一引数にそのインスタンスを束縛した、メソッドオブジェクトを返すのです。

このマジックはメソッドを作るためだけの仕組みじゃないのですが、 マジックの使い方は高度な内容になるのでここでは割愛します。

プロパティ

php では $x->setFoo($y)$z = $x->getFoo() といったアクセッサを定義することが多いですが、 Python では x.foo = yz = x.foo と書くだけでアクセッサ関数を実行する仕組みがあります。 これをプロパティと呼びます。

>>> class Foo(object):
...     _bar = 1
...     @property
...     def bar(self):
...         return self._bar * 2
...     @bar.setter   # これを定義しないと bar は読み込み専用になる
...     def bar(self, value):
...         return self._bar = value
...
>>> foo = Foo()
>>> foo.bar
2
>>> foo.bar = 2
>>> foo.bar
4

この @ は、関数の章で紹介したデコレータの構文で、 property はデコレータとして使える関数です。

属性へのアクセスの動作をフックする魔法で、普通の関数だと第一引数 (self) にインスタンスを束縛していましたが、 property は getter や setter を呼び出す魔法がかかったオブジェクトを返すのです。

継承

class Foo(BaseClass) のように定義すると継承することができます。

インスタンスの属性にアクセスしようとした時、そのインスタンスに属性がなかったらクラスの属性を探していましたが、 それでも見つからないと親クラスの属性を探します。

この仕組で、メソッドのオーバーライドも、それ以外のクラス属性のオーバーライドも可能になります。

>>> class Foo(object):
...     def foo(self):
...         return 'foo'
...     def bar(self):
...         return 'bar'
...
>>> class Bar(Foo):
...     def foo(self):
...         return 'Bar.foo' + super().foo()
...
>>> bar = Bar()
>>> bar.foo()
'Bar.foo foo'
>>> bar.bar()
'bar'
>>> Foo.foo(bar)
'foo'

Note

Python 2 では、 super(Bar, self).foo() と書く必要がありました。 面倒なので、多重継承を使う必要がない場合は直接親クラスを指定して Foo.foo(self) と書くことのほうが多かったです。

classmethod, staticmethod

php で言う static メソッドは staticmethod というデコレータで実現します。 これは関数にかかっている、属性アクセスされた時に第一引数にインスタンスを束縛する、という魔法を解除します。

>>> class Foo(object):
...    @staticmethod
...    def bar():  # self が無いよ!
...        return 1
...
>>> foo = Foo()
>>> foo.bar()
1

また、インスタンスではなくてクラスを第一引数に束縛する魔法をかける classmethod というデコレータもあります。 classmethod の魔法は、インスタンス経由でもクラス経由でも使えるようになっています。 php で言う new static() を実現するために、ファクトリー関数は staticmethod ではなく classmethod を使ったほうが 良いでしょう。

>>> class Foo(object):
...     @classmethod
...     def bar(cls):
...         return cls()
...
>>> class Bar(Foo):
...     pass
...
>>> bar = Bar.bar()  # クラス属性として実行しても第一引数にクラスが束縛される.
>>> bar
<__main__.Bar object at ...>
>>> bar.bar()        # インスタンス属性として呼び出してもクラスが束縛される.
<__main__.Bar object at ...>

プライベート

Python にはプライベートはありません。 慣習として、アンダースコアで始まる属性やメソッドはプライベートだということになっています。

また、クラスを継承して、たまたま同じ属性名がぶつかってしまうのを防ぐために、 class 文の中で __ で始まる名前 (ただし __init__ みたいに __ で終わる名前は除く) を使うと、 自動的に __<クラス名>_<元の名前> という形の名前に変換されます。

ただし、プライベートな属性やメソッドだってテストしたいことがありますし、 テスト中にプライベートメソッドをモックを返すダミー関数に置き換えたい場合もあります。

なので、 __ を使うよりも、クラスを小さくしたり継承を使い過ぎないようにして たまたま継承したクラスで想定外のメソッドや属性を上書きしてしまうミスを防ぐことをお勧めします。