描述符优先级和高级应用

#优先级
描述符是描述另一个类的类属性,描述符自己产生的实例无法调用描述符方法
不能为定义到构造函数中
另一个类或者类产生的实例调用类属性的时候,触发描述符里的方法

类属性>数据描述符>实例属性>非数据描述符>找不到

1. 类属性:直接在定义了 类.属性=值 (定义了确切的值) 则变成类属性,不会触发描述符的方法
2. 数据描述符:至少实现了__get__()和__set__(),对象.属性=值 则被另一个类代理,触发__set__()
3. 实例属性:实例自己的属性调用
4. 非数据描述符:没有实现__set__()的描述符,对象.属性 被另一个类代理,触发__get__(),如果是对象.属性=值,因为描述符为非数据描述符,没有__set__()方法,所以这个{属性:值}会成为实例的私有属性。
5. 都找不到的属性:对象本身、父类和代理类都找不到想调用的属性,则触发对象的父类里面定义的__getattr__()方法

数据描述符>实例属性的证明:实例.属性=值 此时被代理了,并触发的是数据描述符的__set__() del 实例.属性 也触发的是数据描述符的__delete__()
实例调用这个属性时,先去找数据描述符,没有就找自己
```python
class Descriptors:
"""
数据描述符
"""
def __get__(self, instance, owner):
print("执行Descriptors的get")
def __set__(self, instance, value):
print("执行Descriptors的set")
def __delete__(self, instance):
print("执行Descriptors的delete")

class Light:

#使用描述符
name = Descriptors()

def __init__(self, name, price):
self.name = name
self.price = price


#使用类的实例对象来测试
light = Light("电灯泡",60) #执行描述符的set内置属性
light.name #执行描述符的get内置属性
print(light.__dict__) #实例的字典,实例属性price,name无法成为实例属性
print(Light.__dict__) #查看类的字典,存在name(为描述符的对象)
del light.name #执行描述符的delete内置属性
```
在light = Light("电灯泡",60)实例化时,想把“电灯泡”给name,但已经代理了,没办法成为实例属性

实例属性>非数据描述符的证明:实例.属性=值,此时为非数据描述符,特殊类中没有__set__()方法,此时实例.属性=值,会变成私有属性。如果特殊类中加__set__()方法,此时立马被代理,不会成为私有的实例属性

帮助理解:类中被代理的那个属性,属性=特殊类的对象,也就是描述符

```python
1. 实例.__dict__,存的是{属性:值},
2.类.__dict__,当此属性是描述符时(无论是什么描述符),存的是{属性:被代理的描述符地址},因为就是指向描述符对象,就是存的描述符地址
当是类属性时(定义了确切的值),存的是{属性:值}
```
1e390120f5fac009fb7c66a4597c1b30.png  

32ad399eb825435fdcaab21846b213e9.png  

ef665b2ae6ef4f104668a7903bd3f3a1.png  
```python
class Foo:
def __get__(self, instance, owner):
print('===>get方法')
def __set__(self, instance, value):
print('===>set方法',instance,value)
# instance.__dict__['x']=value #b1.__dict__
def __delete__(self, instance):
print('===>delete方法')
#后面x为这个类的实例,__get__()执行时,self是x,instance是另一个类的#实例即b1,owner为另一个类名即Bar
# __set__()执行时,self是x,instance是b1,value是实例.属性#=值 的那个值


class Bar:
x=Foo() #在何地?

print(Bar.x)

Bar.x=1
print(Bar.__dict__)
print(Bar.x)

print(Bar.__dict__)
b1=Bar()
b1.x #get
b1.x=1 # set
del b1.x # delete


b1=Bar()
Bar.x=111111111111111111111111111111111111111
b1.x

del Bar.x
b1.x
```

#描述符的应用

实现了传进来的name,age,salary数据的类型检测
name是字符串,age是数字,salary是数字

```python
class Typed:
def __init__(self,key,expected_type):
self.key=key
self.expected_type=expected_type
def __get__(self, instance, owner):
print('get方法')
# print('instance参数【%s】' %instance)
# print('owner参数【%s】' %owner)
return instance.__dict__[self.key]
def __set__(self, instance, value):
print('set方法')
# print('instance参数【%s】' % instance)
# print('value参数【%s】' % value)
# print('====>',self)
if not isinstance(value,self.expected_type):
# print('你传入的类型不是字符串,错误')
# return
raise TypeError('%s 传入的类型不是%s' %(self.key,self.expected_type))
instance.__dict__[self.key]=value
def __delete__(self, instance):
print('delete方法')
# print('instance参数【%s】' % instance)
instance.__dict__.pop(self.key)

class People:
name=Typed('name',str) #t1.__set__() self.__set__()
age=Typed('age',int) #t1.__set__() self.__set__()
def __init__(self,name,age,salary):
self.name=name
self.age=age
self.salary=salary

# p1=People('alex','13',13.3)
p1=People(213,13,13.3)

# p1=People('alex',13,13.3)
# print(p1.__dict__)
# p1=People(213,13,13.3)
# print(p1.__dict__)
# print(p1.__dict__)
# print(p1.name)

# print(p1.__dict__)
# p1.name='egon'
# print(p1.__dict__)


# print(p1.__dict__)
# del p1.name
# print(p1.__dict__)

# print(p1)

# print(p1.name)
# p1.name='egon'
# print(p1.name)
# print(p1.__dict__)
```

