Python之路(十二):描述符,類裝飾器,元類


python基礎之面向對象(描述符、類裝飾器及元類)

 

描述符

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

  1、 描述符是什么:描述符本質就是一個新式類,在這個新式類中,至少實現了__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方法')
復制代碼

  2、描述符是干什么的:描述符的作用是用來代理另外一個類的屬性的(必須把描述符定義成這個類的類屬性,不能定義到構造函數中)

復制代碼
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
#疑問:何時,何地,會觸發這三個方法的執行
復制代碼

  3、描述符應用在什么時候,什么地方

復制代碼
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__)
復制代碼

  4、描述符分為倆種形式。

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

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')    

      注意事項:
      一、描述符本身應該定義成新式類,被代理的類也應該是新式類
      二、必須把描述符定義成另外一個類觸發的類屬性,不能為定義到構造函數

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

     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__方法
復制代碼

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

復制代碼
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
北冰洋
復制代碼

 

  裝飾器和描述符實現類型檢測的終極版本

  7、描述符總結

      描述符是可以實現大部分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
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} # 實例的屬性字典
復制代碼

 

復制代碼
# 偽造的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()
復制代碼

 

復制代碼
# 偽造的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()
復制代碼

類裝飾器

  1、基本框架

復制代碼
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
復制代碼
復制代碼
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'>  

  1、辣么,什么是元類?

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

  創建類有倆種方式

class Foo:
    def func(self):
        print('from func')
復制代碼
def func(self):
         print('from func')
x=1
Foo=type('Foo',(object,),{'func':func,'x':1})


type要接收三個參數
1、將要創建的類名
2、繼承的類
3、類的屬性字典
復制代碼
復制代碼
# 方式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>}
復制代碼

  2、一個類沒有聲明自己的元類,默認它的元類就是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': '櫻木花道'}

# 可以加斷點體驗
復制代碼

 


免責聲明!

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



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