Python面向對象描述符

描述符

描述符(__get__,__set__,__delete__)   # 這裏着重描述了python的底層實現原理python

  一、 描述符是什麼:描述符本質就是一個新式類,在這個新式類中,至少實現了__get__(),__set__(),__delete__()中的一個,這也被稱爲描述符協議。
    __get__():調用一個屬性時,觸發
    __set__():爲一個屬性賦值時,觸發
    __delete__():採用del刪除屬性時,觸發框架

1 class Foo:   #在python3中Foo是新式類,它實現了三種方法,這個類就被稱做一個描述符
2     def __get__(self,instance,owner):
3         print('get方法')
4     def __set__(self, instance, value):
5         print('set方法')
6     def __delete__(self, instance):
7         print('delete方法')
描述符的簡單定義

  二、描述符是幹什麼的:描述符的做用是用來代理另一個類的屬性的(必須把描述符定義成這個類的類屬性,不能定義到構造函數中)ide

class Foo:
    def __get__(self,instance,owner):
        print('===>get方法')
    def __set__(self, instance, value):
        print('===>set方法')
    def __delete__(self, instance):
        print('===>delete方法')

#包含這三個方法的新式類稱爲描述符,由這個類產生的實例進行屬性的調用/賦值/刪除,並不會觸發這三個方法
f1=Foo()
f1.name='egon'
print(f1.name)
del f1.name
#疑問:什麼時候,何地,會觸發這三個方法的執行
引子

  三、描述符應用在何時,什麼地方函數

class D:
    def __get__(self, instance, owner):
        print("-->get")
    def __set__(self, instance, value):
        print("-->set")
    def __delete__(self, instance):
        print("-->delete")
class E:
    e = D() # 描述誰?

ee = E()
ee.y = 10 # 此時描述的是e  y則不會被描述
ee.e      # 訪問e屬性,則會觸發__get__
ee.e = 2  # 爲e進行賦值操做,則會觸發__set__
del ee.e  # 刪除e的屬性,則會觸發__delete__
# print(ee.__dict__)
簡單例子

  四、描述符分爲倆種形式。工具

    a.數據描述符(至少實現了__get__()和__set__()兩種方法)post

class Foo:
     def __set__(self, instance, value):
         print('set')
     def __get__(self, instance, owner):
         print('get')
數據描述符

    b.非數據描述符(沒有實現__set__()方法)優化

1 class Foo:
2     def __get__(self, instance, owner):
3         print('get')    
非數據描述符

      注意事項:
      1、描述符自己應該定義成新式類,被代理的類也應該是新式類
      2、必須把描述符定義成另一個類觸發的類屬性,不能爲定義到構造函數spa

  五、嚴格遵循描述符的優先級別,由高到低3d

     a.類屬性—》b.數據數據描述符—》c.實例屬性—》d.非數據描述符—》e.找不到的屬性觸發__getattr__()代理

 1 class Foo:
 2     def __get__(self,instance,owner):
 3         print('===>get方法')
 4     def __set__(self, instance, value):
 5         print('===>set方法')
 6     def __delete__(self, instance):
 7         print('===>delete方法')
 8 
 9 class Bar:
10     x=Foo()   #調用foo()屬性,會觸發get方法
11 
12 print(Bar.x)  #類屬性比描述符有更高的優先級,會觸發get方法
13 Bar.x=1       #本身定義了一個類屬性,並賦值給x,跟描述符沒有關係,因此不會觸發描述符的方法
14 # print(Bar.__dict__)
15 print(Bar.x)
16 
17 
18 ===>get方法
19 None
20 1
類屬性>數據描述符
#有get,set就是數據描述符,數據描述符比實例屬性有更高的優化級

class Foo:
    def __get__(self,instance,owner):
        print('===>get方法')
    def __set__(self, instance, value):
        print('===>set方法')
    def __delete__(self, instance):
        print('===>delete方法')

class Bar:
    x = Foo()  # 調用foo()屬性,會觸發get方法

