PyMEL の utils.cachedProperty をみてふと思ったのでやってみたコネタ。
python/property.py at master · hal1932/python · GitHub
- public フィールド
- property をそのまま使う
- fget 内で hasattr してから setattr
- property 作成時に setattr して getattr
- property 作成時に setattr して getattr(値キャッシュ付き)
public フィールド
class Klass(object): def __init__(self): self.public = None
まずは基本。オブジェクト指向というものをどう捉えるかにもよるけど、「カプセル化」が効かないのであまり良い方法とは思われないことが多い。とはいえ Python では比較的ポピュラーな方法。
内部的には getattr/setattr が呼ばれていて、オーバーヘッドは一番少ない。ここがベースライン。
property をそのまま使う
class Klass(object): def __init__(self): self.__prop = 1 def __get_prop(self): return self.__prop def __set_prop(self, value): self.__prop = value prop = property(__get_prop, __set_prop)
Python で「プロパティ」といったときにおそらく一番標準的な方法。public フィールドと比べると「getter のみ定義する」というのができるのが利点。加えて getter/setter の中で メモ化 なんかを含めたいろんなロジックが組めるので柔軟性も高い。ただ、getter/setter をいちいち別定義してやらないといけないのが面倒。
内部的にはプロパティにアクセスするたびに getter/setter メソッドが呼ばれるので、若干のオーバーヘッドはある。パフォーマンス的には、手許の実測値で「public フィールド」の 1/4 程度。
fget 内で hasattr してから setattr
def create_property(name, creator): def fget(obj): value = None if hasattr(obj, name): value = getattr(obj, name) if value is None: value = creator(obj) setattr(obj, name, value) return value def fset(obj, value): setattr(obj, name, value) return property(fget, fset) class Klass(object): def init(self): self.prop = create_property('prop', lambda: 1)
「property そのまま」を動的にしたパターン。こちらは fget が呼ばれるまでフィールドが生成されないのでメモリに優しい。大量のインスタンスが生成される一方で、プロパティへのアクセス頻度が限定されるときに有効な方法。
パフォーマンス的には hasattr (辞書のキー検索)がどうしてもかかってしまうので、その分だけ遅くはなる。手許の実測値で「property そのまま」の 1/2、「public フィールド」の 1/8 程度。
property 作成時に setattr して getattr
def create_property(self, name, default_value): prop_name = '__{}'.format(name) def fget(obj): return getattr(obj, prop_name) def fset(obj, value): setattr(obj, prop_name, value) setattr(self, prop_name, default_value) return property(fget, fset) class Klass(object): def init(self): self.prop = create_property(self, 'prop', lambda: 1, None)
hasattr の負荷を避けるために property 作成時に setattr してしまうパターン。ただまぁ、これだと「property そのまま」とほぼ変わらない。getter/setter を自分で定義する必要がないってのが利点か。
内部的には fget/fset がインライン化されるのか、パフォーマンス的にも「public フィールド」と変わらない。
property 作成時に setattr して getattr(値キャッシュ付き)
def create_property(self, name, creator, default_value): prop_name = '__{}'.format(name) def fget(obj): value = getattr(obj, prop_name) if value is None: value = creator(obj) setattr(obj, prop_name) return value def fset(obj, value): setattr(obj, prop_name, value) setattr(self, prop_name, default_value) return property(fget, fset) class Klass(object): def init(self): self.prop = create_property(self, 'prop', lambda: 1, None)
インスタンスごとにフィールド変数が生成されるのを許容できる場合は、おそらくこれが使いやすい。fset を使わない場合、`self.__prop = None` でキャッシュをクリアできる。ただし __prop 自体が明示的に宣言されていないのが難点。create_property の引数に別途 invalid_value を渡してもいいんだけど、invalid_value == None の場合に if value == invalid_value で比較するわけにはいかないので、そこがちょっと悩みどころ。
パフォーマンス的には、creator の呼び出し頻度にもよるけど、それを除けば「public フィールド」と変わらない。