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

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

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

PyCon JP 2014

Avatar for Nozomu Kaneko

Nozomu Kaneko

September 13, 2014

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)