```
#类的装饰器的运用
Typed定义了数据描述符,People的几个类属性都指向描述符了,描述符对实例化传入的数据做判断,并通过描述符中__set__里的instance参数即instance.__dict__['x']=value,赋值给实例,成为实例的属性,并可通过实例.__dict__查出
下面代码加了一个装饰器,本质是利用装饰器给People类的属性指向了描述符,即通过装饰器来实现 指向Typed函数 的目的
```
f12355fad4cbc56cd84d83f16cd76c89.png  


```
#描述符总结

描述符可实现大部分类特征中的底层魔法,包括@classmethod类方法 @staticmethod静态方法 @property静态属性 @__slots__限制实例的属性,只允许有这里面定义的属性,子类的实例不生效
__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
```

利用描述符自定义property
32ad399eb825435fdcaab21846b213e9.png

__get__(self, instance, owner) 中,self是描述符对象,即lazyproperty(area), instance为Room实例r1, owner是Room类
```
解析:
@Lazyproperty本质是 area=Lazyproperty(area)
Lazyproperty被加快描述符功能,在Room中def同级处 @Lazyproperty,
第一步 Lazyproperty(area),实例化 直接执行__init__, 函数area就成了实例Lazyproperty(area)的func,再由Room类里的area属性接收了这个func,在此之后,area=Lazyproperty(area),Room里的area属性,就被描述符代理了

第二步 r1=Room(“厕所”,3, 3) r1.area这个操作是调用r1的函数属性,但发现r1没有这个属性,就去找Room类中的area属性,由于被非数据描述符代理了,本质是去找描述符了
并触发__get__方法,而__get__方法中,self是Lazyproperty(area) 产生的实例,同时也是接收到实例的area

还不够完善,要实现的是r1.area就直接执行,而如果不加self.func(instance),instance即为 r1 不加这一句话,则r1.area要想运行就要变成 r1.area(r1) __get__中把func运行一次(此时一定要传r1),并返回结果给r1.area
```

```
此外:
类中的静态属性,是给实例用的,类直接调用的时候,返回的是属性的内存地址
类直接调用非数据描述符的属性时,例如Room.area 这个例子中会报错,原因:
1.__get__(self, instance, owner): 实例为空
2. self.func(instance),没有传实例
3.. def area(self): 这个函数被装饰了,不能作为Room类的方法直接用,在__get__中,self.func(instance),其实就是 接收到实例的area属性即Lazyproperty(area) 的实例.函数erea(Room的实例instance) ,而实例为空

最后,setattr(instance, self.func.__name__, res)
```

```
描述符触发的原理
A类设定的描述符 __get__(self,instance, owner): 有instance和owner
描述符用来描述别人的,用来代理B类的属性,而B类和B类的实例b, 他们都可以调用代理的属性,只要调用了这个属性,就会触发描述符的__get__方法,但是B和b调用的时候,传给__get__的instance不同,B调用时传的是None,实例调用时,传实例自己

如果加__set__,成为数据描述符, 那么上面setattr改成的实例属性,无法存在实例属性字典中,因为setattr(instance, self.func.__name__, res),这一步本质是,r1.area=res 只要调用就直接触发的是__set__方法,还没来得及给r1加新属性!
验证了数据描述符优先级>实例属性
```


#利用描述符自定制classmethod

```python
class ClassMethod:
def __init__(self,func):
self.func=func
def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
def feedback(*args,**kwargs):
print('在这里可以加功能啊...')
return self.func(owner,*args,**kwargs)
return feedback

class People:
name='linhaifeng'
@ClassMethod # say_hi=ClassMethod(say_hi)
def say_hi(cls,msg,x):
print('你好啊,帅哥 %s %s %s' %(cls.name,msg,x))

People.say_hi('你是那偷心的贼',10)

p1=People()
p1.say_hi('你是那偷心的贼',10)
```

property的补充
方法一 = 方法二
```python
#方法一
class Foo:
@property
def AAA(self):
print('get的时候运行我啊')

@AAA.setter
def AAA(self,val):
print('set的时候运行我啊',val)
@AAA.deleter
def AAA(self):
print('del的时候运行我啊')
#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA

#方法二
class Foo:
def get_AAA(self):
print('get的时候运行我啊')
def set_AAA(self,val):
print('set的时候运行我啊',val)
def del_AAA(self):
print('del的时候运行我啊')

AAA=property(get_AAA,set_AAA,del_AAA)
#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA
```

 

posted @ 2019-08-14 18:42  坚持fighting  阅读(303)  评论(0编辑  收藏  举报