Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

ということで、

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

お前、誰よ @knzm2011

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

ここから 本題

Slide 11

Slide 11 text

ディスクリプタとは ● プロトコルの一種 (e.g. イテレータプロトコル) ● 属性アクセスをカスタマイズする機構 ● Python の基本的な機能の背後にある仕組み ○ プロパティ ○ メソッド ■ 静的メソッド、クラスメソッド、 ■ インスタンスメソッド ○ super ● ユーザ定義することもできる

Slide 12

Slide 12 text

どんなプロトコルか class Descriptor(object): def __get__(self, obj, type=None): pass def __set__(self, obj, value): pass def __delete__(self, obj): pass これらのメソッドを定義したオブジェクトは ディスクリプタになる

Slide 13

Slide 13 text

分類 ● データディスクリプタ ○ __get__ と __set__ の両方を定義している場合 ● 非データディスクリプタ ○ __get__ だけを定義している場合 ○ 典型的にはメソッドで使われる ● 読み取り専用のデータディスクリプタ ○ データディスクリプタの中で、 __set__ が呼び出されたときに AttributeError が送出されるもの

Slide 14

Slide 14 text

属性アクセスの優先順位 1. データディスクリプタ 2. インスタンスの属性辞書 3. 非データディスクリプタ

Slide 15

Slide 15 text

プロパティとなにが違うの? ● 用途の違い ○ プロパティ: 通常はデコレータとして、クラス定義の中でそのクラスのインス タンスの属性アクセスをカスタマイズするために使う ○ ディスクリプタ: 特定のクラスとは独立に定義されて、他のクラスの属性アク セスをカスタマイズするために使う ● プロパティはディスクリプタの一種 ○ ディスクリプタの方が応用範囲が広い ○ プロパティはディスクリプタの典型的な使われ方に特化したもの

Slide 16

Slide 16 text

クイズ X.Y と書いたとき、 この裏で何が起きているか 知ってますか? ヒント ● X がクラスなのかインスタンスなのか ● Y がプロパティやメソッドだったら

Slide 17

Slide 17 text

インスタンス属性 class C(object): def __init__(self): self.x = 1 obj = C() assert obj.x == obj.__dict__['x']

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

プロパティ 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

Slide 20

Slide 20 text

メソッド 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

Slide 21

Slide 21 text

メソッド(続き) >>> obj.x > Python 2 の場合 >>> C.x Python 3 の場合 >>> C.x

Slide 22

Slide 22 text

__getattribute__ との関係 ● 用途の違い ○ __getattribute__ をオーバーライドすると、あるクラスのすべての属性アク セスをカスタマイズできる ○ ディスクリプタを使うと、特定の属性アクセスをカスタマイズできる ● 組み込み型の __getattribute__ の実装の中でディスクリプ タを扱っている

Slide 23

Slide 23 text

__getattribute__ を実装するクラス ● object (オブジェクト) ● type (クラスオブジェクト) ● super

Slide 24

Slide 24 text

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 */ /* ... */ };

Slide 25

Slide 25 text

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 クラスの属性辞書参照 インスタンス自身の属性辞書参照

Slide 26

Slide 26 text

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 */ /* ... */ };

Slide 27

Slide 27 text

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 自分と親クラスの属性辞書参照

Slide 28

Slide 28 text

● ディスクリプタは常にクラスの属性から取得されて、 クラスを伴って呼ばれる ● データディスクリプタはインスタンスの属性を隠す class C(object): x = property(fget=lambda self: 0) >>> o = C() >>> o.__dict__['x'] = 1 >>> o.x 0 注意点

Slide 29

Slide 29 text

ディスクリプタの具体例

Slide 30

Slide 30 text

プロパティの疑似コード 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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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)

Slide 33

Slide 33 text

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)

Slide 34

Slide 34 text

インスタンスメソッドの実体は関数 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

Slide 35

Slide 35 text

>>> def f(x, y): return x + y ... >>> f >>> f.__get__(1) >>> f.__get__(1)(2) 3 関数はディスクリプタ

Slide 36

Slide 36 text

インスタンスメソッドの疑似コード 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)

Slide 37

Slide 37 text

メソッド呼び出しの等価変換 obj.func(x) ↓ func = type(obj).__dict__['func'] meth = func.__get__(obj, type(obj)) meth.__call__(x) ↓ func(obj, x)

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

Python 2 と 3 の実装の違い class C(object): def f(self): pass $ python3 >>> C.f # == C.__dict__['f'] $ python2 >>> C.f # != C.__dict__['f'] >>> C.f.__func__ # == C.__dict__['f'] Python 3 には unbound method は存在しない

Slide 40

Slide 40 text

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)

Slide 41

Slide 41 text

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 から、そのクラ スの「次」のクラスを検索している

Slide 42

Slide 42 text

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() という仕様と関係?

Slide 43

Slide 43 text

ユーザ定義の例: 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 は非データディスクリプタだから )

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

まとめ ● ディスクリプタは、属性アクセスをカスタマイズするためのプ ロトコル ● データディスクリプタと非データディスクリプタで優先順位が 異なる ● プロパティやメソッドなどで使われている他、オリジナルの ディスクリプタを定義することもできる

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

質問 ある?

Slide 48

Slide 48 text

FAQ: 何に使えるの? 属性アクセス (X.Y) の形で書けることならなんでも ● O/Rマッパー ● クラス経由とインスタンス経由で異なる値を返したい場合 ● プロパティ+特定パターンの置き換え ● 通常とは異なるデータアクセス方法が必要な場合 ● DSL (?)

Slide 49

Slide 49 text

FAQ: ディスクリプタの利用例 ● InstrumentedAttribute, query_property, echo_property, AssociationProxy (SQLAlchemy) ● classinstancemethod (Paste, FormEncode) ● singletonmethod (FormEncode) ● environ_getter (Paste) ● RequestLocalDescriptor (ToscaWidgets)