Python Descriptorsガイド
17028 ワード
原文住所:http://users.rcn.com/python/download/Descriptor.htm
約定descriptor:記述子data descriptor:データ記述子non-data descriptor:非データ記述子object attribute:オブジェクト属性(class instanceとclass objectを含む)attribute access:属性アクセスmethod:メソッドfunction:関数
サマリ
記述子の定義と記述子プロトコルについて述べ,記述子の呼び出し方法を示した.カスタム記述子と、functions、properties、static methods、class methodsなど、Pythonに組み込まれた記述子を例に挙げて説明します.純粋なPythonコードで符号化することで,それらの内部の実際の動作を示す.
学習記述子は、より大きなPythonツールセットに触れるだけでなく、Pythonの動作メカニズムをより深く理解し、優雅な設計理念に感嘆するのに役立ちます.
定義と紹介
一般的に、記述子は、記述子プロトコルで定義されたメソッドによって書き換えられた、ある動作にバインドされたオブジェクト属性である.これらの方法は、
Pythonの1つのオブジェクトの属性アクセス方式はデフォルトでget,set,deleteの3種類があり、オブジェクトの属性辞書でアクセスします.例えば、
ディスクリプタプロトコルは強力で汎用的なプロトコルです.Pythonのproperties,methods,static methods,class methods,super()の下位層の動作メカニズムは記述子プロトコルに関係している.記述子はPython自身に広く用いられ,新式クラス(Python 2.2が開始した特性)を実現する.ディスクリプタは最下位のCコードを簡略化し、私たちが普段書いているPythonコードに新しい、柔軟なツールセットを提供します.
ディスクリプタプロトコル
以上がそのすべての方法です.オブジェクトがメソッドを定義している場合、オブジェクトは記述子と呼ばれ、オブジェクトが検索されるとオブジェクト呼び出しのデフォルトの動作が書き換えられます.
オブジェクトが
データおよび非データ記述子オブジェクトが呼び出される方法は、インスタンスオブジェクトのプロパティ辞書のプロパティの検索方法に依存します.インスタンス・オブジェクトのプロパティ・ディクショナリにレコード名とデータ・ディスクリプタ名が同じ場合、データ・ディスクリプタが先に検出されます.インスタンス・オブジェクトのプロパティ・ディクショナリにレコード名と非データ・ディスクリプタ名が同じ場合、プロパティ・ディクショナリのレコードが先に検索されます.
読み取り専用のデータ記述子を作成するには、
呼び出し記述子
1つの記述子は、例えば、
objectsの場合、変換機構は
classesの場合、変換機構は
いくつかの重要な点があります記述子 書き換え を阻止する. が存在する. .データ記述子の優先度はインスタンス変数より高く、インスタンス変数の優先度は非データ記述子 より高い.
super()メソッドが返すオブジェクトにも、カスタム
以上説明した記述子メカニズムはobject,type,superに埋め込まれた
記述子の例
次のコードは、xプロパティが記述子であるMyClassクラスを作成します.mオブジェクトの属性に対してm.xを呼び出すことによって記述子の呼び出し方法を示す.
以上の例から、記述子プロトコルは比較的簡単であり、いくつかの一般的な例は、いくつかの個別の関数呼び出しにパッケージ化されていることがわかる.Properties,bound and unbound methods,static methodsおよびclass methods実装は記述子プロトコルに基づいている
Properties
Functions and Methods
Pythonのオブジェクト向け特性は関数に基づいて構築され,非データ記述子を用いて両者をシームレスに結合できる.
クラスオブジェクトはメソッドを関数として自分の辞書に格納します.classの定義では、メソッドはdefまたはlambdaで定義され、関数を定義する方法と同様であり、メソッドの最初のパラメータがインスタンスオブジェクトのために保持されるのは唯一異なる.Pythonの符号化スタイルでは、インスタンスの参照をselfと呼ぶが、thisまたは他の変数名と名付けてもよい.
メソッド呼び出しをサポートするために、関数には
インタラクション解釈器における関数記述子が実際にどのように動作するかを以下に示す.
出力表示バインディングメソッドオブジェクトと非バインディングメソッドオブジェクトは2つの異なるタイプであり、実際の
同様に、メソッドオブジェクトを呼び出すとim_が判断されます.selfフィールドが設定されているかどうかは、設定がバインドの方法である場合、元の関数(im_funcフィールドに存在する)が呼び出され、メソッドの最初のパラメータがインスタンスオブジェクトに設定されます.バインドされていない場合、すべての実パラメータが関数にそのまま渡されます.実際のCコード実装
Static Methods and Class Methods
非データ記述子は、関数をバインドする方法を提供する簡単なメカニズムを提供する.さらに,関数には
次の表は、メソッドのバインドと2つの最も有用な変形についてまとめています.
c.fまたはC.fが実行される場合、静的方法の属性アクセス方式は
静的方法は適当ですか?類の方法はすることに適しますか?
クラスメソッドの属性アクセスPythonの実現方式は以下の通りである.
約定descriptor:記述子data descriptor:データ記述子non-data descriptor:非データ記述子object attribute:オブジェクト属性(class instanceとclass objectを含む)attribute access:属性アクセスmethod:メソッドfunction:関数
サマリ
記述子の定義と記述子プロトコルについて述べ,記述子の呼び出し方法を示した.カスタム記述子と、functions、properties、static methods、class methodsなど、Pythonに組み込まれた記述子を例に挙げて説明します.純粋なPythonコードで符号化することで,それらの内部の実際の動作を示す.
学習記述子は、より大きなPythonツールセットに触れるだけでなく、Pythonの動作メカニズムをより深く理解し、優雅な設計理念に感嘆するのに役立ちます.
定義と紹介
一般的に、記述子は、記述子プロトコルで定義されたメソッドによって書き換えられた、ある動作にバインドされたオブジェクト属性である.これらの方法は、
__get__
、__set__
、および__delete__
を含み、オブジェクトがいずれかの方法を定義している場合、このオブジェクトを記述子と呼ぶことができる.Pythonの1つのオブジェクトの属性アクセス方式はデフォルトでget,set,deleteの3種類があり、オブジェクトの属性辞書でアクセスします.例えば、
a.x
のルックアップチェーンは、metaclassesを除いてa.__dict__['x']
、次いでtype(a).__dict__['x']
、次いでtype(a)
のベースクラスの属性から始まる.検索された値が記述子プロトコルで説明されたメソッドを定義するオブジェクトである場合、Pythonは属性検索のデフォルトの動作を書き換え、記述子メソッドを呼び出す可能性があります.特定の呼び出しプロセスについては、オブジェクトがどの記述子メソッドを定義しているかによって異なります.記述子プロパティはnew style objects/classes(objectまたはtypeから継承された新しいクラス)にのみ適用されることに注意してください.ディスクリプタプロトコルは強力で汎用的なプロトコルです.Pythonのproperties,methods,static methods,class methods,super()の下位層の動作メカニズムは記述子プロトコルに関係している.記述子はPython自身に広く用いられ,新式クラス(Python 2.2が開始した特性)を実現する.ディスクリプタは最下位のCコードを簡略化し、私たちが普段書いているPythonコードに新しい、柔軟なツールセットを提供します.
ディスクリプタプロトコル
descr.__get__(self, obj, type=None) --> value
descr.__set__(self, obj, value) --> None
descr.__delete__(self, obj) --> None
以上がそのすべての方法です.オブジェクトがメソッドを定義している場合、オブジェクトは記述子と呼ばれ、オブジェクトが検索されるとオブジェクト呼び出しのデフォルトの動作が書き換えられます.
オブジェクトが
__get__
メソッドと__set__
メソッドを定義している場合、それはデータ記述子です.__get__
メソッドのみが非データ記述子である場合(通常はメソッドオブジェクトに使用され、他のオブジェクトにも使用されます).データおよび非データ記述子オブジェクトが呼び出される方法は、インスタンスオブジェクトのプロパティ辞書のプロパティの検索方法に依存します.インスタンス・オブジェクトのプロパティ・ディクショナリにレコード名とデータ・ディスクリプタ名が同じ場合、データ・ディスクリプタが先に検出されます.インスタンス・オブジェクトのプロパティ・ディクショナリにレコード名と非データ・ディスクリプタ名が同じ場合、プロパティ・ディクショナリのレコードが先に検索されます.
読み取り専用のデータ記述子を作成するには、
__get__
および__set__
メソッドを定義し、__set__
メソッドでAttributeError
例外を投げ出す必要があります.これで十分です.呼び出し記述子
1つの記述子は、例えば、
d.__get__(obj)
のような独自の方法で直接呼び出すことができるが、より一般的には、属性アクセスによって自動的に呼び出される.たとえば、obj.d
はobjの辞書でdを探し、dが__get__
を定義するとd.__get__(obj)
が呼び出されます.具体的な呼び出しプロセスはobjがobjectであるかclassであるかによって異なりますが、いずれにしても記述子は新しいクラス(objects and classes)でのみ有効です.objectsの場合、変換機構は
object.__getattribute__
法において、b.xをtype(b).__dict__['x'].__get__(b, type(b))
に変換する.Python内部で実装されるルックアップチェーンでは、データ記述子の優先度はインスタンス変数の優先度よりも高く、インスタンス変数の優先度は非データ記述子の優先度よりも高く、__getattr__
メソッドの優先度は最も低い(このメソッドが定義されている場合).具体的なCコードはObjects/object.c
のPyObject_GenericGetAttr()
関数に実装されるclassesの場合、変換機構は
type.__getattribute__
法において、B.xをB.__dict__['x'].__get__(None, B)
に変換する.以上の変換はPythonコードで次のように表されます.def __getattribute__(self, key):
"Emulate type_getattro() in Objects/typeobject.c"
v = object.__getattribute__(self, key)
if hasattr(v, '__get__'):
return v.__get__(None, self)
return v
いくつかの重要な点があります
__getattribute__
メソッドによって呼び出される__getattribute__
メソッドは、記述子の自動呼び出し__getattribute__
メソッドは、新しいクラス(classesおよびobjects)にのみobject.__getattribute__
とtype.__getattribute__
が__get__
を呼び出す方式は異なるsuper()メソッドが返すオブジェクトにも、カスタム
__getattribute__
メソッド呼び出し記述子があります.super(B, obj).m()
呼び出しは、obj.__class__.__mro__
に従ってBクラスのベースクラスAを検索し、A.__dict__['m'].__get__(obj, A)
を返す.mが記述子でない場合、mはそのまま返されます.mがAの辞書にない場合、mは次の検索方法に変わります:object.__getattribute__
なお、Python 2.2の場合、mがデータ記述子super(B, obj).m()
であれば、__get__
メソッドのみが呼び出される.Python 2.3の場合、非データ記述子でも呼び出されます.以上の実装の詳細は、ソースコード:Objects/typeobject.c
の方法:super_getattro()
において、同様のPython実装はGuido’s Tutorialを参照することができる.以上説明した記述子メカニズムはobject,type,superに埋め込まれた
__getattribute__()
メソッドを実現する記述子の例
次のコードは、xプロパティが記述子であるMyClassクラスを作成します.mオブジェクトの属性に対してm.xを呼び出すことによって記述子の呼び出し方法を示す.
class RevealAccess(object):
"""A data descriptor that sets and returns values
normally and prints a message logging their access.
"""
def __init__(self, initval=None, name='var'):
self.val = initval
self.name = name
def __get__(self, obj, objtype):
print 'Retrieving', self.name
return self.val
def __set__(self, obj, val):
print 'Updating' , self.name
self.val = val
>>> class MyClass(object):
x = RevealAccess(10, 'var "x"')
y = 5
>>> m = MyClass()
>>> m.x
Retrieving var "x"
10
>>> m.x = 20
Updating var "x"
>>> m.x
Retrieving var "x"
20
>>> m.y
5
以上の例から、記述子プロトコルは比較的簡単であり、いくつかの一般的な例は、いくつかの個別の関数呼び出しにパッケージ化されていることがわかる.Properties,bound and unbound methods,static methodsおよびclass methods実装は記述子プロトコルに基づいている
Properties
property()
を呼び出す方法は、データ記述子を作成する簡単な方法である.メソッド宣言は次のとおりです.property(fget=None,fset=None,fdel=None,doc=None) -> property attribute
の使用方法は次のとおりです.class C(object):
def getx(self): return self.__x
def setx(self, value): self.__x = value
def delx(self): del self.__x
x = property(getx, setx, delx, "I'm the 'x' property.")
property()
が具体的にどのように実現されているかを知るには、以下の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
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)
property()
メソッドは、属性のセキュリティチェックまたはコードの前方互換性を行うことができる.次のCellクラスでは、最初にCell('b10').value
でvalueプロパティを直接操作でき、その後、いくつかの制限が必要で、元のコードロジックを修正する必要がない場合は、property()
の方法でvalueを包装すればいいです.class Cell(object):
. . .
def getvalue(self, obj):
"Recalculate cell before returning value"
self.recalc()
return obj._value
value = property(getvalue)
Functions and Methods
Pythonのオブジェクト向け特性は関数に基づいて構築され,非データ記述子を用いて両者をシームレスに結合できる.
クラスオブジェクトはメソッドを関数として自分の辞書に格納します.classの定義では、メソッドはdefまたはlambdaで定義され、関数を定義する方法と同様であり、メソッドの最初のパラメータがインスタンスオブジェクトのために保持されるのは唯一異なる.Pythonの符号化スタイルでは、インスタンスの参照をselfと呼ぶが、thisまたは他の変数名と名付けてもよい.
メソッド呼び出しをサポートするために、関数には
__get__
メソッドが含まれており、プロパティアクセス中にメソッドをオブジェクトにバインドします.これは、objectまたはclassによって呼び出されるかどうかに基づいてバインドまたは非バインドメソッドを返すすべての関数が非データ記述子であることを意味する.Pythonで実現するとこう見えます.class Function(object):
. . .
def __get__(self, obj, objtype=None):
"Simulate func_descr_get() in Objects/funcobject.c"
return types.MethodType(self, obj, objtype)
インタラクション解釈器における関数記述子が実際にどのように動作するかを以下に示す.
>>> class D(object):
def f(self, x):
return x
>>> d = D()
>>> D.__dict__['f'] # Stored internally as a function
0x00C45070>
>>> D.f # Get from a class becomes an unbound method
>>> d.f # Get from an instance becomes a bound method
0x00B18C90>>
出力表示バインディングメソッドオブジェクトと非バインディングメソッドオブジェクトは2つの異なるタイプであり、実際の
PyMethod_Type
Cコード実装Objects/classobject.c
はim_selfこのフィールドは、特定のタイプを区別するためにNULLに設定されているか、NULLに設定されています.同様に、メソッドオブジェクトを呼び出すとim_が判断されます.selfフィールドが設定されているかどうかは、設定がバインドの方法である場合、元の関数(im_funcフィールドに存在する)が呼び出され、メソッドの最初のパラメータがインスタンスオブジェクトに設定されます.バインドされていない場合、すべての実パラメータが関数にそのまま渡されます.実際のCコード実装
instancemethod_call()
は、元の関数にいくつかのタイプのチェックを加えただけである.Static Methods and Class Methods
非データ記述子は、関数をバインドする方法を提供する簡単なメカニズムを提供する.さらに,関数には
__get__
メソッドが必要であり,属性アクセスがある場合にメソッドに変換できることを思い出す.非データ記述子は、obj.f(*args)
をf(obj, *args)
に変換し、klass.f(*args)
、すなわちf(*args)
を呼び出す.次の表は、メソッドのバインドと2つの最も有用な変形についてまとめています.
c.fまたはC.fが実行される場合、静的方法の属性アクセス方式は
object.__getattribute__(c, "f")
またはobject.__getattribute__(C, "f")
である.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
静的方法は適当ですか?類の方法はすることに適しますか?
クラスメソッドの属性アクセス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