b1=Bar()   #在本身的屬性字典裏面找,找不到就去類裏面找,會觸發__get__方法
b1.x       #調用一個屬性的時候觸發get方法
b1.x=1     #爲一個屬性賦值的時候觸發set方法
del b1.x   #採用del刪除屬性時觸發delete方法

===>get方法
===>set方法
===>delete方法
數據描述符>實例屬性
 1 #類屬性>數據描述符>實例屬性
 2 
 3 class Foo:
 4     def __get__(self,instance,owner):
 5         print('===>get方法')
 6     def __set__(self, instance, value):
 7         print('===>set方法')
 8     def __delete__(self, instance):
 9         print('===>delete方法')
10 
11 class Bar:
12     x = Foo()             #調用foo()屬性,會觸發get方法
13 
14 b1=Bar()                  #實例化
15 Bar.x=11111111111111111   #不會觸發get方法
16 b1.x                      #會觸發get方法
17 
18 del Bar.x                 #已經給刪除,因此調用不了!報錯:AttributeError: 'Bar' object has no attribute 'x'
19 b1.x
類屬性>數據描述符>實例屬性
#實例屬性>非數據描述符
class Foo:
    def __get__(self,instance,owner):
        print('===>get方法')

class Bar:
    x = Foo()

b1=Bar()
b1.x=1
print(b1.__dict__)  #在本身的屬性字典裏面,{'x': 1}

{'x': 1}
實例屬性>非數據描述符
 1 #非數據描述符>找不到
 2 
 3 class Foo:
 4     def __get__(self,instance,owner):
 5         print('===>get方法')
 6 
 7 class Bar:
 8     x = Foo()
 9     def __getattr__(self, item):
10         print('------------>')
11 
12 b1=Bar()
13 b1.xxxxxxxxxxxxxxxxxxx    #調用沒有的xxxxxxx,就會觸發__getattr__方法
14 
15 
16 ------------>    #解發__getattr__方法
非數據描述符>找不到

   六、關於描述符的應用(類型檢測的應用)

class Typed:
    def __get__(self, instance,owner):
        print('get方法')
        print('instance參數【%s】' %instance)
        print('owner參數【%s】' %owner)       # owner是顯示對象是屬於誰擁有的
    def __set__(self, instance, value):
        print('set方法')
        print('instance參數【%s】' %instance) # instance是被描述類的對象(實例)
        print('value參數【%s】' %value)       # value是被描述的值
    def __delete__(self, instance):
        print('delete方法')
        print('instance參數【%s】'% instance)
class People:
    name=Typed()
    def __init__(self,name,age,salary):
        self.name=name        #觸發的是代理
        self.age=age
        self.salary=salary

p1=People('alex',13,13.3)
#'alex'             #觸發set方法
p1.name             #觸發get方法,沒有返回值
p1.name='age'       #觸發set方法
print(p1.__dict__)
#{'salary': 13.3, 'age': 13}  # 由於name已經被描述,因此實例的屬性字典並不存在name
# 固然也說明一點實例屬性的權限並無數據描述符的權限大


set方法
instance參數【<__main__.People object at 0x000001CECBFF0080>】
value參數【alex】
get方法
instance參數【<__main__.People object at 0x000001CECBFF0080>】
owner參數【<class '__main__.People'>】
set方法
instance參數【<__main__.People object at 0x000001CECBFF0080>】
value參數【age】
{'salary': 13.3, 'age': 13}
描述符所包含的參數是什麼

 

class Foo:
    def __init__(self,key,pd_type):
        self.key = key
        self.pd_type = pd_type
    def __get__(self, instance, owner):
        print("get")
        return instance.__dict__[self.key] # 返回值是 instace對象屬性字典self.key所對應的值
    def __set__(self, instance, value):
        print(value) # 輸出value所對應的值
        if not isinstance(value,self.pd_type): # 判斷被描述的值 是否 屬於這個類的
            raise TypeError("%s 傳入的類型不是%s" %(value,self.pd_type)) # 爲否 則拋出類型異常
        instance.__dict__[self.key] = value # True 對instance對象的屬性字典進行賦值操做
    def __delete__(self, instance):
        print("delete")
        instance.__dict__.pop(self.key) # 若是進行刪除操做,也是對instance對象的屬性字典進行刪除操做
