Cached_propertyデコレータ

そのような構造はどれくらいの頻度で書きますか?



class SomeClass(object): @property def param(self): if not hasattr(self, '_param'): self._param = computing() return self._param @param.setter def param(self, value): self._param = value @param.deleter def param(self): del self._param
      
      





これは非常に便利です。このアプローチのparam属性値はオブジェクトに直接保存されませんが、毎回計算されるわけではありません。 計算は最初の呼び出しで行われ、この値は一時的な名前_paramでオブジェクトに保存されます。 param値が依存する条件が変更された場合、それを削除して、次の呼び出しで再度計算されます。 または、既知の場合は、現在の値をすぐに割り当てることができます。



このコードにはマイナスがあります。オブジェクトには_paramという名前の追加の属性があります。 属性が呼び出されるたびに、hasattrを行うparam()メソッドが呼び出されます。 特にクラスにそのような属性がいくつかある場合、結果のコードは十分に大きくなります。



オブジェクトディクショナリを直接操作することで、_param属性を削除できます。



 class SomeClass(object): @property def param(self): if 'param' not in self.__dict__: self.__dict__['param'] = computing() return self.__dict__['param'] @param.setter def param(self, value): self.__dict__['param'] = value @param.deleter def param(self): del self.__dict__['param']
      
      





ここでは、計算された値は記述子と同じ名前の属性に格納されます。 @propertyデコレータがデータ記述子を作成する(宣言されたメソッド__set __()の記述子が呼び出される)ため、必要な属性が__dict__オブジェクトディクショナリに存在する場合でも、ゲッターとセッターが実行されます。 そして、オブジェクトの属性をバイパスして、この__dict__を直接操作するという事実により、競合や無限の再帰はありません。



しかし、上記のコードにはまだ一般的な部分が多すぎます。 同じ属性の2番目は、computing()関数のみが異なります。 すべての大まかな作業を行う別のデコレータを作成してみましょう。 そして、次のようなデコレータを使用できます。



 class SomeClass(object): @cached_property def param(self): return computing()
      
      





残りのコードはデコレータ記述子自体に転送されます。



 class cached_property(object): def __init__(self, func): self.func = func self.name = func.__name__ def __get__(self, instance, cls=None): if self.name not in instance.__dict__: result = instance.__dict__[self.name] = self.func(instance) return result return instance.__dict__[self.name] def __set__(self, instance, value): instance.__dict__[self.name] = value def __delete__(self, instance): del instance.__dict__[self.name]
      
      





そこに止めることができます。 しかし、Pythonでは、そのような場合に特別であるかのように、記述子はデータ記述子と非データ記述子に分けられます。 非データ記述子には__get __()メソッドのみが必要です。属性にアクセスするとき、オブジェクトのディクショナリに既に値がある場合、このメソッドは呼び出されません。 つまり __set __()および__delete __()メソッドを削除すると、Pythonインタープリター自体がオブジェクトのディクショナリ内の属性の存在を確認します。 その結果、@ cached_propertyデコレーターは数回単純化されます。



 class cached_property(object): def __init__(self, func): self.func = func def __get__(self, instance, cls=None): result = instance.__dict__[self.func.__name__] = self.func(instance) return result
      
      





このデコレータは、多くのPythonプロジェクトで長い間使用されており、django.utils.functionalからバージョンDjango 1.4以降でインポートできます。 その使用方法は非常にシンプルで安価なので、一部の属性の計算を延期できる場所であればどこでも使用する価値があります。 例:



 class SomeList(object): storage_pattern = 'some-list-by-pages-{}-{}' def __init__(self, page_num, per_page): self.page_num, self.per_page = page_num, per_page self.storage_key = self.storage_pattern.format(page_num, per_page)
      
      





以下に変換できます:



 class SomeList(object): storage_pattern = 'some-list-by-pages-{}-{}' def __init__(self, page_num, per_page): self.page_num, self.per_page = page_num, per_page @cached_property def storage_key(self): return self.storage_pattern.format(self.page_num, self.per_page)
      
      






All Articles