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

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

33a639236e5fa989dde2f579202ec18a?s=47 Nozomu Kaneko
September 13, 2014

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

PyCon JP 2014

33a639236e5fa989dde2f579202ec18a?s=128

Nozomu Kaneko

September 13, 2014
Tweet

Transcript

  1. Python を支える技術 ディスクリプタ編 Nozomu Kaneko 2014.9.13 PyCon JP 2014 @国際研究交流大学村

  2. 発表タイトルについて シリーズものではありません (少なくとも今のところは) ディスクリプタ or デスクリプタ?

  3. 発表内容について Python 公式ドキュメントに載ってる内容 のまとめ+αです ドキュメントを読みましょう ソースコードを読みましょう

  4. Python 公式ドキュメント http://docs.python.jp/3/index.html 翻訳やってます 全然手が足りてません(><) 協力者募集

  5. None
  6. ということで、

  7. お前、誰よ(´・ω・`)

  8. お前、誰よ @knzm2011

  9. 仕事 • Web 開発とか • サーバインフラとか • 自然言語処理とか • 機械学習とか

  10. ここから 本題

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

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

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

    ◦ __get__ だけを定義している場合 ◦ 典型的にはメソッドで使われる • 読み取り専用のデータディスクリプタ ◦ データディスクリプタの中で、 __set__ が呼び出されたときに AttributeError が送出されるもの
  14. 属性アクセスの優先順位 1. データディスクリプタ 2. インスタンスの属性辞書 3. 非データディスクリプタ

  15. プロパティとなにが違うの? • 用途の違い ◦ プロパティ: 通常はデコレータとして、クラス定義の中でそのクラスのインス タンスの属性アクセスをカスタマイズするために使う ◦ ディスクリプタ: 特定のクラスとは独立に定義されて、他のクラスの属性アク

    セスをカスタマイズするために使う • プロパティはディスクリプタの一種 ◦ ディスクリプタの方が応用範囲が広い ◦ プロパティはディスクリプタの典型的な使われ方に特化したもの
  16. クイズ X.Y と書いたとき、 この裏で何が起きているか 知ってますか? ヒント • X がクラスなのかインスタンスなのか •

    Y がプロパティやメソッドだったら
  17. インスタンス属性 class C(object): def __init__(self): self.x = 1 obj =

    C() assert obj.x == obj.__dict__['x']
  18. クラス属性 class C(object): x = 1 assert C.x == C.__dict__['x']

    obj = C() assert obj.x == C.__dict__['x']
  19. プロパティ 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
  20. メソッド 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
  21. メソッド(続き) >>> 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>
  22. __getattribute__ との関係 • 用途の違い ◦ __getattribute__ をオーバーライドすると、あるクラスのすべての属性アク セスをカスタマイズできる ◦ ディスクリプタを使うと、特定の属性アクセスをカスタマイズできる

    • 組み込み型の __getattribute__ の実装の中でディスクリプ タを扱っている
  23. __getattribute__ を実装するクラス • object (オブジェクト) • type (クラスオブジェクト) • super

  24. 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 */ /* ... */ };
  25. 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 クラスの属性辞書参照 インスタンス自身の属性辞書参照
  26. 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 */ /* ... */ };
  27. 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 自分と親クラスの属性辞書参照
  28. • ディスクリプタは常にクラスの属性から取得されて、 クラスを伴って呼ばれる • データディスクリプタはインスタンスの属性を隠す class C(object): x = property(fget=lambda

    self: 0) >>> o = C() >>> o.__dict__['x'] = 1 >>> o.x 0 注意点
  29. ディスクリプタの具体例

  30. プロパティの疑似コード 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
  31. staticmethod の疑似コード class StaticMethod(object): "Emulate PyStaticMethod_Type() in Objects/funcobject.c" def __init__(self,

    f): self.f = f def __get__(self, obj, klass=None): return self.f
  32. 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)
  33. MethodType.__call__ の疑似コード def method_call(meth, *args, **kw): "Emulate method_call() in Objects/classobject.c"

    self = meth.__self__ func = meth.__func__ return func(self, *args, **kw)
  34. インスタンスメソッドの実体は関数 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
  35. >>> 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 関数はディスクリプタ
  36. インスタンスメソッドの疑似コード class Function(object): "Emulate PyFunction_Type() in Objects/funcobject.c" def __get__(self, obj,

    klass=None): if obj is None: return self return types.MethodType(self, obj)
  37. メソッド呼び出しの等価変換 obj.func(x) ↓ func = type(obj).__dict__['func'] meth = func.__get__(obj, type(obj))

    meth.__call__(x) ↓ func(obj, x)
  38. 答え:インスタンスメソッドの呼び出しが ディスクリプタの働きによって最終的に 単なる関数呼び出しに変換されるから メソッドの第1引数がなぜ self なのか

  39. 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 は存在しない
  40. 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)
  41. 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 から、そのクラ スの「次」のクラスを検索している
  42. 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() という仕様と関係?
  43. ユーザ定義の例: 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 は非データディスクリプタだから )
  44. 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
  45. まとめ • ディスクリプタは、属性アクセスをカスタマイズするためのプ ロトコル • データディスクリプタと非データディスクリプタで優先順位が 異なる • プロパティやメソッドなどで使われている他、オリジナルの ディスクリプタを定義することもできる

  46. None
  47. 質問 ある?

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

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

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