溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

33面向對象8_descriptors

發(fā)布時間:2020-05-31 17:15:14 來源:網(wǎng)絡 閱讀:137 作者:chaijowin 欄目:編程語言

?

descriptors描述器:

?

descriptor的表現(xiàn):

用到3個魔術方法:__get__()、__set__()、__delete__()

object.__get__(self,instance,owner)

object.__set__(self,instance,value)

object.__delete__(self,instance)

self,指代當前實例,調用者;

instance,是owner的實例;

owner,是屬性所屬的類;

?

py中,一個類實現(xiàn)了__get__()、__set__()__delete__()三個方法中的任何一個方法,就是描述器;

如果僅實現(xiàn)了__get__(),就是non-data descriptor非數(shù)據(jù)描述器;

如果同時實現(xiàn)了__get__()__set__(),就是data descriptor數(shù)據(jù)描述器,如@property;

如果一個類的類屬性設置為描述器,那么這個類它被稱為owner屬主,如B類中類屬性x = A()

關鍵記?。侯悓傩?;

?

注:

當一個類的類屬性,是另一個類的實例時,這“另一個類”上有__get__()、__set__()__delete__()三者之一,它就是個描述器的類,在類屬性上訪問另一個類的實例時,它就會觸發(fā)__get__()方法;如果是通過實例的屬性訪問另一個類的實例self.x = A(),它不會觸發(fā)__get__()方法;

?

non-data descriptordata descriptor

理解1

如果一個類的屬性是一個數(shù)據(jù)描述器,對實例屬性的操作(該實例屬性與類屬性名相同時)相當于操作類屬性;

理解2

官方是用優(yōu)先級定義的;

一個類的類屬性是一個數(shù)據(jù)描述器,對該類的實例屬性的操作,該類的實例的__dict__優(yōu)先級降低(數(shù)據(jù)描述器的優(yōu)先級高于實例的__dict__);

如果是非數(shù)據(jù)描述器,則實例的__dict__高于描述器的優(yōu)先級;

?

屬性查找順序

實例的__dict__優(yōu)先于non-data descriptor;

data descriptor優(yōu)先于實例的__dict__;

__delete__有同樣的效果,有此方法就是data descriptor;

?

B.x = 500?? #對描述器不能這么用,賦值即定義,直接把類屬性覆蓋了,注意,不要直接用類來操作,盡管是在類上定義,也要用實例來操作,除非明確知道在干什么

print(B.x)

?

b = B()

b.x = 600?? #雖觸發(fā)了__set__(),未把類屬性覆蓋,也寫不進__dict__中,被__set__()攔截了,對數(shù)據(jù)起到一定保護作用

?

本質

查看實例的__dict__可知,data descriptor,實例的__dict__都被__set__()攔住,實例的屬性名與類屬性名相同時,寫不進實例的__dict__中;

原來不是什么data descriptor優(yōu)先級高,而是把實例的屬性從__dict__中給去掉了(實例的屬性名與類的屬性名相同),造成了該屬性如果是data descriptor優(yōu)先訪問的假象,說到底,屬性訪問的順序從來就沒變過;

?

py的描述器應用非常廣泛;

py的所有方法(包括@staticmethod@classmethod、__init__()),都是non-data descriptor,因此,實例可以重新定義和覆蓋方法,這允許單個實例獲取與同一類的其它實例不同的行為;

@property類實現(xiàn)是一個data descriptor,因此,實例不能覆蓋屬性的行為;

?

例:

class A:

??? def __init__(self):

??????? print('A.__init__')

??????? self.a1 = 'a1'

?

class B:

??? x = A()

??? def __init__(self):

??????? print('B.__init__')

??????? self.x = 100

?

print(B.x.a1)

b = B()

# print(b.x.a1)?? # X,AttributeError: 'int' object has no attribute 'a1'

輸出:

A.__init__

a1

B.__init__

?

例:

class A:

??? def __init__(self):

??????? print('A.__init__')

??????? self.a1 = 'a1'

?

??? def __get__(self, instance, owner): ??#A中定義了__get__(),類A就是一個描述器,對類B的屬性x讀取,成為對類A的實例的訪問就會調用__get__()

??????? print('A.__get__',self,instance,owner)

??????? # return self?? #解決B.x.a1報錯NoneType問題,黑魔法,通過屬性描述器來操作屬主,拿到屬主的類,可動態(tài)的改所有屬性

class B:

??? x = A()?? #當一個類的類屬性,是另一個類的實例時,這“另一個類”上有__get__()、__set__()__delete__()三者之一,它就是個描述器的類,在類屬性上訪問另一個類的實例時,它就會觸發(fā)__get__()方法

??? def __init__(self):

??????? print('B.__init__')

??????? # self.x = 100

??????? self.x = A()?? #如果是通過實例的屬性訪問另一個類的實例self.x = A(),它不會觸發(fā)__get__()方法

