即使一个人,也要活得像军队一样!

Python-- 描述符的使用

一、描述符是什么
  描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议
  __get__():调用一个属性时,触发
  __set__():为一个属性赋值时,触发
  __delete__():采用del删除属性时,触发

1
2
3
4
5
6
7
class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符
def __get__(self, instance, owner):
print('__get__(),被执行了')
def __set__(self, instance, value):
print('__set__(),被执行了')
def __delete__(self, instance):
print('__delete__(),被执行了')

二、描述符的作用
  描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)

1
2
3
4
5
6
7
class Test:
x = Foo()
def __init__(self,x):
self.x = x

t = Test(2) #'__set__(),被执行了'
print(t.x) #'__get__(),被执行了' 'None'

三、描述符分为两种
  1、数据描述符:至少实现了__get__()__set__()
  2、非数据描述符:没有实现__set__()

1
2
3
4
5
6
7
8
9
10
11
#数据描述符
class Foo:
def __set__(self, instance, value):
print('set')
def __get__(self, instance, owner):
print('get')

#非数据描述符
class Foo:
def __get__(self, instance, owner):
print('get')

四、注意事项:
  描述符本身应该定义成新式类,被代理的类也应该是新式类
  必须把描述符定义成这个类的类属性,不能为定义到构造函数中
  要严格遵循该优先级,优先级由高到底分别是
    1类属性
    2数据描述符
    3实例属性
    4非数据描述符
    5找不到的属性触发__getattr__()
五、描述符的应用
  1、现在有一个需求,定义一个用户信息,用户的名字为字符串类型,年龄为int类型,收入为float类型,可以用描述符来代理这些属性,从而控制传入的数据类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 定义描述符
class DescType:

def __get__(self, instance, owner):
print('\n执行了__get__')
print('self是 %s' % self) # self表示的是描述符实例的对象DescType()--->name
print('instance是 %s' % instance) # instance表示的是被代理的类属性的类实例化出的对象,这里是p1
print('owner是 %s' % owner) # owner表示的是被代理的类,这里是People这个类

def __set__(self, instance, value):
print('\n执行了__set__')
print('self是 %s' % self) # self表示的是描述符实例的对象DescType()--->name
print('instance是 %s' % instance) # instance表示的是被代理的类属性的类实例化出的对象,这里是p1
print('value是 %s' % value) # value表示的是被代理的类的属性的值,这里是'Loney'

def __delete__(self, instance):
print('\n执行了__delete__')
print('instance是 %s' % instance)


# 定义一个人的类(被代理的类)
class People:
name = DescType() # 用描述符代理了name这个属性

def __init__(self, name, age, salary):
self.name = name
self.age = age
self.salary = salary


p1 = People('Loney', 24, 11.1)
print(p1.name) # 会出发__get__
print(p1.__dict__) # {'age': 24, 'salary': 11.1}

发现被代理的name属性并没有被设置对应的值,所以__dict__中没有’name’,那是因为实例化的时候执行了__init__,所以也执行了__set__, 但是在__set__中并没有真正的操作进行设置。

  2、所以要想真正的对属性进行代理,对属性进行设置、获取和删除值,则需要通过操作底层__dict__字典,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 定义描述符
class DescType:
def __init__(self, key, value_type): # 传入key用来操作底层属性字典,value_type用来表示期望的数据类型
print('描述符初始化... key:', key, ' value_type:', value_type)
self.key = key
self.value_type = value_type

def __get__(self, instance, owner):
print('执行了__get__')
return instance.__dict__[self.key] # return p2.name

def __set__(self, instance, value):
print('\n执行了__set__')
print('self是 %s' % self) # self表示的是描述符实例的对象DescType()
print('instance是 %s' % instance) # instance表示的是被代理的类属性的类实例化出的对象,这里是p2
print('value是 %s' % value) # value表示的是被代理的类的属性的值
print('value_type %s' % self.value_type) # value表示的是被代理的类的属性的值
if not isinstance(value, self.value_type): # 用来判断用户传入的是否符合要求
raise TypeError('%s 传入的不是 %s' % (self.key, self.value_type)) # 抛出类型异常,提示用户程序终止
instance.__dict__[self.key] = value # 符合要求,则设置属性对应的值

def __delete__(self, instance):
print('执行了__delete__')
instance.__dict__.pop(self.key)


# 定义一个人的类(被代理的类)
class People:
name = DescType('name', str) # 用描述符代理了name这个属性,相当于执行了Desc_type中的self.__set__
age = DescType('age', int)
salary = DescType('salary', float)

def __init__(self, name, age, salary):
self.name = name
self.age = age
self.salary = salary


p2 = People('Loney', 24, 20.0)

#访问
print('*'*100)
print(p2.name)

六、总结
  1、描述符就是一个类(新式类);
  2、描述符分为数据描述和非数据描述符,区别在于前者有__set__方法,后者没有;
  3、描述符的使用要遵循优先级:类属性>数据描述符>实例属性>非数据描述符>找不到(__getattr__);
  4、描述符方法中的self表示的是描述符实例化的对象,instance表示的是被描述(代理)的类实例化的对象,owner表示的是被描述(代理)的类,value表示的是设置到被描述(代理)属性的值。

以上。
– end –

-------------本文结束感谢您的阅读-------------