Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Python を支える技術 ディスクリプタ編

Nozomu Kaneko
September 13, 2014

Python を支える技術 ディスクリプタ編

PyCon JP 2014

Nozomu Kaneko

September 13, 2014
Tweet

More Decks by Nozomu Kaneko

Other Decks in Programming

Transcript

  1. ディスクリプタとは • プロトコルの一種 (e.g. イテレータプロトコル) • 属性アクセスをカスタマイズする機構 • Python の基本的な機能の背後にある仕組み

    ◦ プロパティ ◦ メソッド ▪ 静的メソッド、クラスメソッド、 ▪ インスタンスメソッド ◦ super • ユーザ定義することもできる
  2. どんなプロトコルか class Descriptor(object): def __get__(self, obj, type=None): pass def __set__(self,

    obj, value): pass def __delete__(self, obj): pass これらのメソッドを定義したオブジェクトは ディスクリプタになる
  3. 分類 • データディスクリプタ ◦ __get__ と __set__ の両方を定義している場合 • 非データディスクリプタ

    ◦ __get__ だけを定義している場合 ◦ 典型的にはメソッドで使われる • 読み取り専用のデータディスクリプタ ◦ データディスクリプタの中で、 __set__ が呼び出されたときに AttributeError が送出されるもの
  4. プロパティとなにが違うの? • 用途の違い ◦ プロパティ: 通常はデコレータとして、クラス定義の中でそのクラスのインス タンスの属性アクセスをカスタマイズするために使う ◦ ディスクリプタ: 特定のクラスとは独立に定義されて、他のクラスの属性アク

    セスをカスタマイズするために使う • プロパティはディスクリプタの一種 ◦ ディスクリプタの方が応用範囲が広い ◦ プロパティはディスクリプタの典型的な使われ方に特化したもの
  5. プロパティ class C(object): @property def x(self): return 1 assert C.x

    == C.__dict__['x'].__get__(None, C) # クラスから参照した場合はプロパティ自身 assert isinstance(C.x, property) obj = C() assert obj.x == C.__dict__['x'].__get__(obj, C) # インスタンスから参照した場合は関数の戻り値 assert obj.x == 1
  6. メソッド class C(object): def x(self): return 1 assert C.x ==

    C.__dict__['x'].__get__(None, C) obj = C() assert obj.x == C.__dict__['x'].__get__(obj, C) assert C.x != obj.x
  7. メソッド(続き) >>> obj.x <bound method C.x of <__main__.C object at

    0x1018fb990>> Python 2 の場合 >>> C.x <unbound method C.x> Python 3 の場合 >>> C.x <function C.x at 0x1104b2b00>
  8. object.__getattribute__ Objects/typeobject.c PyTypeObject PyBaseObject_Type = { PyVarObject_HEAD_INIT( &PyType_Type, 0) "object",

    /* tp_name */ /* ... */ PyObject_GenericGetAttr, /* tp_getattro */ PyObject_GenericSetAttr, /* tp_setattro */ /* ... */ };
  9. object.__getattribute__ の疑似コード def object_getattribute (self, key): "Emulate PyObject_GenericGetAttr() in Objects/object.c"

    tp = type(self) attr = PyType_Lookup(tp, key) if attr: if hasattr(attr, '__get__') and hasattr(attr, '__set__'): # data descriptor return attr.__get__(self, tp) if key in self.__dict__: return self.__dict__[key] if attr: if hasattr(attr, '__get__'): return attr.__get__(self, tp) return attr raise AttributeError データディスクリプタ呼び出し 非データディスクリプタ呼び出し or クラスの属性辞書参照 インスタンス自身の属性辞書参照
  10. type.__getattribute__ Objects/typeobject.c PyTypeObject PyType_Type = { PyVarObject_HEAD_INIT( &PyType_Type, 0) "type",

    /* tp_name */ /* ... */ (getattrofunc)type_getattro, /* tp_getattro */ (setattrofunc)type_setattro, /* tp_setattro */ /* ... */ };
  11. type.__getattribute__ の疑似コード def type_getattribute (cls, key): "Emulate type_getattro() in Objects/typeobject.c"

    meta = type(cls) # (メタクラスに対して object.__getattribute__ と同じ処理) attr = PyType_Lookup(cls, key) if attr: if hasattr(attr, '__get__'): return attr.__get__(None, cls) return attr # (メタクラスに対して object.__getattribute__ と同じ処理) ディスクリプタ呼び出し or 自分と親クラスの属性辞書参照
  12. プロパティの疑似コード class Property(object): "Emulate PyProperty_Type() in Objects/descrobject.c" def __init__(self, fget=None,

    fset=None, fdel=None): self.fget = fget self.fset = fset self.fdel = fdel def __get__(self, obj, klass=None): if obj is None: # via class return self if self.fget is not None: return self.fget(obj) raise AttributeError
  13. classmethod の疑似コード class ClassMethod(object): "Emulate PyClassMethod_Type() in Objects/funcobject.c" def __init__(self,

    f): self.f = f def __get__(self, obj, klass=None): if klass is None: klass = type(obj) return types.MethodType(self.f, klass)
  14. インスタンスメソッドの実体は関数 class C(object): pass def f(self, x): return x obj

    = C() # obj.f(1) をエミュレートする meth = f.__get__(obj, C) assert isinstance(meth, types.MethodType) assert meth(1) == 1
  15. >>> def f(x, y): return x + y ... >>>

    f <function f at 0x10e51b1b8> >>> f.__get__(1) <bound method int.f of 1> >>> f.__get__(1)(2) 3 関数はディスクリプタ
  16. Python 2 と 3 の実装の違い class C(object): def f(self): pass

    $ python3 >>> C.f # == C.__dict__['f'] <function C.f at 0x10356ab00> $ python2 >>> C.f # != C.__dict__['f'] <unbound method C.f> >>> C.f.__func__ # == C.__dict__['f'] <function f at 0x10e02d050> Python 3 には unbound method は存在しない
  17. super class C(object): def x(self): pass class D(C): def x(self):

    pass class E(D): pass obj = E() assert super(D, obj).x == C.__dict__['x'].__get__(obj, D)
  18. super.__getattribute__ の疑似コード def super_getattribute (su, key): "Emulate super_getattro() in Objects/typeobject.c"

    starttype = su.__self_class__ mro = iter(starttype.__mro__) for cls in mro: if cls is su.__self_class__: break # Note: mro is an iterator, so the second loop # picks up where the first one left off! for cls in mro: if key in cls.__dict__: attr = cls.__dict__[key] if hasattr(attr, '__get__'): return attr.__get__(su.__self__, starttype) return attr 指定されたクラスの mro から、そのクラ スの「次」のクラスを検索している
  19. super はディスクリプタでもある class C(A): def meth(self, a): return "C(%r)" %

    a + self.__super.meth(a) C._C__super = super(C) assert C().meth(3) == "C(3)A(3)" あまり有効に使われてなさそう。 Guido の思いつき? PEP 367 -> 3135 (New super) で提案された self.__super__. foo() という仕様と関係?
  20. ユーザ定義の例: reify class reify(object): def __init__(self, wrapped): self.wrapped = wrapped

    def __get__(self, inst, objtype=None): if inst is None: return self val = self.wrapped(inst) setattr(inst, self.wrapped.__name__, val) return val 同じ名前でインスタンスの属性辞書にセットしているため 次回からはその値が優先される (なぜなら reify は非データディスクリプタだから )
  21. reify の使い方 class C(object): _count = 0 @reify def count(self):

    C._count += 1 return C._count o = C() assert o.count == 1 assert o.count == 1 C._count = 2 assert o.count == 1
  22. FAQ: 何に使えるの? 属性アクセス (X.Y) の形で書けることならなんでも • O/Rマッパー • クラス経由とインスタンス経由で異なる値を返したい場合 •

    プロパティ+特定パターンの置き換え • 通常とは異なるデータアクセス方法が必要な場合 • DSL (?)
  23. FAQ: ディスクリプタの利用例 • InstrumentedAttribute, query_property, echo_property, AssociationProxy (SQLAlchemy) • classinstancemethod

    (Paste, FormEncode) • singletonmethod (FormEncode) • environ_getter (Paste) • RequestLocalDescriptor (ToscaWidgets)