Pythonディスクリプタ解析
7744 ワード
構文の概要
一般に、ディスクリプタ(descriptor)は、ディスクリプタプロトコルメソッドによって書き換えられる「バインド動作」を持つオブジェクト属性(object attribute)である.これらの方法は、
ディスクリプタは本質的にクラスオブジェクトであり、ディスクリプタプロトコルの3つの方法のうち少なくとも1つを定義する.この3つの方法は、クラスのインスタンスが1つの所有者クラス(owner class)内に存在する場合にのみ有効であり、すなわち、記述器は所有者クラスまたはその親クラスの辞書
ディスクリプタは装飾器として使用されることが多く、両者が混同されることが多い.ディスクリプタクラスは、パラメータを持たないディスクリプタクラスと同様に、関数オブジェクトにパラメータとして渡され、ディスクリプタクラスがcallableのインスタンスを返し、ディスクリプタがディスクリプタインスタンスを返します.
上の言葉を覚えて、次に例を挙げて説明します.
@Property
Pythonに内蔵されている
ソースコードと使用法を組み合わせて
関数
新しいディスクリプタは
一般的な属性アクセスとは異なり、
では、ディスクリプタはどのようにアクセスされているのでしょうか.
呼び出しディスクリプタ
2種類の記述器がある:
前述したように、記述器は、所有者クラスまたはそのインスタンスで呼び出されることが多い.
インスタンスオブジェクトの場合、
クラスの場合、
覚えておきたいことはいくつかあります.ディスクリプタは、 .
したがって、は、リロード である. のみに存在する. .資料記述器は、常に実例辞書をカバーする、すなわち、資料記述器具には最高優先度 がある.非資料記述器は、実例辞書によって覆われる可能性がある、すなわち、非資料記述器は、最低優先度 を有する.
非資料記述器とクラスメソッド
Pythonのオブジェクト向けの特徴は,関数ベースの環境に基づいている.Pythonは非資料記述器で両者をシームレスに結合した.
メソッドと一般関数の唯一の違いは、一般メソッドの最初のパラメータが現在のインスタンス、すなわち通常
Pythonの関数は、
関数が属性としてアクセスされると、非資料記述器は関数を方法に変え、インスタンス呼び出し
バインドと変換の詳細については、次の表を参照してください.
へんかん
オブジェクトから呼び出す
クラスから呼び出す
関数#カンスウ#
f(obj, *args)
f(*args)
スタティツクメソッド
f(*args)
f(*args)
クラスメソッド
f(type(obj), *args)
f(klass, *args)
静的メソッドは特殊なメソッドであり、インスタンス化することなくクラスで直接呼び出されることができ、この場合、合法的な
クラスメソッドは、現在のインスタンス
参考資料 Descriptor HowTo Guideとその中国語翻訳
一般に、ディスクリプタ(descriptor)は、ディスクリプタプロトコルメソッドによって書き換えられる「バインド動作」を持つオブジェクト属性(object attribute)である.これらの方法は、
__get__()
、__set__()
、および__delete__()
である.オブジェクトが上記のいずれかのメソッドを定義している場合は、ディスクリプタです.ディスクリプタプロトコルの具体的な形式は次のとおりです.descr.__get__(self, obj, type=None) --> value
descr.__set__(self, obj, value) --> None
descr.__delete__(self, obj) --> None
ディスクリプタは本質的にクラスオブジェクトであり、ディスクリプタプロトコルの3つの方法のうち少なくとも1つを定義する.この3つの方法は、クラスのインスタンスが1つの所有者クラス(owner class)内に存在する場合にのみ有効であり、すなわち、記述器は所有者クラスまたはその親クラスの辞書
__dict__
に存在しなければならない.ここでは、ディスクリプタプロトコルを定義するディスクリプタクラスと、ディスクリプタを使用する所有者クラスの2つのクラスについて説明します.ディスクリプタは装飾器として使用されることが多く、両者が混同されることが多い.ディスクリプタクラスは、パラメータを持たないディスクリプタクラスと同様に、関数オブジェクトにパラメータとして渡され、ディスクリプタクラスがcallableのインスタンスを返し、ディスクリプタがディスクリプタインスタンスを返します.
上の言葉を覚えて、次に例を挙げて説明します.
@Property
Pythonに内蔵されている
property
関数は最も有名な記述器の一つと言え、ほとんどの記述器の文章がそれを例にしています.property
はCで実現されていますが、ここには等価なPythonが実現されています.class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
Property
はどうやって使いますか?次の例を見てください.class C(object):
def __init__(self):
self._x = None
@Property
def x(self):
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
assert value > 0
self._x = value
@x.deleter
def x(self):
del self._x
ソースコードと使用法を組み合わせて
Property
を解析した.@Property
の使い方は装飾器です.等価性を次のように変換できます.x = Property(x)
関数
x
は、位置パラメータとしてProperty.__init__()
のfget
に与えられ、新しいx
が関数ではなく、__get__()
法を完全に実現した記述器の例を得た.@x.setter
の使い方は少し違います.実際には、上述したディスクリプタインスタンスx
のsetter
メソッドを用いて、新しいインスタンスを再作成する.このとき、変数x
は再び更新され、__get__()
および__set__()
の方法を完全に実装する新しい記述器を指す.setter
メソッドに伝達される関数名はx
でなければならない.そうでなければy
であれば、装飾器の性質に従って、y = x.setter(y)
新しいディスクリプタは
y
によって参照され、需要と一致しない.Property
は、クラス「メンバー変数」にアクセスするようにget、setメソッドにアクセスする能力を提供する.In [123]: c = C()
In [124]: c.x = 1
In [125]: c.x
Out[125]: 1
In [126]: c.x = 0
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
in ()
----> 1 c.x = 0
in __set__(self, obj, value)
20 if self.fset is None:
21 raise AttributeError("can't set attribute")
---> 22 self.fset(obj, value)
23
24 def __delete__(self, obj):
in x(self, value)
10 @x.setter
11 def x(self, value):
---> 12 assert value > 0
13 self._x = value
14
AssertionError:
一般的な属性アクセスとは異なり、
c.x
がアクセスするのは単純な属性ではなく、x.__get__(c)
に相当し、様々な複雑な方法を呼び出して属性を検査し、包装することができる.では、ディスクリプタはどのようにアクセスされているのでしょうか.
呼び出しディスクリプタ
2種類の記述器がある:
__get__()
と__set__()
の方法を同時に定義した記述器を資料記述器(data descriptor)と呼び、__get__()
のみを定義した記述器を非資料記述器(non-data descriptor)と呼ぶ.非資料記述器は、一般的なstaticmethod
およびclassmethod
のようなクラスの方法によく用いられる.前述したように、記述器は、所有者クラスまたはそのインスタンスで呼び出されることが多い.
インスタンスオブジェクトの場合、
object.__getattribute__()
はc.x
をtype(c).__dict__['x'].__get__(c, type(c))
に変換します.インスタンスにディスクリプタと重複するプロパティx
がある場合はどうしますか?資料記述器と非資料記述器の違いは、インスタンス辞書に対する優先度が異なることである.ディスクリプタとインスタンス辞書の属性が重複すると、アクセス優先度によって、資料ディスクリプタ>同名インスタンス辞書の属性>非資料ディスクリプタが優先度が小さい場合に大きく上書きされます.上記のクラスC
では、資料記述器x
に優先的にアクセスする.次に、クラスの方法は、実際には__get__()
のみを実装した非資料記述器であるため、インスタンスc
にfoo
という方法と属性が同時に定義されている場合、c.foo
は方法ではなく属性にアクセスする.クラスの場合、
type.__getattribute__()
はC.x
をC.__dict__['x'].__get__(None, C)
に変換する.覚えておきたいことはいくつかあります.
__getattribute__()
メソッドによって呼び出されるしたがって、
__getattribute__()
がディスクリプタの自動呼び出しを妨げる可能性がある__getattribute__()
は、object
から継承する新式クラスのうちobject.__getattribute__()
とtype.__getattribute__()
の__get__()
に対する呼び出しが異なる非資料記述器とクラスメソッド
Pythonのオブジェクト向けの特徴は,関数ベースの環境に基づいている.Pythonは非資料記述器で両者をシームレスに結合した.
メソッドと一般関数の唯一の違いは、一般メソッドの最初のパラメータが現在のインスタンス、すなわち通常
self
と命名された変数を参照することである.Pythonの関数は、
__get__()
を実現した非資料記述器と考えられ、Pythonで記述すると、class Function(object):
. . .
def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
return types.MethodType(self, obj, objtype)
関数が属性としてアクセスされると、非資料記述器は関数を方法に変え、インスタンス呼び出し
obj.f(*args)
をf(obj, *args)
に変換し、クラス呼び出しklass.f(*args)
をf(*args)
に変換する.バインドと変換の詳細については、次の表を参照してください.
へんかん
オブジェクトから呼び出す
クラスから呼び出す
関数#カンスウ#
f(obj, *args)
f(*args)
スタティツクメソッド
f(*args)
f(*args)
クラスメソッド
f(type(obj), *args)
f(klass, *args)
静的メソッドは特殊なメソッドであり、インスタンス化することなくクラスで直接呼び出されることができ、この場合、合法的な
self
を提供することは当然できない.そのためには、staticmethod
記述器が必要であり、その__get__()
が返す関数はインスタンスパラメータを必要とせず、実際にはそのまま返すことができ、Pythonでこのように実現することができる.class StaticMethod(object):
"Emulate PyStaticMethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f
def __get__(self, obj, objtype=None):
return self.f
クラスメソッドは、現在のインスタンス
self
を必要としない別の特殊な方法であるが、現在のクラスklass
(通常はcls
とも書く)が必要であり、純粋なPythonは以下のように実現される.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)
def newfunc(*args):
return self.f(klass, *args)
return newfunc
参考資料