class Sea:
    name = Foo("name",str)      # 向描述符傳入倆個值
    history = Foo("history",int)
    def __init__(self,name,addr,history):
        self.name = name
        self.addr = addr
        self.history = history
s1 = Sea("北冰洋","北半球",10000)
print(s1.__dict__)
print(s1.name) # 對被描述的屬性進行訪問,觸發__get__

北冰洋
10000
{'addr': '北半球', 'history': 10000, 'name': '北冰洋'}
get
北冰洋
用描述符實現的類型檢測

 

# 描述符和類裝飾器的結合使用
class Foo:
    """ 描述符 """
    def __init__(self,key,pd_type):
        self.key = key
        self.pd_type = pd_type
    def __get__(self, instance, owner):
        print("get")
        return instance.__dict__[self.key]
    def __set__(self,instance,value):
        """ instance是對象(實例) """
        print("set")
        if not isinstance(value,self.pd_type): # 若是判斷類型爲False
            raise TypeError("%s 傳入的類型不是%s" %(value,self.pd_type)) # 拋出異常
        instance.__dict__[self.key] = value #操做對象(實例)的屬性字典設置值
    def __delete__(self, instance):
        print("delete")
        instance.__dict__.pop(self.key)
def Typed(**kwargs):
    def func(obj):
        for key,value in kwargs.items(): # 遍歷字典的鍵值對
            setattr(obj,key,Foo(key,value)) # 爲Sea的屬性字典設置值。 並執行Foo 給Foo傳入倆個值
            # print("--->" ,obj)
        return obj # func的返回值是Sea
    return func
@Typed(name=str,addr=str,history=int)  # Typed函數運行結束實際上就是 @func --> sea=func(Sea)
# Typed運行,並將參數所有傳給kwargs
class Sea:
    def __init__(self,name,addr,history):
        self.name = name
        self.addr = addr
        self.history = history
s1 = Sea("大西洋","地球",10000)
print(s1.__dict__)
print(Sea.__dict__) # 此時的name,addr,history均被Foo所描述
裝飾器和描述符實現類型檢測的終極版本

  七、描述符總結

      描述符是能夠實現大部分python類特性中的底層魔法,包括@classmethod,@staticmethd,@property甚至是__slots__屬性

    描述符是不少高級庫和框架的重要工具之一,描述符一般是使用到裝飾器或者元類的大型框架中的一個組件.

    a.利用描述符原理完成一個自定製@property,實現延遲計算(本質就是把一個函數屬性利用裝飾器原理作成一個描述符:類的屬性字典中函數名爲key,value爲描述符類產生的對象)

class Room:
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length

    @property
    def area(self):
        return self.width * self.length

