初めてのPython(24章中編)
演算子のオーバーロード
__getitem__ と __iter__
どちらも反復処理を実現させるのに使う。
反復処理には次のようなものが該当する。
- forループ
- in演算子によるメンバの存在確認
- タプル/リストのアンパック代入
どっちの演算子をオーバーロードでも反復処理を実現できるけど、反復処理を行う時に検索される順番は、
- __iter__
- __getitem__
__getitem__の場合
[kobakoba0723@fedora13-intel64 ~]$ cat loop_index.py #!/usr/bin/env python class C1(object): def __init__(self, string): self.data = string def __getitem__(self, index): return self.data[index] if __name__ == '__main__': x = C1("spam") for i in range(4): print 'x[%d]: %s' % (i, x[i]) for data in x: print data, [kobakoba0723@fedora13-intel64 ~]$ python loop_index.py x[0]: s x[1]: p x[2]: a x[3]: m s p a m [kobakoba0723@fedora13-intel64 ~]$
__iter__の場合
イテレータプロトコルの場合、反復処理は次の要領で行われる。
- ビルドイン関数iterが呼び出される
- __iter__ メソッドが実行され、イテレータオブジェクトが返される
- イテレータオブジェクトのnextメソッドが呼ばれる(StopIteration例外が発生するまで繰り返し)
なので、__iter__メソッドをオーバーロードする場合、nextメソッドも定義しておく必要がある。
[kobakoba0723@fedora13-intel64 ~]$ cat loop_iter.py #!/usr/bin/env python class C1(object): def __init__(self, string): self.data = string self.index = 0 def __iter__(self): return self def next(self): if self.index == len(self.data): raise StopIteration result = self.data[self.index] self.index += 1 return result if __name__ == '__main__': x = C1('spam') for data in x: print data, [kobakoba0723@fedora13-intel64 ~]$ python loop_iter.py s p a m [kobakoba0723@fedora13-intel64 ~]$
__getitem__ と __iter__ の違い
同じようなことができるけど、__iter__は1回反復処理を実施してしまうと2回目以降には対応できない。
新しくイテレータオブジェクトを作らないといけない。
イテレータプロトコルの場合は、イテレータオブジェクトの要素を消費しながらループを回しているけど、
インデクシングの場合は、単純に要素を指定してその値を返しているだけだから何度でもループを回すことが出来る。
[kobakoba0723@fedora13-intel64 ~]$ python Python 2.6.4 (r264:75706, Apr 1 2010, 02:55:51) [GCC 4.4.3 20100226 (Red Hat 4.4.3-8)] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> import loop_index >>> import loop_iter >>> x = loop_index.C1("spam") >>> [n for n in x] ['s', 'p', 'a', 'm'] >>> [n for n in x] ['s', 'p', 'a', 'm'] >>> y = loop_iter.C1("spam") >>> [n for n in y] ['s', 'p', 'a', 'm'] >>> [n for n in y] [] >>>
ただ、次のように__iter__で新しいオブジェクトを毎回返すようにしておけば対応できる
def __iter__(self): return C1(self.data)
__getattr__ と __setattr__
属性へのアクセス/値の代入に対応するフックメソッド
通常のアクセス、値の代入とは少し違う動きをするので注意が必要
- __getattr__が呼ばれるのは、属性の検索が実施されてヒットしなかった時だけ
- __setattr__の中でインスタンス属性への値を代入すると無限ループになる*2ので、self.__dict__['属性名'] = 値 を使う
[kobakoba0723@fedora13-intel64 ~]$ cat getattr.py #!/usr/bin/env python class C1(object): def __init__(self, value): self.name = value def __getattr__(self, attrname): if attrname == 'age': print '(in __getattr__ method)', return 28 else: raise AttributeError, attrname if __name__ == '__main__': x = C1("taro") print 'name:', x.name print 'age:', x.age print 'address: ', x.address [kobakoba0723@fedora13-intel64 ~]$ python getattr.py name: taro age: (in __getattr__ method) 28 address: Traceback (most recent call last): File "getattr.py", line 18, in <module> print 'address: ', x.address File "getattr.py", line 11, in __getattr__ raise AttributeError, attrname AttributeError: address [kobakoba0723@fedora13-intel64 ~]$
__setattr__は、インスタンス.属性名 = 値 が実行された時に、インスタンス.__setattr__('属性名', '値')の形で呼び出される
[kobakoba0723@fedora13-intel64 ~]$ cat setattr.py #!/usr/bin/env python class C1(object): def __init__(self): pass def __setattr__(self, attrname, value): if attrname == 'name': self.__dict__['name'] = value elif attrname == 'age': self.__dict__['age'] = value else: raise AttributeError, attrname + ' is not allowed to define' if __name__ == '__main__': x = C1() x.name = 'taro' x.age = '28' print 'name:', x.name print 'age:', x.age x.tel = '012-3456-7890' [kobakoba0723@fedora13-intel64 ~]$ python setattr.py name: taro age: 28 Traceback (most recent call last): File "setattr.py", line 20, in <module> x.tel = '012-3456-7890' File "setattr.py", line 12, in __setattr__ raise AttributeError, attrname + ' is not allowed to define' AttributeError: tel is not allowed to define [kobakoba0723@fedora13-intel64 ~]$
__setattr__を使うことで、クラスの外から値を代入できる変数を制限することが出来るようになる。
ただ、上みたいに1個1個if文で書いてると簡単に制限を緩くしたり出来ないので、そういう時は、in演算子を使ってやるとよい
[kobakoba0723@fedora13-intel64 ~]$ cat setattr_new.py #!/usr/bin/env python class C1(object): def __init__(self): pass def __setattr__(self, attrname, value): if attrname in self.private_attr: self.__dict__[attrname] = value else: raise AttributeError, attrname + ' is not allowed to define' class C2(C1): private_attr = ['name', 'age'] if __name__ == '__main__': x = C2() x.name = 'taro' x.age = '28' print 'name:', x.name print 'age:', x.age x.tel = '012-3456-7890' [kobakoba0723@fedora13-intel64 ~]$ python setattr_new.py name: taro age: 28 Traceback (most recent call last): File "setattr_new.py", line 21, in <module> x.tel = '012-3456-7890' File "setattr_new.py", line 10, in __setattr__ raise AttributeError, attrname + ' is not allowed to define' AttributeError: tel is not allowed to define [kobakoba0723@fedora13-intel64 ~]$
今回は、許可するもののリストを作って、そこに対してin演算子でチェックしたが、許可しないもののリストで作るのもいいかも。
あと、__getattr__の場合、何かを制限したりするんじゃないし、どういう用途で使うんだろうか。
__repr__ と __str__
文字列の表現処理に関するフックメソッド
- __repr__ は repr関数や対話型コマンドラインの自動出力機能に対応。また、__str__が無かった時にも対応する
- __str__ は str関数やprintステートメントに対応(人にとって判りやすい形で出力)
reprだけ定義しておけば最低限対応は可能だけど、両方定義すれば呼び出し方に応じて出力内容を変えられる
[kobakoba0723@fedora13-intel64 ~]$ cat output_str.py #!/usr/bin/env python class C1(object): def __init__(self, value): self.data = value def __repr__(self): return 'C1(%s)' % self.data def __str__(self): return 'data: %s' % self.data if __name__ == '__main__': x = C1(10) print '(repr(x), str(x)) = (%s, %s)' % (repr(x), str(x)) print x [kobakoba0723@fedora13-intel64 ~]$ python output_str.py (repr(x), str(x)) = (C1(10), data: 10) data: 10 [kobakoba0723@fedora13-intel64 ~]$
__call__
インスタンスが呼び出された時のフックメソッド
以下のようなコードを書いた時に、関数だけでなくインスタンスを渡せるようになる
[kobakoba0723@fedora13-intel64 ~]$ cat call.py #!/usr/bin/env python def call_func(funcname, value): funcname(value) def func1(value): print value class C1(object): def __init__(self, value): self.data = value def __call__(self, value): print self.data * value if __name__ == '__main__': x = C1(10) call_func(func1, 10) call_func(x, 10) [kobakoba0723@fedora13-intel64 ~]$ python call.py 10 100 [kobakoba0723@fedora13-intel64 ~]$
__del__
[kobakoba0723@fedora13-intel64 ~]$ cat del.py #!/usr/bin/env python class C1(object): def __init__(self, name='unknown'): print 'Hello', name self.name = name def __del__(self): print 'Goodbye', self.name if __name__ == '__main__': x = C1('taro') [kobakoba0723@fedora13-intel64 ~]$ python del.py Hello taro Goodbye taro
その他
オーバーロード可能なメソッドの種類は以下のようにいっぱいある
The Python Language Reference 3.4 Specail method names