『Effective Python』の続き。ナマケモノでいこう。 www.effectivepython.com
遅延評価を__getattr__
やgetattribute__
、__setattr__
で実現できますよ、という話。
__getattr__
らは一般に、クラスの属性アクセスを制御するための特殊メソッドで、その具体的な応用例の項目である。
class LazyDB(object): def __init__(self): self.exists = 5 def __getattr__(self, name): value = 'Value for %s' % name setattr(self, name, value) return value class LoggingLazyDB(LazyDB): def __getattr__(self, name): print('Called __getattr__(%s)' % name) return super().__getattr__(name) data = LoggingLazyDB() print('exists:', data.exists) print('foo: ', data.foo) print('foo: ', data.foo)
のように、__getattr__
が実装されたクラスのインスタンスに存在しない属性へアクセスした場合、__getattr__
が実行される。
つまり、クラスのインスタンス生成時にすべての属性を保持しようとすると重たくなるようなものでも呼び出されてから作るようにすれば何かと経済的にできるよ、という話である。
__getattr__
に似た特殊メソッドとして__getattribute__
がある。
__getattr__
は 存在しない属性へアクセスした場合 に実行されるが、
__getattribute__
は アクセス時毎回 実行される。
class ValidatingDB(object): def __init__(self): self.exists = 5 def __getattribute__(self, name): print('Called __getattribute__(%s)' % name) try: return super().__getattribute__(name) except AttributeError: value = 'Value for %s' % name setattr(self, name, value) return value data = ValidatingDB() print('exists:', data.exists) print('foo: ', data.foo) print('foo: ', data.foo)
__setattr__
も同様に扱える。こちらも アクセス時毎回 実行される。
class SavingDB(object): def __setattr__(self, name, value): # Save some data to the DB log pass super().__setattr__(name, value) class LoggingSavingDB(SavingDB): def __setattr__(self, name, value): print('Called __setattr__(%s, %r)' % (name, value)) super().__setattr__(name, value) data = LoggingSavingDB() print('Before: ', data.__dict__) data.foo = 5 print('After: ', data.__dict__) data.foo = 7 print('Finally:', data.__dict__)
注意点として、__getattribute__
や__setattr__
内で直接self.value
を扱おうとすると 無限ループ に陥るのでsuper().__setattr__(self, value)
など
親クラスから__getattribute__
や__setattr__
を呼び出して設定すること、とある。
この話は3. データモデル — Python 3.4.3 ドキュメントにも触れられている。
このメソッドは (計算された) 属性値を返すか、 AttributeError 例外を送出します。このメソッドが再帰的に際限なく呼び出されてしまうのを防ぐため、実装の際には常に、必要な属性全てへのアクセスで、例えば object.getattribute(self, name) のように基底クラスのメソッドを同じ属性名を使って呼び出さなければなりません。
また、『Effective Python』では触れられていないが、公式ドキュメントや『Python文法詳解』には__getattribute__
や__getattr__
が呼び出されない例外的な状況が説明されている。
公式ドキュメントには注釈として書かれている。
言語構文や組み込み関数から暗黙に呼び出された特殊メソッドの検索では、このメソッドも回避されることがあります。
『Python文法詳解』には具体例も交えて説明している。
パフォーマンス上の理由から、特殊メソッドが暗黙的に呼び出される場合は、getattribute()/getattr()
メソッドは呼び出されません。 例えば、
spam + hamという式を評価すると
spam.add(ham)呼び出されますが、このとき、
addメソッドは
getattribute`メソッドを経由せず、直接実行されます。『Python文法詳解』7.8.11 属性アクセス
属性にまつわる項目はこれで最後。次回からはメタクラスへ。