Python魔法の方法(五)から_get__,__set__, __delete__再プローブ属性アクセス順序

4560 ワード

ここではまず記述子の概念を述べる必要がある.
記述子:記述子の本質は新しいクラスであり、この新しいクラスでは、少なくとも__が実現される.get__(),__set__(),__delete__()の1つであり、これは記述子プロトコルとも呼ばれる.
記述子はデータ記述子に分けられ、__のみget__の記述子は非データ記述子であり、get__および_set__の記述子はデータ記述子です.
 
__get__(self,instance,owner)-プロパティを取得するときに呼び出され、設定されたプロパティ値が返されます.通常は_set_に表示されます. __set__(self,instance,value)-プロパティを設定するときに呼び出され、Noneに戻ります. __delete__(self,instance)-属性を削除するときに呼び出され、Noneに戻ります.
ここでinstanceは、この記述子属性が存在するクラスのインスタンスであり、ownerは記述子が存在するクラスである.
次の例を見てください
class A(object):
    name = "unchange"

    def  __init__(self, value):
        print "into A __init__"
        self.value = value

    def __get__(self, instance, owner):
        print "into __get__"
        print instance,owner

class B(object):
    value = A(10)

    def __init__(self, value):
        print "into B __init__"

b = B(20)
# into A __init__
# into B __init__
print b.value
# into __get__
# <__main__.b object="" at=""> 
# None

つまり、instanceとownerはクラスBのものです.
 
前編では、データ記述子がない場合、インスタンスアクセス属性の順序は次のようになります.
インスタンス属性a.nameにアクセスすると、そのアクセス順は、(データ記述子がないと仮定する)
先に入る_getattribute__
1、インスタンス属性a_dict__2、クラス属性A._dict__ 3、親およびベース属性A._bases__.__dict
検索できないgetattr__
 
データ記述子、または非データ記述子がある場合、アクセス順は次のようになります.
先に入る_getattribute__
1.属性がデータ記述子である場合get__2、インスタンス属性3、非データ記述子
検索できないgetattr__
 
class A(object):

    def  __init__(self, name):
        print "into A __init__"
        self.name = name

    def __get__(self, instance, owner):
        print "into __get__"

    def __set__(self, instance, value):
        print "into __set__"


class B(object):
    name = A("Tom") #           ,      “Bob”

    def __init__(self, name, age):
        print "into B __init__"
        self.age = age
        self.name = name

    def __getattribute__(self, item):
        print "into __getattribute__"
        return object.__getattribute__(self, item)

b = B("Bob", 100)
# into A __init__
# into B __init__
# into __set__
print b.name
# into __getattribute__
# into __get__
# None

データ記述子があり、依然として__に入ります.getattribute__,そして__へget__.データ記述子がない場合は、インスタンスプロパティに直接アクセスします.
理由:初期化_init__開始前から、データ記述子name、すなわちA(「Tom」)のインスタンス化が開始される.
Bのインスタンス化に入るとself.nameは記述子の付与操作が__に入るため、付与された.set__(ここでは実際には__set__は印刷操作のみを行い、instanceに値を付けていない).
インスタンスbの_dict__にnameという属性はなく,もちろんインスタンスbで所望の属性を得ることはできない.
 
Aの中の_をset__メソッドを書かない場合、nameは非データ記述子です.
たとえば
class A(object):

    def  __init__(self, name):
        print "into A __init__"
        self.name = name

    def __get__(self, instance, owner):
        print "into __get__"



class B(object):
    name = A("Tom") #           ,      “Bob”

    def __init__(self, name, age):
        print "into B __init__"
        self.age = age
        self.name = name

    def __getattribute__(self, item):
        print "into __getattribute__"
        return object.__getattribute__(self, item)

b = B("Bob", 100)
# into A __init__
# into B __init__
# into __set__
print b.name
# into __getattribute__
# Bob

非データ記述子の優先度はインスタンス属性より低い.
 
関連用法:1、属性アクセス、修正制御
class A(object):

    def  __init__(self, name):
        print "into A __init__"
        self.name = name

    def __get__(self, instance, owner):
        print "into __get__"
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print "into __set__"
        if value < 0:
            raise ValueError
        instance.__dict__[self.name] = value

class B(object):
    name = A("name")
    age = A("age")

    def __init__(self, name, age):
        print "into B __init__"
        self.age = age
        self.name = name

    def __getattribute__(self, item):
        print "into __getattribute__"
        return object.__getattribute__(self, item)

b = B("Bob", 20)
b.age = -1
print b.age

で_set__割り当て操作をブロックすると、不合理な値が間違って投げられます._delete__の使い方と_set__同様に、削除時に削除操作をブロックします.
データ記述子は@propertyと同様にプロパティアクセス制御の効果がありますが、データ記述子はより多くのプロパティ制御に使用でき、コードの煩雑さはありません.
 
注意すべき点:
1、書き換え_get__,__set__,__delete__デッドサイクルに入るのを避ける.
2、記述子が_getattribute()メソッド呼び出し、リロード_getattribute__()が適切でないと、記述子の自動呼び出しがブロックされます.
3、データ記述子はインスタンス辞書をリロードし、非データ記述子はインスタンス辞書にリロードされる可能性がある(属性アクセス順の理由).