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の動作メカニズムをより深く理解し、優雅な設計理念に感嘆するのに役立ちます.
定義と紹介
一般的に、記述子は、記述子プロトコルで定義されたメソッドによって書き換えられた、ある動作にバインドされたオブジェクト属性である.これらの方法は、__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.cPyObject_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つの最も有用な変形についてまとめています.
    Python Descriptors 指南_第1张图片
    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