Python之描述器


1.描述器的表現

用到三個魔術方法,__get__(), __set__(), __delete__()
方法簽名如下
object.__get__(self,instance,owner)
object.__set__(self,instance,value)
object.__delete(self,instance)
self指代當前實例,調用者
instance是owner的實例
owner是屬性的所輸的類
#描述器A調用並賦值給了類B的屬性,當調用類B或者實例b時,去類A執行__get__()函數,類調用instance返回none,實例調用返回實例
#執行順序和類,實例字典無關
class A:
    def __init__(self):
        print(2,'A init')
    def __set__(self, instance, value):
        print(3,self,instance,value)
    def __get__(self, instance, owner): #return值,將會影響b.x or B.x的調用屬性
        print(4,self,instance,owner)

class B:
    x = A()
    def __init__(self):
        print(1,'B init')

b = B()  #output 2->1
b.x   #output 4 <__main__.A object at 0x047B09D0> <__main__.B object at 0x047BB350> <class '__main__.B'>
B.x   #output 4 <__main__.A object at 0x047B09D0> None <class '__main__.B'>

此時訪問b.x.a1 B.x.a1都會報錯 AttributeError:Nonetype
問題出在__get__的返回值,修改為 return self 返回A的實例就具備了a1屬性 返回正常

class B:
    x = A()
    def __init__(self):
        self.b1 = A()
        print(1,'B init')

b = B()  #output 2->1
print(b.b1) #output <__main__.A object at 0x03000990> 沒有觸發__get__的打印
從運行結果可以看出,只有類屬性是類的實例才行

2.描述其的定義

python中,一個類實現了__get__,__set__.__delete__的三個方法中的任意一個就是描述器
1.如果僅僅實現了__get__.就是非數據描述器 non-data descriptor
2.同時實現了__get__,__set__,就是數據描述器,data descriptor
如果一個類的類屬性,設置為描述器,那么這個類被稱為owner屬主,method也是類的屬性
class A:
    def __init__(self):
        self.a1 = 'a1'
        print(2,'A init')

    def __get__(self, instance, owner):
        print(4,self,instance,owner)
        return self

class B:
    x = A()
    def __init__(self):
        self.x = 'b1'   #如果描述器定義了__set__,此時b1就是value
        print(1,'B init')

b = B()  #output 2->1
print(B.x) #output 4  <__main__.A object at 0x04EEB350> None <class '__main__.B'> ;;;; return <__main__.A object at 0x02E8B350>
print(B.x.a1) #output 4  <__main__.A object at 0x02E8B350> None <class '__main__.B'> ;;;;return a1
print(b.x)  #return b1 訪問到了實例的屬性,而不是描述器
print(b.x.a1) #AttributeError 'str object has no Attribute
在非數據描述器中,owner實例的屬性 會被實例調用,而不是訪問__get__描述器
#添加了set方法 對比上個代碼,;數據描述器
class A:
    def __init__(self):
        self.a1 = 'a1'
        print(2,'A init')
    def __set__(self, instance, value):  #當定義了set魔術方法后,B實例定義的實例屬性self.x = 'b1 不會在寫進實例字典,而是調用set方法
        print(3,self,instance,value)
        # instance.__dict__['x']=value
    def __get__(self, instance, owner): #return值,將會影響b.x or B.x的調用屬性
        print(4,self,instance,owner)
        # return instance.__dict__['x']
        return self
class B:
    x = A()
    def __init__(self):
        print(1,'B init')
        print("+++++++++++++++++++++++")
        self.x = 'b1'
        print("+++++++++++++++++++++++")

b = B()  #output 2->1->+ ->3-> + ;;實例化時候,self.x = 'b1'調用了set方法
print(b.x.a1)  #return a1 直接調用get
b.x = 100       #return a1 直接調用set
print(b.__dict__)  #實例字典為空
print(B.__dict__)

總結:實例的__dict__優先於非數據描述器;;;數據描述器優先於實例__dict__

2.1描述器查找順序和__dict__的關系

class A:
    def __init__(self):
        self.a1 = 'a1'
        print(2,'A init')
    def __set__(self, instance, value):
        print(3,self,instance,value)
        self.data = value
        print(self.data)
    def __get__(self, instance, owner):
        print(4,self,instance,owner)
        return self
class B:
    x = A()
    def __init__(self):
        print(1,'B init')
        self.x = 'b.x'
        self.y = 'b.y'
        self.z = 'b.z'


b = B()  #output 2->1->+ ->3-> + ;;實例化時候,self.x = 'b1'調用了set方法
print(b.y) #return b.y
print(b.x)
print(B.__dict__)
print(b.__dict__)#output {'y': 'b.y', 'z': 'b.z'}  ;;;self.x 這里的x是x = A()所以調用set方法,而self.y self.z追加到字典

2.3練習

from functools import  partial
class StaticMethod:
    def __init__(self,fn):
        self.fn = fn
    def __get__(self, instance, owner):
        return self.fn
class ClassMethod:
    def __init__(self,fn):
        self.fn = fn
    def __get__(self, instance, owner):
        # return self.fn(instance)
        return partial(self.fn,instance)
class Test:
    @StaticMethod  #st = staticmethod(st) 去調用__get__方法時,不用在考慮這個Test類了 裝飾器到描述器執行,調用時加()就返回值
    def st(): #st = return self.fn   
        print('st')
    @ClassMethod
    def ct(cls):    #ct = return self.fn(instance)
        print('ct')
t = Test()
Test.st()
t.st()
t.ct()
Test.ct()

 

class Typed:
    def __init__(self,name,type):
        self.name = name
        self.type = type

    def __set__(self, instance, value):
        if not  isinstance(value,self.type):
            raise TypeError(value)
        instance.__dict__[self.name]=value

    def __get__(self, instance, owner):
        if instance is  not None:
            return instance.__dict__[self.name]
        return self
class Person:
    name = Typed('name',str)   # 1  演變其他的基礎
    age = Typed('age',int)
    def __init__(self,name:str,age:int):
        self.name = name       # 2
        self.age = age

p = Person('tom',21)
print(p.__dict__)       #output {'name': 'tom', 'age': 21}
print(p.age)            #output 21
print(Person.age.name)  #output age
print(Person.name.name) #output name
print(Typed.__dict__)
print(Person.__dict__)
# 1-->類屬性調用了類實例,2-->有set,此處調用set方法,因為數據描述器,所以字典為空,自己追加字典值,
#get也需要從字典中獲取值

 

import inspect
class Typed:
    def __init__(self,name,type):
        self.name = name
        self.type = type

    def __set__(self, instance, value):
        if not  isinstance(value,self.type):
            raise TypeError(value)
        instance.__dict__[self.name]=value

    def __get__(self, instance, owner):
        if instance is  not None:
            return instance.__dict__[self.name]
        return self

def typeassert(cls):
    parmas = inspect.signature(cls).parameters
    for k,v in parmas.items():
        if v.annotation != v.empty:
            setattr(cls,k,Typed(v,v.annotation))
    return cls
@typeassert
class Person:
    def __init__(self,name:str,age:int):
        self.name = name       # 2
        self.age = age

p = Person('tom',21)

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM