Pythonとオブジェクト指向

前回までの説明で、Pythonを実際のツールとして活用するのに必要となる基本的な知識はだいたい伝えられたと思う。後は、デフォルトで用意されている機能を実際にはどう使うのかを理解しておけば、随時検索エンジンなどでTipsやQ&Aを調べていくことである程度のことはできるようになるだろう。

そのため、取り上げるべきかどうか迷うところではあるのだが、「The Python Tutorial」では次の解説対象として「クラス」を挙げているので、ここでも読める程度にクラスの機能を紹介しておこう。

Pythonそのものはオブジェクト指向プログラミングに必要となる機能を一通り提供していると言われており、いわゆるオブジェクト指向プログラミング言語ということになる。しかし、これまでにも説明してきたように、Pythonはオブジェクト指向を意識しなくても利用できる仕組みになっている。ツールとして手軽に使っていきたい場合、クラスの機能はコーディングや考え方をちょっとばかり複雑なものにするので、本連載ではあまり深入りしたくなかったりもする。

The Python Tutorial」のクラスの説明を読んでもらえればわかると思うが、概念やスコープの説明が結構ややこしい。ほかのプログラミング言語でオブジェクト指向プログラミングの経験があるなら、この部分の説明は”よくあるオブジェクト指向プログラミングの説明”だということがわかるかもしれない。しかし、そうでない場合、ちょっとばかり頭が痛くなるところではないかと思う。

本連載では、「ツールとしてPythonを使って楽をする」というところに主眼を置いているので、こういう書き方ができる、という程度に知っておいていただきたい。無論、説明を読み解くのが特に苦ではないなら、Pythonでバリバリのオブジェクト指向プログラミングをしてもらってよいと思う。

クラス

Pythonでは「class」というキーワードを使って、クラスを次のように定義する。サンプルでは「MyClass」がクラス名、「i」がクラス変数、「f()」がメソッドということになる。

>>> class MyClass:
...     """A simple example class"""
...     i = 12345
...
...     def f(self):
...         return 'hello world'
...
>>>

オブジェクトの生成と、クラス変数とメソッドへのアクセス方法は次のようになる。

>>> x = MyClass()
>>> x.i
12345
>>> x.f
<bound method MyClass.f of <__main__.MyClass object at 0x7f87f9faa198>>
>>> x.f()
'hello world'
>>>

次はコンストラクタだ。Pythonではオブジェクトを生成する段階で処理を行わせることができ、その処理はコンストラクタが担う。Pythonでは「__init__()」がコンストラクタとされており、次のような感じで使用する。

>>> class Complex:
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)
>>>

コンストラクタの存在理由だが、その必要性はクラス変数とインスタンス変数の違いがわかるようになるとわかりやすくなる。まず、次のサンプルを見てほしい。

>>> class Dog:
...     kind = 'canine'         # class variable shared by all instances
...
...     def __init__(self, name):
...         self.name = name    # instance variable unique to each instance
...
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind                  # shared by all dogs
'canine'
>>> e.kind                  # shared by all dogs
'canine'
>>> d.name                  # unique to d
'Fido'
>>> e.name                  # unique to e
'Buddy'
>>>

上記サンプルでは「kind」と「name」という2つの変数があることがわかる。kindはクラス変数と呼ばれ、nameはインスタンス変数と呼ばれる。kindはクラスから生成されるすべてのオブジェクト(インスタンス)で共有される。逆に、コンストラクタで生成されるnameはオブジェクト(インスタンス)ごとに用意されており、共有はされない。

つまり、インスタンス変数として値を保持させたい場合には、コンストラクタに引数として値を与えるといった使い方になる。

クラス変数とインスタンス変数の違いを示す次のサンプルを見てみよう。このサンプルでは、add_trick()メソッドが呼ばれると、クラス変数であるtricksに対して値が追加される。このため、2つの異なるオブジェクトからadd_trick()メソッドをコールした結果、共有されているtricksクラス変数に値がアペンドされたことがわかる。

>>> class Dog:
...     tricks = []             # mistaken use of a class variable
...
...     def __init__(self, name):
...         self.name = name
...
...     def add_trick(self, trick):
...         self.tricks.append(trick)
...
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks                # unexpectedly shared by all dogs
['roll over', 'play dead']
>>>

次のサンプルはインスタンス変数の使用サンプルだ。add_trick()メソッドはインスタンス変数に対してアペンドを行っている。インスタンス変数はオブジェクトごとに保持されるため、add_trick()をコールしてもほかのオブジェクトには影響が及んでいないことがわかる。

>>> class Dog:
...     def __init__(self, name):
...         self.name = name
...         self.tricks = []    # creates a new empty list for each dog
...
...     def add_trick(self, trick):
...         self.tricks.append(trick)
...
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']
>>>

次のサンプルはクラスを使った応用例だ。for制御構文にそのまま指定できるオブジェクトを作るために、__iter__()と__next()__というメソッドが実装され、求められる仕様を満たしている。このように必要なメソッドが実装されていれば、要求される動作を満たすことができる。

>>> class Reverse:
...     """Iterator for looping over a sequence backwards."""
...     def __init__(self, data):
...         self.data = data
...         self.index = len(data)
...
...     def __iter__(self):
...         return self
...
...     def __next__(self):
...         if self.index == 0:
...             raise StopIteration
...         self.index = self.index - 1
...         return self.data[self.index]
...
>>>
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x7f87f9faa908>
>>> for char in rev:
...     print(char)
...
m
a
p
s
>>>

Pythonのクラスに関しては最低限、この辺りを理解しておけばよいだろう。

処理やデータをまとめておく

お手軽ツールとしてPythonを使っていく場合には、クラスをオブジェクト指向的に考えるというよりは、データや処理を(関数を)まとめていくためのデータ構造の1つ、くらいに考えておけばよいと思う。似たような意味合いを持つデータと関数をまとめておいたほうがいいな、と思い始めたらクラスとして整理する、くらいの感じでよいだろう。

もちろんオブジェクト指向で考えを整理することに慣れているなら、最初からその方法でPythonを使っていく方向でよいと思う。好きなら使い、よくわからないなら使わない。ツールとしてPythonを使うという観点から言えば、クラスについてはそんなスタンスでよいのではないだろうか。

【参考資料】
The Python Tutorial