?

print(B.x) ??#V,要在類屬性上訪問,才觸發(fā)__get__(),該例__get__()方法返回None

# print(B.x.a1)?? #X,AttributeError: 'NoneType' object has no attribute 'a1',解決辦法:在類A__get__()添加返回值return self

b = B()

print(b.x)

print(b.x.a1)?? #實例屬性上訪問不會觸發(fā)__get__()

輸出:

A.__init__

A.__get__ <__main__.A object at 0x7f3d53b2bb38> None <class '__main__.B'> ??#依次為A的實例,None沒有類B的實例,類B

None

B.__init__

A.__init__

<__main__.A object at 0x7f3d53b2bba8>

a1

?

例:

class A:

??? def __init__(self):

??????? print('A.__init__')

??????? self.a1 = 'a1test'

?

??? def __get__(self, instance, owner):

??????? print('A.__get__',self,instance,owner)

??????? return self

?

??? def __set__(self, instance, value):?? #__set__()后,類B的實例b__dict__為空,只能向上訪問類屬性的

??????? print('A.__set__',self,instance,value)

?

class B:

??? x = A()

??? def __init__(self):

??????? print('B.__init__')

??????? # self.x = 100

??????? self.x = A()

?

print(B.x)

print("*"*20)

print(B.x.a1)

?

print("#"*20)

?

b = B()?? #觸發(fā)__set__()

print("*"*20)

print(b.x)?? #數(shù)據(jù)描述器,對實例屬性的操作(該實例屬性與類屬性的名字相同)相當于操作類屬性,查看實例的__dict__(為空)可知(向上找了類屬性)

print("*"*20)

print(b.x.a1)

?

print("#"*20)

?

print(b.__dict__)

print(B.__dict__)

?

# B.x = 500?? #對描述器不能這么用,賦值即定義,直接把類屬性覆蓋了,注意,不要直接用類來操作,盡管是在類上定義,也要用實例來操作,除非明確知道在干什么

# print(B.x)

輸出:

A.__init__

A.__get__ <__main__.A object at 0x7f63acbcd400> None <class '__main__.B'>

<__main__.A object at 0x7f63acbcd400>

********************

A.__get__ <__main__.A object at 0x7f63acbcd400> None <class '__main__.B'>

a1test

####################

B.__init__

A.__init__

A.__set__ <__main__.A object at 0x7f63acbcd400> <__main__.B object at 0x7f63acbcdb70> <__main__.A object at 0x7f63acbcdba8>

********************

A.__get__ <__main__.A object at 0x7f63acbcd400> <__main__.B object at 0x7f63acbcdb70> <class '__main__.B'>

<__main__.A object at 0x7f63acbcd400>

********************

A.__get__ <__main__.A object at 0x7f63acbcd400> <__main__.B object at 0x7f63acbcdb70> <class '__main__.B'>

a1test

####################

{}

