メモ代わり。てきとーに。 いや、ですからてきとーですって。 2年前ぐらいにPythonあたりでメールくれた方、ごめんなさい。メール紛失してしまい無視した形になってしまいました。。。

2008年2月22日金曜日

[Python][お勉強] Python入門(50) - 演算子のオーバーロード

演算子のオーバーロードから。


演算子のオーバーロードを行うには、演算子に対応付けられたメソッドを対象クラスに実装してやる必要がある。
以下、演算子と演算子に対応付けられたメソッドの一部。

演算子・処理対応するメソッド名メソッド起動の例
+__add__A + B
-__sub__A - B
/__div__A / B
*__mul__A * B
|__or__A | B
インスタンスの作成__init__Class()
ガーベージコレクション__del__
出力・型変換__repr__,__str__print X, `X`, str(X)
インスタンスの呼び出し__call__X()
.を使用した属性へのアクセス__getattr__X.abc
属性への値の代入__setattr__X.abc = value
インデクシング__getitem__X[key]
シーケンスの特定の要素への代入__setitem__X[key] = value
シーケンスの長さ取得__len__len(X)
比較__cmp__X == Y, X < Y
小なり__lt__ X < Y(__cmp__でも対応可)
同等か__eq__ X == Y(__cmp__でも対応可)
インスタンスが右側に使われた場合の+演算子__radd__Noninstance + X
上書き加算__iadd__X += Y(__add__でも対応可)
ループ、反復__iter__forループ, X in Y


演算子が呼ばれると、対応する__XXX__といった前後にアンダースコアがつけられた関数が呼ばれる。また、そういったメソッドを他の通常のメソッドと区別して「フックメソッド」と呼ばれる。


__getitem__
__getitem__はインデクシングの処理に対応する「フックメソッド」。
対象クラスに定義されているか、対象クラスの継承ツリーに定義されていないとコールされない。

以下、__getitem__の例

>>> class C1:
... def __getitem__(self, index):
... return index * 100
...
>>> a = C1()
>>> a[0]
0
>>> a[1]
100
>>> a[2]
200
>>> a[3]
300
>>>
 


この__getitem__メソッドはループにも対応可。

>>> for b in a:
... if b > 1000: break
... print b
...
0
100
200
300
400
500
600
700
800
900
1000
>>>
 

このようにループでも__getitem__が呼ばれる。が、Pythonの中では__getitem__メソッドを実行する
前に、__iter__メソッドが実装されていないか調べている。もし、__iter__があった場合で、ループ処理
の場合には__iter__がコールされる。


__iter__
__iter__は「イテレータプロトコル」をサポートしたオブジェクト(イテレータオブジェクト)を返す。

以下、その例。

>>> class C1:
... def __init__(self, start, stop):
... self.value = start - 1
... self.stop = stop
... def __iter__(self):
... return self
... def next(self):
... if self.value == self.stop:
... raise StopIteration
... self.value += 1
... return self.value ** 2
...
>>> for aa in C1(1,10):
... print aa
...
1
4
9
16
25
36
49
64
81
100
>>>

 

上記の例では__iter__はselfを返している。
__iter__はイテレータオブジェクトを返す必要があるので、self自身にnextメソッドを持たせる必要がある。
nextメソッドでは現在保持している値に1を加算し、その加算結果を2乗している。

__iter__はループ処理自体を複数回繰り返すことには対応していない。

>>> aa = C1(1,5)
>>> for bb in aa:
... print bb
...
1
4
9
16
25
>>> for bb in aa:
... print bb
...
>>>
 

複数回実行するにはもう一度オブジェクトを作成しなければならない。


__getattr__
通常の「インスタンスの属性へのアクセス」では呼ばれないとのこと。

>>> class C1:
... def __getattr__(self, name):
... print "name=[", name , "]"
... return self.X
... X = [1,2,3,4]
...
>>> a = C1()
>>> a.X
[1, 2, 3, 4]
>>> a.Y
name=[ Y ]
[1, 2, 3, 4]
>>>
 

ほんとだ。
XはC1クラスに存在するので、__getattr__はコールされない。
オブジェクトツリーも含めて存在しない場合にのみコールされるとのこと。
上記Yはどこにも存在しないので、__getattr__が呼ばれている。



__setattr__
<オブジェクト>.属性 = 値
といった形の場合、コールされるメソッド。
実際には、

<オブジェクト>.__setattr__(属性名、値)
 

というコードが実行される。
このメソッドで注意しなければいけないところは、
__setattr__メソッド内で属性へ代入すると無限ループになってしまう、という点。
つまり、

def __setattr__(self, attr, value):
self.X = value
 

とすると、__setattr__メソッドの中でさらに__setattr__が呼ばれる。
いつまでたっても終了しない。

そこで、こういった場合、__dict__を使用するとのこと。

>>> class C1:
... def __setattr__(self, attr, value):
... if attr == 'age':
... self.__dict__[attr] = value
... else:
... raise AttributeError, attr + ' not allowed'
...
>>> a = C1()
>>> a.age = 1000
>>> a.age
1000
>>> a.ageage = 1000
Traceback (most recent call last):
File "", line 1, in ?
File "", line 6, in __setattr__
AttributeError: ageage not allowed
>>>
 

な感じ。


__repr__、__str__
str()や、repr()で別の動作をさせたいときに実装する。


__call__
インスタンスを関数のように「呼び出し」たときにコールされる。

>>> class C1:
... def __call__(self):
... return str(self)
...
>>> a = C1()
>>> a()
'<__main__.c1>'
>>>
 


コールバックハンドラなどを作成するときに使える。



__del__
インスタンスが破棄されるときに呼ばれる。
リファレンスがどこにも存在しなくなり、ガーベージコレクタによって破棄されるときにコールされる
メソッド。







おしまい。
.

0 コメント: