クラスにイテレータの性質を付加する
- クラスと辞書の関係
ちょっと前の記事で紹介したとき、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