クラスにイテレータの性質を付加する

  • クラスと辞書の関係

 ちょっと前の記事で紹介したとき、pythonのクラスはデフォルトとしてイテレータとして使用できていませんでした。今回も基本的なことですが、イテレータを定義する話です。

>>> class _:
	pass
>>> list(_)
Traceback (most recent call last):
  File "<pyshell#26>", line 1, in <module>
    list(_)
TypeError: 'type' object is not iterable

 「オブジェクトはイテレータではありません。」そうか、じゃあ定義しよう。たとえば、辞書型みたいに整形して、属性をイテラブルに呼び出したりできるクラスがほしいな。。と思っていたら、できるみたいです。

class Person:
    def __init__(self, age, sex, name, subject):
        self.age = age
        self.sex = sex
        self.name = name
        self.subject = subject

    def __iter__(self):
        for k, v in self.__dict__.items():
            yield (k,v)

if __name__ == '__main__':
    p = Person(20, "male", "John Smith", "computer simulation")
  • 実際に動かしてみた

 これでクラスの定義ができました。実際に動かしてみましょう。

>>> p
<__main__.Person object at 0x0000000003924518>
>>> p.__dict__
{'subject': 'computer simulation', 'sex': 'male', 'name': 'John Smith', 'age': 15}
>>> [i for i in p]
[('age', 15), ('subject', 'computer simulation'), ('name', 'John Smith'), ('sex', 'male')]
>>> [print(k+":",v) for k,v in p]
subject: computer simulation
sex: male
name: John Smith
age: 15
[None, None, None, None]

 クラスの属性は辞書型で保存されているようです。それを用いてジェネレータ関数を使用し__iter__を定義してみました。ジェネレータをまわすときにはyieldを使いましょう。もしreturnで定義してしまうと

#変更したクラスの箇所
def __iter__(self):
	for k, v in self.__dict__.items():
		#yield (k, v)
		return (k, v)
>>> [i for i in p]
Traceback (most recent call last):
  File "<pyshell#45>", line 1, in <module>
    [i for i in p]
TypeError: iter() returned non-iterator of type 'tuple'

 イテレータではなく、普通にタプルを返すので、イテレータをちゃんと返せとエラーが返ってきます。

  • そもそも論

 初めから__iter__や__dict__があるならあるいはもっと基本的な__iter__, __dict__の定義があるんじゃないだろうか。そうなんです。たとえば、これだと

class A:
	def __init__(self,x,y):
		self.x = x
		self.y = y
>>> a = A(1,2)
>>> a
<__main__._ object at 0x000000000376EE48>
>>> a.__dict__
{'y': 2, 'x': 1}

 辞書はもともと入っているので、先ほどのようにイテレータを定義せずとも辞書は出せたわけです。当たり前といえばそうなんだけど

>>> help(a)
Help on A in module __main__ object:

class _(builtins.object)
 |  Methods defined here:
 |  
 |  __init__(self, x, y)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

 ヘルプにもちゃんと初めから書いてあるし。。じゃあ、__iter__はあるのかというと

>>> help(a.__iter__)
Traceback (most recent call last):
  File "<pyshell#97>", line 1, in <module>
    help(a.__iter__)
AttributeError: 'A' object has no attribute '__iter__'

 ない。だったら、書けばいいのだが、iterだけを用意したらいいのかというとそうでもなくて、next()関数を定義してやることで正常に動かせるようです。

(引用)
Pythonの技法:ジェネレータを用いた遅延リストの構築 - builder by ZDNet Japan
Python のジェネレータ (1) - 動作を試す | すぐに忘れる脳みそのためのメモ
Python のイテレータ (3) | すぐに忘れる脳みそのためのメモ
エキスパートPythonプログラミング(1) イテレータ、ジェネレータ、ジェネレータ式 - 作業記録/備忘録(仮)
Pythonのイテレータとジェネレータ - Qiita
ジェネレータ (プログラミング) - Wikipedia