天天看點

Python 方法調用流程詳解

python 新式類引進了内建屬性__getattribute__,通路屬性都要先通過内建屬性__getattribute__,接下來談談類和執行個體分别通路屬性的過程

class DevNull:
    def __init__(self,initval = None,name='var'):
        self.val = initval
        self.name = name

    def __get__(self, instance, owner):
        #self指的是RevealAccess執行個體,instance代表被代理的類的執行個體,owner代表被代理的類
        print("擷取..",self.name)
        return self.val

    def __set__(self, instance, value):
        print("設定值:",self.name)
        self.val = value

class MyClass(object):
    x = DevNull(1000,"var 'x'")
    def foo(self):
        print("hello world")
           
m=MyClass()
m.x
輸出:
擷取.. var 'x'
1000
           

x屬性是資料描述符,DevNull同時擁有 set 和 __get__方法,執行個體通路x, getattribute__會調用資料描述符的__get 方法

m.x=1
m.x
輸出:
設定值: var 'x'
擷取.. var 'x'
1
           

上面m.x=1觸發了資料描述符的__set__ 方法,修改了DevNull中self.val的值。是以__get__傳回的值也發生了變化。

print(m.__dict__)
輸出 {}
           
print(MyClass.x)
MyClass.x=1
print(MyClass.x)

輸出:
擷取.. var 'x'
1000
1
           

上面通過類通路x,第一次通過資料描述符DevNull 的__get__,而第二次通路x,沒有經過__get__ 并且類對x的指派并沒有觸發資料描述符的__set__方法;這說明類對x指派,把之前類中資料描述符屬性x給覆寫了,是以再次通過類通路x,沒有經過__get__.

接下來分析非資料描述符的情況

class DevNull:
    def __init__(self,initval = None,name='var'):
        self.val = initval
        self.name = name

    def __get__(self, instance, owner):
        #self指的是RevealAccess執行個體,instance代表被代理的類的執行個體,owner代表被代理的類
        print("擷取..",self.name)
        return self.val

   # def __set__(self, instance, value):
    #     print("設定值:",self.name)
    #     self.val = value

class MyClass(object):
    x = DevNull(1000,"var 'x'")
    def foo(self):
        print("hello world")
           
m=MyClass()
m.x
輸出:
擷取.. var 'x'
1000
           

可以看出和資料描述符的情況一直

m.x=1
print(m.x)
輸出:
1
           

對于非資料描述符x,執行個體修改x的值後,通路x, 沒有經過__get__,非資料描述符x被執行個體屬性x覆寫了。

print(m.__dict__)
輸出:
{'x': 1}
           

接下來看下類通路非資料描述符x

print(MyClass.x)
MyClass.x=1
print(MyClass.x)

輸出:
擷取.. var 'x'
1000
1
           

與最上面非資料描述符的情況一緻;

通過以上的分析由此得出以下結論:

通過類通路屬性時有無資料描述符差別

通過執行個體通路屬性時,遵循

資料描述符級别>執行個體屬性>非資料描述符