ディスクリプタの使い方を色々考えてて、挙動が面白かったので書いておこう。
てか、ディスクリプタって何で使えばいいんだろうか。
何かに使えそうな感じはするんだけど、具体的に何使うかは何とも・・・。
オブジェクトに値入れる時のバリデーションを共通化とかかな?
とりあえず、公式はこの辺
で、公式に
クラスへ束縛すると、A.x は呼び出し A.__dict__['x'].__get__(None, A) に変換されます。
って書いてあったので、試しに、、、
class DescriptorA: def __set_name__(self,owner,name): self._name = name def __get__(self, instance, owner): return owner.a class MyClassA: a = DescriptorA()
ってやってみたら、案の定、無限ループして落ちた。つか、Pythonのエンジン側で再帰の制限かかってたのね。初めて知った。
[Previous line repeated 496 more times] RecursionError: maximum recursion depth exceeded
ふむふむ。アクセスメソッド的に呼ばれるインターフェースが決まってて、それを定義したクラスがディスクリプタって感じでしょか。
で、公式に乗ってるサンプルは下記なんだけど、
class RevealAccess(object): """A data descriptor that sets and returns values normally and prints a message logging their access. """ def __init__(self, initval=None, name='var'): self.val = initval self.name = name def __get__(self, obj, objtype): print('Retrieving', self.name) return self.val def __set__(self, obj, val): print('Updating', self.name) self.val = val class MyClass(object): ... x = RevealAccess(10, 'var "x"') ... y = 5
これって、値の本体はMyClassじゃなくて、RevealAccess側のインスタンスだよね?
なんかMyClassのクラス変数でもインスタンス変数でも無いように思うのですが、こういう使い方が良いの?
てか、むしろこれやると、
class DescriptorB: def __init__(self,value): self.value = value def __set_name__(self,owner,name): self._name = name def __get__(self, instance, owner): return self.value def __set__(self, instance, value): self.value = value class MyClassA: a = DescriptorA(10) b = 100 if __name__ == "__main__": x = MyClassB() y = MyClassB() print(f"before x.a : {x.a}") print(f"before y.a : {y.a}") y.a = 20 print("set : y.a = 20") print(f"after x.a : {x.a}!!!!") print(f"after y.a : {y.a}") print(f"before x.b : {x.b}") print(f"before y.b : {y.b}") y.b = 200 print("set : y.b = 200") print(f"after x.b : {x.b}") print(f"after y.b : {y.b}") ↓↓↓↓↓↓↓↓ before x.a : 10 before y.a : 10 set : y.a = 20 after x.a : 20!!!! after y.a : 20 before x.b : 100 before y.b : 100 set : y.b = 200 after x.b : 100 after y.b : 200
って感じで、なんちゃってシングルトンのような超グローバル変数的な何かになる。
意図せずやっちゃったら相当ヤバイけど、意図的に何かに使えそうな気もする。
で、あと、色々見てると↓の感じでインスタンス変数扱いにしてるのを見る。
class DescriptorC: def __set_name__(self,owner,name): self._name = name def __get__(self, instance, owner): return instance.__dict__[self._name] def __set__(self, instance, value): instance.__dict__[self._name] = value class MyClassC: a = DescriptorC() if __name__ == "__main__": x = MyClassC() x.a = 10 print(x.a) ↓↓↓↓↓ 10
で、インスタンス変数へのアクセスっぽいんだけど、これって、値の代入するまでインスタンスの__dict__に対象の名前いないから、代入する前にアクセスすると落ちるよね?
if __name__ == "__main__": x = MyClassC() print(x.a)#これは落る x.a = 10 print(x.a)#こっちは落ちない ↓↓↓↓↓ return instance.__dict__[self._name]でkeyerror
あと、公式だと↓で書いてあるので、
オブジェクトインスタンスへ束縛すると、a.x は呼び出し type(a).__dict__['x'].__get__(a, type(a))
クラスへ束縛すると、A.x は呼び出しA.__dict__['x'].__get__(None, A)
クラス変数として呼ぶと
if __name__ == "__main__": x = MyClassC() x.a = 10 print(x.a) print(MyClassC.a)#これでも__get__呼び出されてキーエラー
んー、扱いがムズイ。
基本的にはインスタンス変数として扱いたいんだと思うので、↓の感じがいいのかしら。
class DescriptorD: def __set_name__(self,owner,name): self._name = name def __get__(self, instance, owner): if instance is None: return "申し訳ないがClassはNG" return instance.__dict__[self._name] def __set__(self, instance, value): instance.__dict__[self._name] = value class MyClassD: a = DescriptorD() def __init__(self): self.a = "" if __name__ == "__main__": x = MyClassD() print(x.a) x.a = "init" print(x.a) print(MyClassD.a)
どうなんでしょ。個人的にはこれがいいと思うんだけど。それか、__get__の中でkeyが無かったら何かしらの初期値入れて、初期化するように組むか。
あと、公式の引数がそうなってるからだと思うのですが、色々見るサンプルって、下の感じ。
object.__get__(self, instance, owner)
このownerってクラスなんだけど、分りにくくない?
実際、Djangoのmodelsのソースでもディスクリプタ使われてるんですけど、
下の名前で使ってる。
def __get__(self, instance, cls=None):
clsの方が分かりやすいと思うんだけどなー。