オブジェクトの属性を操る(2) - 属性へのアクセスをフックする
【 目次 】
特殊メソッド__getattribute__メソッド,__getattr__ メソッド,__setattr__メソッド,__delattr__メソッドを使って属性値アクセスをカスタマイズする事ができる。
- 属性値アクセスをカスタマイズする - データモデル — Python 2.7ja1 documentation
- B.4.算出属性 - 特殊メソッド名 - Dive Into Python 3 日本語版
__getattribute__メソッド
__getattribute__メソッドを使うとオブジェクトのすべての属性へのアクセスを横取り(フック)する事ができる。
すべての属性アクセスの意味は、定義済みの属性だけでなく未定義の属性にアクセスに対しても__getattribute__メソッドが呼び出されるという事。
__getattribute__メソッドの例を以下に示す。
__getattribute___メソッドの例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class MyCls(object): def __init__(self): self.x="x_value" self.y="y_value" def __getattribute__(self,name): print "__getattribute__:name=",name # 属性名がxの場合はフックして別の値を返す。 if name=="x": return "other_value" try: # x以外の定義済みの属性名の場合はもともとの属性値にアクセス。 return object.__getattribute__(self, name) except: # 未定義の属性の場合はundefine_valueを返す。 return "undefine_value" my_inst=MyCls() print my_inst.x print my_inst.y print my_inst.z |
実行結果
__getattribute__:name= x other_value __getattribute__:name= y y_value __getattribute__:name= z undefine_value
上記のプログラムはクラスMyClsのインスタンス変数としてxとyを定義しているが__getattribute__メソッド内で属性名がxの場合にxの値ではなくother_valueを返す。
また、未定義の属性(ここではz)の場合にはundefine_valueを返す。
それ以外の定義済みの属性yの場合は属性値をそのまま返している。
13行目でobject.__getattribute__(self, name)
を使ってスーパクラス(objectクラス)の__getattribute__メソッドを呼び出して、もともと持っている属性値を取り出しているが、これをself.__getattribute__としてしまうと再帰的に__getattribute__メソッドが呼び出されて無限ループに陥ってしまう。
また、同様に__getattribute__メソッド内で、
self.__dict__[name]
や、組込み関数を使って
getattr(self,name)
または
属性名が存在するかどうか確認のために
if name in self.__dict__:
や
if hasattr(self,name):
等のコードを記述しても無限ループが発生してしまう。
object.__getattribute__(self, name)
を使ってobjectクラスのメソッドを直接呼び出す事で、オーバライト前のobjectクラスが本来持っている特殊メソッドを使ってフックを阻止して無限ループを回避することができる事になる。
__getattr__ メソッド
クラスに__getattr__ メソッドを定義されていれば、未定義の属性にアクセスしようとした時に__getattr__ メソッドが呼び出される。
class MyCls(object): def __init__(self): self.x="x_value" def __getattr__(self,name): print "__getattribute__:name=",name return "undefine_value" my_inst=MyCls() print my_inst.x print my_inst.y
実行結果
x_value __getattr__:name= y undefine_value
__getattr__ メソッドは未定義の属性にアクセスしようとした時にのみ呼び出され、定義済みの属性の場合には呼び出されない。
__getattribute__メソッドと__getattr__ メソッドの関係
__getattribute__メソッドが定義されていると未定義の属性にアクセスしようとした時に先に__getattribute__メソッドが呼び出されてしまい属性アクセスが横取りされてしまい、__getattr__ メソッドが呼び出されなくなってしまう。
class MyCls(object): def __init__(self): self.x="x_value" def __getattribute__(self,name): print "__getattribute__:name=",name try: return object.__getattribute__(self, name) except: return "other_value" def __getattr__(self,name): print "__getattr__:name=",name return "undefine_value" my_inst=MyCls() print my_inst.x print my_inst.y
実行結果
__getattribute__:name= x x_value __getattribute__:name= y other_value
__getattribute__メソッドと__getattr__ メソッドが両方呼び出されるようにしたい場合は、以下のように__getattribute__内で属性が未定義の場合にobject.__getattribute__を呼び出せば良い。
class MyCls(object): def __init__(self): self.x="x_value" def __getattribute__(self,name): print "__getattribute__:name=",name try: return "fuck_value"+"("+object.__getattribute__(self, name)+")" except: return object.__getattribute__(self, name) def __getattr__(self,name): print "__getattr__:name=",name return "undefine_value" my_inst=MyCls() print my_inst.x print my_inst.y
実行結果
__getattribute__:name= x fuck_value(x_value) __getattribute__:name= y __getattr__:name= y undefine_value
__setattr__メソッド
__setattr__メソッドを定義して属性の値の設定をフックする事ができる。
次のプログラムは属性値に文字列型の値を代入すると値に「str:」を付加する例である。
class MyCls(object): def __init__(self): self.x="x_value" def __setattr__(self,name,value): if isinstance(value, str): object.__setattr__(self,name,"str:"+value)
面白いのは、上記のコードのように__init__メソッド等のクラス内のメソッドから値を代入しても__setattr__が呼び出されてしまうことである。
my_inst=MyCls() print my_inst.x
実行結果
str:x_value
組込み関数setattrで値を設定しても__setattr__が呼び出される。
setattr(my_inst, "y", "y_value") print my_inst.y
実行結果
str:y_value
しかし、__dict__を使ってインスタンスの属性辞書に直接アクセスすると__setattr__は呼び出されない。
my_inst.__dict__["z"]="z_value" print my_inst.z
実行結果
z_value
__delattr__メソッド
__delattr__メソッドを使って属性の削除をフックする事ができる。
次のプログラムはプライベート変数(変数名の先頭が_)の削除を禁止する__delattr__メソッドの例である。
class MyCls(object): def __init__(self): self.x="x_value" self._x="_x_value" def __delattr__(self,name): if not name.startswith("_"): object.__delattr__(self,name) my_inst=MyCls() print hasattr(my_inst,"x") del my_inst.x print hasattr(my_inst,"x")
実行結果
True False
del関数により属性xは削除された。
しかし属性_xは削除されない。
print hasattr(my_inst,"_x") del my_inst._x print hasattr(my_inst,"_x")
実行結果
True True
delattr関数を使っても属性_xは削除されない。
delattr(my_inst,"_x") print hasattr(my_inst,"_x")
実行結果
True
__dict__を使って直接、削除をおこなう場合は__setattr__と同様に__delattr__メソッドも呼び出されないので削除できる。
del my_inst.__dict__["_x"] print hasattr(my_inst,"_x")
実行結果
False
他にもgetやsetの付く特殊メソッドとして__getitem__と__setitem__もあるが、これは属性アクセスとは異なった用途で使われる。