r1=Room(Tom',1,1)
print(r1.area)
@property的特性回顧

 

# 僞造的property
class Wzproperty:
    def __init__(self,func):
        self.func = func
    def __get__(self, instance, owner):
        """ 若是類去調用 instance 爲None"""
        print("get")
        if instance is None: 
            return self
        setattr(instance,self.func.__name__,self.func(instance)) # 給實例字典設置值,避免重複計算
        return self.func(instance)
class Sea:
    def __init__(self,name,history,speed):
        self.name = name
        self.history = history
        self.speed = speed
    @Wzproperty # test = Wzptoperty(test)
    def test(self):
        return self.history * self.speed
s1 = Sea("大西洋",10,20)
# print(Sea.__dict__)
# print(Sea.test) # 若是類去調用 描述符的instance 此時是None
print(s1.test)
print(s1.test) # 這一次就不會觸發描述符,由於實例屬性字典就有
"""由於有了爲實例的屬性字典設置告終果。因此會率先從本身的屬性字典找
其次觸發非數據描述符,同時也聲明瞭實例屬性的權限大於非數據描述。
若是給描述符+__set__,描述符就變爲數據描述符,根據權限實例再去用不會先去
本身的屬性字典,而是觸發描述符的操做"""
print(s1.__dict__)

控制檯輸出
get
200
200
{'test': 200, 'speed': 20, 'name': '大西洋', 'history': 10} # 實例的屬性字典
僞造的property以及闡釋

 

# 僞造的classmethod
class Wzclassmethod:
    def __init__(self,func):
        self.func = func
    def __get__(self, instance, owner):
        print("get")
        def bar():
            return self.func(owner)  #  test(Sea)
        return bar
    def __set__(self, instance, value):
        print("set")
class Sea:
    long = 10
    kuan = 20
    @Wzclassmethod  # test = Wzclassmethod(test)
    def test(cls):
        print("長%s 寬%s" %(cls.long,cls.kuan))
Sea.test()
僞造的classmethod

 

# 僞造的staticmethod
import hashlib,time
class Wzstaticmethod:
    def __init__(self,func):
        self.func = func
    def __set__(self, instance, value):
        print("set")
    def __get__(self, instance, owner):
        print("get")
        def bar():
            if instance is None:
                return self.func()
        return bar
    def __delete__(self, instance):
        print("delete")
class Onepiece:
    def __init__(self):
        pass
    @Wzstaticmethod # test = Wzstaticmethod(test)
    def test(x=1):
        hash = hashlib.md5()
        hash.update(str(time.time()).encode("utf-8"))
        filename = hash.hexdigest()
        print(filename)
        return filename
# print(Onepiece.__dict__)
Onepiece.test()
僞造的staticmethod

類裝飾器

  一、基本框架

def deco(func):
    print('===================')
    return func  #fuc=test

@deco    #test=deco(test)
def test():
    print('test函數運行')
test()
框架

 

def deco(obj):
    print('============',obj)
    obj.x=1   #增長屬性
    obj.y=2
    obj.z=3
    return obj

@deco   #Foo=deco(Foo)   #@deco語法糖的基本原理
class Foo:
    pass

print(Foo.__dict__)  #加到類的屬性字典中

輸出
============ <class '__main__.Foo'>
{'__module__': '__main__', 'z': 3, 'x': 1, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, 'y': 2}
對類增長類屬性
def Typed(**kwargs):
    def deco(obj):
        obj.x=1
        obj.y=2
        obj.z=3
        return obj
    print('====>',kwargs)
    return deco

@Typed(x=2,y=3,z=4)  #typed(x=2,y=3,z=4)--->deco  會覆蓋原有值
class Foo:
    pass
View Code
def Typed(**kwargs):
    def deco(obj):
        for key,val in kwargs.items():
            setattr(obj,key,val)
        return obj
    return deco

@Typed(x=1,y=2,z=3)  #typed(x=1,y=2,z=3)--->deco
class Foo:
    pass
print(Foo.__dict__)

@Typed(name='egon')
class Bar:
    pass
print(Bar.name)

控制檯輸出
{'y': 2, '__dict__': <attribute '__dict__' of 'Foo' objects>, 'z': 3, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__module__': '__main__', 'x': 1, '__doc__': None}

egon
加強版

元類

  元類(metaclass)

class Foo:
     pass
 
f1=Foo() #f1是經過Foo類實例化的對象
引子

  python中一切皆是對象,類自己也是一個對象,當使用關鍵字class的時候,python解釋器在加載class的時候就會建立一個對象(這裏的對象指的是類而非類的實例)

  上例能夠看出f1是由Foo這個類產生的對象,而Foo自己也是對象,那它又是由哪一個類產生的呢?

#type函數能夠查看類型,也能夠用來查看對象的類,兩者是同樣的
print(type(f1)) # 輸出:<class '__main__.Foo'>     表示,obj 對象由Foo類建立
print(type(Foo)) # 輸出:<type 'type'>  
類的爸爸

  一、辣麼,什麼是元類?

  • 元類是類的類,是類的模板
  • 元類是用來控制如何建立類的,正如類是建立對象的模板同樣
  • 元類的實例爲類,正如類的實例爲對象(f1對象是Foo類的一個實例Foo類是 type 類的一個實例)
  • type是python的一個內建元類,用來直接控制生成類,python中任何class定義的類其實都是type類實例化的對象

  建立類有倆種方式

class Foo:
    def func(self):
        print('from func')
方式A,用class定義
def func(self):
         print('from func')
x=1
Foo=type('Foo',(object,),{'func':func,'x':1})


type要接收三個參數
1、將要建立的類名
2、繼承的類
三、類的屬性字典
方式B,用type生成
# 方式1
class Foo:
    pass
# 方式2
Bar = type("Bar",(object,),{})
print(Foo.__dict__)
print(Bar.__dict__)


控制檯輸出
{'__module__': '__main__', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__dict__': <attribute '__dict__' of 'Foo' objects>}
{'__module__': '__main__', '__doc__': None, '__weakref__': <attribute '__weakref__' of 'Bar' objects>, '__dict__': <attribute '__dict__' of 'Bar' objects>}
倆者間的簡單對比

  二、一個類沒有聲明本身的元類,默認它的元類就是type,除了使用元類type,用戶也能夠經過繼承type來自定義元類(順便咱們也能夠瞅一瞅元類如何控制類的建立,工做流程是什麼)

class Mytype(type):
    def __init__(self,a,b,c):
        print(self)
        print(a)
        print(b)
        print(c)
    def __call__(self, *args, **kwargs):
        print("call")

class Slamdunk(metaclass=Mytype): # Mytype("Slamdunk",(object,),{}) 實際上就是這麼作,可是傳了4個參數
    # 聲明Foo類由Mytype建立,聲明本身的元類
    def __init__(self,name):
        self.name = name

s1 = Slamdunk("櫻木花道")
# 根據python一切皆對象,Slamdunk() 本質上就是在觸發建立 Slamdunk類的 元類的__call__

控制檯輸出
<class '__main__.Slamdunk'>  # 元類建立的實例(對象)
Slamdunk # 實例名
() # 繼承的類,在python3中都默認繼承object,即都爲新式類
{'__qualname__': 'Slamdunk', '__init__': <function Slamdunk.__init__ at 0x000002106AFBF840>, '__module__': '__main__'} # 實例類的屬性字典
call # 實例+() 觸發了元類的__call__方法
模擬初步認識
class Mytype(type):
    def __init__(self,a,b,c):
        print(self)
    def __call__(self, *args, **kwargs): # 傳的值是怎麼傳進去的,就去怎麼接收
        print("call")
        obj = object.__new__(self) # 生成一個實例
        self.__init__(obj,*args,**kwargs)   # 這裏的self是Mytype產生的實例,這一步觸發 Slamdunk 的構造方法
        return obj # __call__方法下的返回值是 self 產生的實例 賦值給s1
class Slamdunk(metaclass=Mytype):
    #  Slamdunk = Mytype("Slamdunk",(object,),{}) 實際上就是這麼作,可是傳了4個參數
    # 聲明Foo類由Mytype建立,聲明本身的元類
    # 觸發元類的__init__(元類的構造方法)
    def __init__(self,name):
        self.name = name
s1 = Slamdunk("櫻木花道")
# 根據python一切皆對象,Slamdunk() 本質上就是在觸發建立 Slamdunk類的 元類的__call__
print(s1.__dict__) # 能夠訪問到實例對象的屬性字典
詳解

 

class Mytype(type):
    def __init__(self,a,b,c):
        print(self)
    def __call__(self, *args, **kwargs):
        obj = object.__new__(self)
        self.__init__(obj,*args,**kwargs) 
        return obj 
class Slamdunk(metaclass=Mytype):
    def __init__(self,name):
        self.name = name
s1 = Slamdunk("櫻木花道")
print(s1.__dict__) 

控制檯輸出
<class '__main__.Slamdunk'>
{'name': '櫻木花道'}

# 能夠加斷點體驗
實現建立類的流程 精簡版
相關文章
相關標籤/搜索