{'__module__': '__main__', 'x': <__main__.A object at 0x7f63acbcd400>, '__init__': <function B.__init__ at 0x7f63acbca510>, '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}

?

例:

class A:

??? def __init__(self):

??????? print('A.__init__')

??????? self.a1 = 'a1test'

?

??? def __get__(self, instance, owner):

??????? print('A.__get__',self,instance,owner)

??????? return self

?

??? def __set__(self, instance, value):

??????? print('A.__set__',self,instance,value)

?

class B:

??? x = A()

??? def __init__(self):

??????? print('B.__init__')

??????? # self.x = 100

??????? self.x = A()

?

b = B()

print("*"*20)

b.x = 600?? #雖觸發(fā)了__set__(),未把類屬性覆蓋,也寫不進實例的__dict__中(查看實例的__dict__可知),被__set__()攔截了,對數(shù)據(jù)起到一定保護作用

print("*"*20)

print(b.x)?? #調用__get__()

print("*"*20)

print(b.__dict__)

輸出:

A.__init__

B.__init__

A.__init__

A.__set__ <__main__.A object at 0x7f05dd15eb70> <__main__.B object at 0x7f05dd15eba8> <__main__.A object at 0x7f05dd15ebe0>

********************

A.__set__ <__main__.A object at 0x7f05dd15eb70> <__main__.B object at 0x7f05dd15eba8> 600

********************

A.__get__ <__main__.A object at 0x7f05dd15eb70> <__main__.B object at 0x7f05dd15eba8> <class '__main__.B'>

<__main__.A object at 0x7f05dd15eb70>

********************

{}

?

?

?

習題:

1、實現(xiàn)StaticMethod裝飾器,完成staticmethod裝飾器的功能;

2、實現(xiàn)ClassMethod裝飾器,完成classmethod裝飾器的功能;

3、對實例的數(shù)據(jù)進行校驗;

class Person:

??? def __init__(self,name:str,age:int):

??????? self.name = name

??????? self.age = age

?

1、

class StaticMethod:

??? def __init__(self,fn):

??????? # print('__init__',fn)

??????? self.fn = fn

?

??? def __get__(self, instance, owner):

??????? # print('__get__',self,instance,owner)

??????? return self.fn

?

class A:

??? @StaticMethod

??? def foo():?? #類裝飾器裝飾完后,原函數(shù)消失了,foo=StaticMethod(foo),成為裝飾器類的實例了,在類屬性上訪問另一個類的實例時就會觸發(fā)__get__()方法

??????? print('test function')

?

??? @staticmethod

??? def foo2():

??????? print('test2 func')

?

f = A.foo

print(f)

f()

A.foo2()

A().foo()

A().foo2()

輸出

<function A.foo at 0x7fd0675bb488>

test function

test2 func

test function

test2 func

?

2、

from functools import partial

?

class ClassMethod:

??? def __init__(self,fn):

???? ???print('__init__',fn)

??????? self.fn = fn

?

??? def __get__(self, instance, cls):

??????? print('__get__', self, instance, cls)

??????? # return self.fn(cls)?? #XNoneType

?????? ?return partial(self.fn, cls)?? #固定下來,返回一個新函數(shù)

?

class A:

??? @ClassMethod

??? def bar(cls):

??????? print(cls.__name__)

?

# print(A.bar)

# print()

A.bar()

print()

A().bar()

print()

print(A.__dict__)

輸出:

__init__ <function A.bar at 0x7f2998f97e18>

__get__ <__main__.ClassMethod object at 0x7f2999039d68> None <class '__main__.A'>

A

?

__get__ <__main__.ClassMethod object at 0x7f2999039d68> <__main__.A object at 0x7f2999039da0> <class '__main__.A'>

A

?

{'__module__': '__main__', 'bar': <__main__.ClassMethod object at 0x7f2999039d68>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}

?

3、

class Typed:

??? def __init__(self,type):

??????? self.type = type

?

??? def __get__(self, instance, owner):

??????? pass

?

??? def __set__(self, instance, value):

??????? print('T.__set__',self,instance,value)

??????? if not isinstance(value,self.type):

??????????? raise ValueError('value')

?

class Person:

??? name = Typed(str)?? #硬編碼,需改進

??? age = Typed(int)

??? def __init__(self,name:str,age:int):

??????? self.name = name

??????? self.age = age

?

p1 = Person('tom',18)

?

3、

改進:用裝飾器+描述器,py中大量使用;

import inspect

?

?

class Typed:

??? def __init__(self, type):

??????? self.type = type

?

??? def __get__(self, instance, owner):

??????? pass

?

??? def __set__(self, instance, value):

??????? print('set', self, instance, value)

??????? if not isinstance(value, self.type):

??????????? raise ValueError(value)

?

?

class TypeAssert:

??? def __init__(self, cls):

??????? self.cls = cls

??????? params = inspect.signature(self.cls).parameters

??????? # print(params)

??????? for name, param in params.items():

??????????? print(name, param.annotation)

??????????? if param.annotation != param.empty:

??????????????? setattr(self.cls, name, Typed(param.annotation))?? #動態(tài)類屬性注入

?

??? def __call__(self, name, age):

??????? # params = inspect.signature(self.cls).parameters

??????? # print(params)

??????? # for name,param in params.items():

??????? #???? print(name,param.annotation)

??????? #???? if param.annotation != param.empty:

??????? #???????? setattr(self.cls,name,Typed(param.annotation))

??????? p = self.cls(name, age)

??????? return p

?

?

@TypeAssert

class Person:

??? # name = Typed(str)?? #動態(tài)類屬性注入

??? # age = Typed(age)

??? def __init__(self, name: str, age: int):

??????? self.name = name

??????? self.age = age

?

?

p1 = Person('jerry', 18)

p2 = Person('tom', 16)

print(id(p1))

print(id(p2))

輸出:

name <class 'str'>

age <class 'int'>

set <__main__.Typed object at 0x7f596a121da0> <__main__.Person object at 0x7f596a121dd8> jerry

set <__main__.Typed object at 0x7f596a121cf8> <__main__.Person object at 0x7f596a121dd8> 18

set <__main__.Typed object at 0x7f596a121da0> <__main__.Person object at 0x7f596a0af940> tom

set <__main__.Typed object at 0x7f596a121cf8> <__main__.Person object at 0x7f596a0af940> 16

140022008389080

140022007920960

?

?

?

習題:

1、將鏈表,封裝成容器:

要求:

1)提供__getitem__()、__iter__()、__setitem__();

2)使用一個列表,輔助完成上面的方法;

3)進階:不使用列表,完成上面的方法;

?

2、實現(xiàn)類property裝飾器,類名稱為Property;

?

?

?


向AI問一下細節(jié)

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內容。

AI