描述器

描述器 Descriptors

描述器的表现

用到3个魔术方法: __get__() 、 __set__() 、 __delete__()

方法签名如下

object.__get__(self, instance, owner)

object.__set__(self, instance, value)

object.__delete__(self, instance)

self 指代当前实例,调用者

instance 是owner的实例

owner 是属性的所属的类

请思考下面程序的执行流程是什么?

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
class A:

def __init__(self):

self.a1 = 'a1'

print('A.init')

class B:

x = A()

def __init__(self):

print('B.init')

print('-'*20)

print(B.x.a1)

print('='*20)

b = B()

print(b.x.a1)



# 运行结果

A.init

--------------------

a1

====================

B.init

a1

可以看出执行的先后顺序吧?

类加载的时候,类变量需要先生成,而类B的x属性是类A的实例,所以类A先初始化,所以打印A.init。 然后执行到打印B.x.a1。

然后实例化并初始化B的实例b。

打印b.x.a1,会查找类属性b.x,指向A的实例,所以返回A实例的属性a1的值。

描述器定义

Python中,一个类实现了 __get__ 、 __set__ 、 __delete__ 三个方法中的任何一个方法,就是描述器。实现这 三个中的某些方法,就支持了描述器协议。

仅实现了 __get__ ,就是非数据描述符 non-data descriptor

实现了 __get__ 、 __set__ 就是数据描述符 data descriptor

如果一个类的类属性设置为描述器实例,那么它被称为owner属主。

当该类的该类属性被查找、设置、删除时,就会调用描述器相应的方法。

属性的访问顺序

为上例中的类B增加实例属性x

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
class A:

def __init__(self):

self.a1 = 'a1'

print('A.init')

def __get__(self, instance, owner):

print("A.__get__ {} {} {}".format(self, instance, owner))

return self

class B:

x = A()

def __init__(self):

print('B.init')

self.x = 'b.x' # 增加实例属性x

print('-'*20)

print(B.x)

print(B.x.a1)

print('='*20)

b = B()

print(b.x)

print(b.x.a1) # AttributeError: 'str' object has no attribute 'a1'

类A只实现了__get__()方法,b.x访问到了实例的属性,而不是描述器。

继续修改代码,为类A增加 __set__ 方法。

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
44
45
class A:

def __init__(self):

self.a1 = 'a1'

print('A.init')

def __get__(self, instance, owner):

print("A.__get__ {} {} {}".format(self, instance, owner))

return self

def __set__(self, instance, value):

print('A.__set__ {} {} {}'.format(self, instance, value))

self.data = value

class B:

x = A()

def __init__(self):

print('B.init')

self.x = 'b.x' # 增加实例属性x

print('-'*20)

print(B.x)

print(B.x.a1)

print('='*20)

b = B()

print(b.x) # 返回什么

print(b.x.a1) # 返回什么

print(b.x.data) # 返回什么?

所有的b.x就会访问描述器的__get__()方法,代码中返回的self就是描述器实例,它的实例字典中就保存着a1和data 属性,可以打印b.x.__dict__就可以看到这些属性。


属性查找顺序

实例的 __dict__ 优先于 非数据描述器

数据描述器 优先于 实例的 __dict__

__delete__ 方法有同样的效果,有了这个方法,也是数据描述器。

尝试着增加下面的2行代码,看看字典的变化

b.x = 500

B.x = 600

b.x = 500,这是调用数据描述器的 __set__ 方法,或调用非数据描述器的实例覆盖。

B.x = 600,赋值即定义,这是覆盖类属性。把描述器给替换了。


###

Python中的描述器

描述器在Python中应用非常广泛。

Python的方法(包括staticmethod()和classmethod())都实现为非数据描述器。因此,实例可以重新定义和覆盖 方法。这允许单个实例获取与同一类的其他实例不同的行为。

property()函数实现为一个数据描述器。因此,实例不能覆盖属性的行为。

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
class A:

@classmethod

def foo(cls): # 非数据描述器

pass

@staticmethod # 非数据描述器

def bar():

pass

@property # 数据描述器

def z(self):

return 5

def getfoo(self): # 非数据描述器

return self.foo

def __init__(self): # 非数据描述器

self.foo = 100

self.bar = 200

#self.z = 300

a = A()

print(a.__dict__)

print(A.__dict__)

foo、bar都可以在实例中覆盖,但是z不可以。


新增方法

3.6新增描述器方法 __set_name__ ,它在属主类构建的时候就会调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class A:

def init(self):

print('A init')

def get(self, instance, owner):

print(1, self, instance, owner)

return self

def set_name(self, owner, name):
print(2, self, owner, name)

self.name = name

class B:

x = A() # 类属性创建时调用描述器的set_name方法 print('-' * 30)

print(B().x)
感谢支持 !
0%