初めてのPython(26章中編)

__getattr__ / __setattr__ / __getattribute__

__getattr__は、オブジェクトツリーに存在しない属性へのアクセスがあった時だけ呼び出される。
__setattr__は、オブジェクトツリーに存在するしない関わらず呼び出される。

[kobakoba0723@fedora13-intel64 ~]$ cat getsetattr.py 
#!/usr/bin/env python

class A(object):
  def __init__(self):
    self.hoge = "hoge"
    self.piyo = "piyo"
  def __getattr__(self, attrname):
    print "%s not exist in instance or class tree" % attrname
    return "spam"
  def __setattr__(self, attrname, data):
    if attrname in self.__dict__.keys():
      print "%s exists, override" % attrname
    else:
      print "%s not exists, create" % attrname
    self.__dict__[attrname] = data

if __name__ == '__main__':
  x = A()
  print x.hoge
  print x.piyo
  print x.spam
  x.hoge = "hogehoge"
  x.spam = "spamer"
  print x.hoge
  print x.piyo
  print x.spam

[kobakoba0723@fedora13-intel64 ~]$ python getsetattr.py 
hoge not exists, create
piyo not exists, create
hoge
piyo
spam not exist in instance or class tree
spam
hoge exists, override
spam not exists, create
hogehoge
piyo
spamer
[kobakoba0723@fedora13-intel64 ~]$ 

__getattr__って、オブジェクトツリー上の存在有無に関係なく呼び出されると勘違いしてた。。。
思ってたとおり(?)に呼び出されるのが__getattribute__

[kobakoba0723@fedora13-intel64 ~]$ cat getattribute.py 
#!/usr/bin/env python

class A(object):
  def __init__(self):
    self.hoge = "hoge"
    self.piyo = "piyo"
  def __getattribute__(self, attrname):
#    if attrname in self.__dict__.keys():
#      return self.attrname
#    else:
#      print "%s not exists" % attrname
#      return "spam"
    print "Class getattribute invoke"
    return object.__getattribute__(self, attrname)

if __name__ == '__main__':
  x = A()
  print x.hoge
  print x.piyo
  print x.spam

[kobakoba0723@fedora13-intel64 ~]$ python getattribute.py 
Class getattribute invoke
hoge
Class getattribute invoke
piyo
Class getattribute invoke
Traceback (most recent call last):
  File "getattribute.py", line 20, in <module>
    print x.spam
  File "getattribute.py", line 14, in __getattribute__
    return object.__getattribute__(self, attrname)
AttributeError: 'A' object has no attribute 'spam'
[kobakoba0723@fedora13-intel64 ~]$ 

__getattibute__を使って __getattr__相当のことをやろうとして、self.__dict__[attrname]を使おうとしたら、

  File "getattribute_err.py", line 8, in __getattribute__
    if attrname in self.__dict__.keys():

延々このエラーが表示されて、しまいにはRuntimeError まで。
インスタンスに対して、"."でアクセスすると、__getattribute__が呼ばれて無限ループになってしまうみたい。
__setattr__のなかで、

self.attrname = value

ってやると無限ループになるのと同じ感じかな。

なにか対処しかたがあるんだろうということで公式ドキュメントを見てると

object.__getattribute__(self, name) のように基底クラスのメソッドを同じ属性名を使って呼び出さなければなりません。

と書いてあるので、見よう見まねで書き換えたらどうやらうまくいった。
とはいえ、なんで動くのかよくわからないので、object.__getattribute__を調べてみると、

>>> help(object.__getattribute__)
__getattribute__(...)
    x.__getattribute__('name') <==> x.name
(END) 
    
指定された属性の値を返してくれるみたいだけど、objectクラスにはhoge/spamなんかの属性は存在しない。 なんで動いているのか調べてみたものの、結果よく判らないまま。