学习笔记说明
这是一篇Python的学习笔记,关于Python描述符,仅做记录
问题
import math
class Circle:
def __init__(self, radius):
self.radius = radius
self.area = radius**2 * math.pi
self.diameter = radius * 2
c = Circle(5)
print(c.radius, c.area, c.diameter) # 没问题
# 出现问题
# 1. 不符合逻辑的赋值
c.radius = -10
# 2. 数据不一致
c.radius = 10
print(c.radius, c.area, c.diameter) # 数据不一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
描述符协议
认识术语
描述符协议规定,只要一个类,实现了__get__、__set__、__delete__任意一个实例方法:
- 该类称之为描述符类
- 该类的对象称之为描述符对象,也可以简称为描述符
- 如果描述符类实现了
__set__、__delete__任意一个
它的对象又称之为数据型描述符(Data Descriptor) - 如果描述符类只实现了
__get__
它的对象又称为非数据型描述符(Non-Data Descriptor)
- 如果描述符类实现了
- 描述符对象只有是类属性时才有意义
所以描述符通常又称为属性描述符
# 描述符类
class MyDescriptor:
def __get__(self, instance, owner):
pass
def __set__(self, instance, value):
pass
def __delete__(self, instance):
pass
class MyClass:
my_attr = MyDescriptor() # 描述符、属性描述符、数据描述符
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
访问顺序
当访问实例成员时,按照以下优先级查找成员:
- 数据型描述符(类属性)
- 实例属性(
instance.__dict__) - 类属性(普通)
- 父类…
# 描述符类
class MyDescriptor:
def __get__(self, instance, owner):
pass
def __set__(self, instance, value):
pass
def __delete__(self, instance):
pass
class MyClass:
my_attr = MyDescriptor() # 描述符、属性描述符、数据描述符
def __init__(self, value):
self.my_attr = value # 赋值的是类属性
ins = MyClass(10)
print(ins.__dict__) # 不包含 my_attr
print(ins.my_attr) # 访问的是类属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
读写删操作
描述符会拦截对它的读、写、删操作
# 描述符类
class MyDescriptor:
def __get__(self, instance, owner):
print("__get__ called")
pass
def __set__(self, instance, value):
print("__set__ called")
pass
def __delete__(self, instance):
print("__delete__ called")
pass
class MyClass:
my_attr1 = MyDescriptor() # 描述符、属性描述符、数据描述符
my_attr2 = MyDescriptor() # 描述符、属性描述符、数据描述符
ins = MyClass()
ins.my_attr1 # MyDescriptor.__get__(MyClass.my_attr, ins, MyClass)
ins.my_attr1 = 10 # MyDescriptor.__set__(MyClass.my_attr, ins, 10)
del ins.my_attr1 # MyDescriptor.__delete__(MyClass.my_attr, ins)
MyClass.my_attr2 # MyDescriptor.__get__(MyClass.my_attr, None, MyClass)
MyClass.my_attr2 = 20 # 直接覆盖 my_attr2,my_attr2 不再是描述符了
del MyClass.my_attr2 # 直接删除 my_attr2 属性,my_attr2 不再存在
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
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
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
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
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
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
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
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
最佳实践:
- 绝大部分时候都使用数据型描述符
- 对描述符的访问,永远通过实例去访问
钩子函数
目前,描述符类中仅提供了一个钩子函数__set_name__
Python3.6版本加入
# 描述符类
class MyDescriptor:
def __set_name__(self, owner, name):
print(f"__set_name__ called with owner={owner}, name={name}")
def __get__(self, instance, owner):
pass
def __set__(self, instance, value):
pass
def __delete__(self, instance):
pass
class MyClass:
# 这里会触发__set_name__
# 时间点:完成赋值后
# 作用:让描述符知道自己被赋值给了哪个类的哪个属性
my_attr1 = MyDescriptor()
my_attr2 = MyDescriptor()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
解决最初的问题
import math
class RadiusDescriptor:
def __set__(self, instance, value):
if value < 0:
raise ValueError("半径不能为负数")
instance._radius = value
def __get__(self, instance, owner):
return instance._radius
def __delete__(self, instance):
raise AttributeError("radius 不能被删除")
class AreaDescriptor:
def __get__(self, instance, owner):
return instance.radius**2 * math.pi
def __set__(self, instance, value):
raise AttributeError("area 是只读属性,不能设置")
def __delete__(self, instance):
raise AttributeError("area 不能被删除")
class DiameterDescriptor:
def __get__(self, instance, owner):
return instance.radius * 2
def __set__(self, instance, value):
raise AttributeError("diameter 是只读属性,不能设置")
def __delete__(self, instance):
raise AttributeError("diameter 不能被删除")
class Circle:
radius = RadiusDescriptor()
area = AreaDescriptor()
diameter = DiameterDescriptor()
def __init__(self, radius):
self.radius = radius
c1 = Circle(5) # 没问题
print(c1.radius) # 输出 5
print(c1.area) # 输出 78.53981633974483
print(c1.diameter) # 输出 10
c1.radius = 10 # 没问题
print(c1.radius) # 输出 10
print(c1.area) # 输出 314.1592653589793
print(c1.diameter) # 输出 20
c1.area = 100 # 报错
print(c1.area)
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
46
47
48
49
50
51
52
53
54
55
56
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
46
47
48
49
50
51
52
53
54
55
56
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
46
47
48
49
50
51
52
53
54
55
56
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
46
47
48
49
50
51
52
53
54
55
56
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
46
47
48
49
50
51
52
53
54
55
56
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
46
47
48
49
50
51
52
53
54
55
56
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
46
47
48
49
50
51
52
53
54
55
56
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
46
47
48
49
50
51
52
53
54
55
56
@property 装饰器
@property 可以将方法变成属性,访问时像访问普通属性一样,不需要加括号:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""获取半径"""
return self._radius
@radius.setter
def radius(self, value):
"""设置半径,带验证"""
if value < 0:
raise ValueError("半径不能为负数")
self._radius = value
@radius.deleter
def radius(self):
"""删除半径"""
print("删除半径")
del self._radius
@property
def area(self):
"""计算面积(只读属性)"""
import math
return math.pi * self._radius ** 2
@property
def diameter(self):
"""计算直径(只读属性)"""
return self._radius * 2
c = Circle(5)
print(c.radius) # 5 —— 调用 getter
print(c.area) # 78.54... —— 自动计算
print(c.diameter) # 10
c.radius = 10 # 调用 setter
print(c.area) # 314.15...
# c.area = 100 # AttributeError! area 没有 setter
# c.radius = -5 # ValueError! 半径不能为负数
# del c.radius # 调用 deleter
# print(c.radius) # AttributeError!
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
46
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
46
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
46
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
46
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
46
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
46
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
46
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
46
关键点:
@property将方法变成只读属性@属性名.setter定义可写属性(必须和 property 同名)@属性名.deleter定义可删除属性
property的实现
import math
# class RadiusDecorator:
# def __set__(self, instance, value):
# if value < 0:
# raise ValueError("半径不能是负数")
# instance._radius = value
# def __get__(self, instance, owner):
# return instance._radius
# def __delete__(self, instance):
# raise AttributeError("不能删除半径属性")
class MyProperty:
def __init__(self, get_func=None, set_func=None, delete_func=None):
self.get_func = get_func
self.set_func = set_func
self.delete_func = delete_func
def __get__(self, instance, owner):
if self.get_func is None:
raise AttributeError("属性不可读")
return self.get_func(instance)
def __set__(self, instance, value):
if self.set_func is None:
raise AttributeError("属性不可写")
self.set_func(instance, value)
def __delete__(self, instance):
if self.delete_func is None:
raise AttributeError("属性不可删除")
self.delete_func(instance)
def setter(self, func):
self.set_func = func
return self
def deleter(self, func):
self.delete_func = func
return self
class Circle:
@MyProperty
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("半径不能是负数")
self._radius = value
@radius.deleter
def radius(self):
raise AttributeError("不能删除半径属性")
@MyProperty
def area(self):
return math.pi * self.radius**2
@MyProperty
def circumference(self):
return 2 * math.pi * self.radius
@MyProperty
def diameter(self):
return 2 * self.radius
def __init__(self, radius=5):
self.radius = radius
c = Circle(5)
print(c.radius, c.area, c.circumference, c.diameter)
c.radius = 10
print(c.radius, c.area, c.circumference, c.diameter)
# 装饰器(可调用对象) --> 返回任何东西
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
应用场景
1. 惰性计算
class LazyProperty:
"""惰性加载属性:只在第一次访问时计算"""
def __init__(self, func):
self.func = func
self.name = func.__name__
def __get__(self, instance, owner):
if instance is None:
return self
value = self.func(instance)
# 将结果缓存到实例的属性中
setattr(instance, self.name, value)
return value
class DataLoader:
def __init__(self, file_path):
self.file_path = file_path
@LazyProperty
def data(self):
print(f"正在加载文件: {self.file_path}")
# 模拟耗时操作
return [1, 2, 3, 4, 5]
loader = DataLoader("data.txt")
print(loader.data) # 正在加载文件: data.txt
# [1, 2, 3, 4, 5]
print(loader.data) # [1, 2, 3, 4, 5] —— 不再加载,直接从属性读取
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
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
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
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
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
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
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
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
2. 类型检查
class Typed:
"""强制类型检查的描述符"""
def __init__(self, expected_type):
self.expected_type = expected_type
self.name = None
def __set_name__(self, owner, name):
self.name = name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, self.expected_type):
raise TypeError(
f"{self.name} 必须是 {self.expected_type.__name__} 类型,"
f"而不是 {type(value).__name__}"
)
instance.__dict__[self.name] = value
class Student:
name = Typed(str)
age = Typed(int)
score = Typed(float)
def __init__(self, name, age, score):
self.name = name
self.age = age
self.score = score
s = Student("Alice", 20, 85.5)
# s.age = "20" # TypeError! age 必须是 int 类型,而不是 str
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
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
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
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
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
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
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
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
作业(可使用AI)
一、实现只读属性
编写一个类 ImmutablePoint,创建后不能修改坐标:
p = ImmutablePoint(3, 4)
print(p.x) # 3
print(p.y) # 4
# p.x = 10 # AttributeError! 不能修改只读属性
1
2
3
4
5
2
3
4
5
1
2
3
4
5
2
3
4
5
1
2
3
4
5
2
3
4
5
1
2
3
4
5
2
3
4
5
提示: 使用 @property 但不提供 setter。
二、实现范围验证
编写一个 Temperature 类,温度必须在 -273.15(绝对零度)到 1000 之间:
t = Temperature(25)
print(t.celsius) # 25
print(t.fahrenheit) # 77.0(只读属性,自动计算)
print(t.kelvin) # 298.15(只读属性,自动计算)
# t.celsius = -300 # ValueError! 温度不能低于绝对零度
1
2
3
4
5
6
2
3
4
5
6
1
2
3
4
5
6
2
3
4
5
6
1
2
3
4
5
6
2
3
4
5
6
1
2
3
4
5
6
2
3
4
5
6
三、实现类属性计数器
编写一个描述符,记录某个类属性被访问和修改的次数:
class AccessCounter:
# 你的代码
pass
class MyClass:
value = AccessCounter(10) # 初始值为 10
obj = MyClass()
print(obj.value) # 10
print(obj.value) # 10
obj.value = 20
print(obj.value) # 20
# 查看访问和修改次数
print(AccessCounter.get_access_count()) # 3(被访问了 3 次)
print(AccessCounter.get_modify_count()) # 1(被修改了 1 次)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
四、思考题
下面代码的输出是什么?为什么?
class Descriptor:
def __get__(self, instance, owner):
print(f"__get__ called, instance={instance}, owner={owner}")
return 42
def __set__(self, instance, value):
print(f"__set__ called, instance={instance}, value={value}")
class A:
x = Descriptor()
a = A()
print(a.x)
a.x = 100
a.__dict__["x"] = "instance"
print(a.x)
print(a.__dict__)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
作业答案
一、实现只读属性
class ImmutablePoint:
def __init__(self, x, y):
self._x = x
self._y = y
@property
def x(self):
return self._x
@property
def y(self):
return self._y
p = ImmutablePoint(3, 4)
print(p.x) # 3
print(p.y) # 4
# p.x = 10 # AttributeError! 不能修改只读属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
二、实现范围验证
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("温度不能低于绝对零度")
if value > 1000:
raise ValueError("温度不能超过1000")
self._celsius = value
@property
def fahrenheit(self):
return self._celsius * 9 / 5 + 32
@property
def kelvin(self):
return self._celsius + 273.15
t = Temperature(25)
print(t.celsius) # 25
print(t.fahrenheit) # 77.0(只读属性,自动计算)
print(t.kelvin) # 298.15(只读属性,自动计算)
# t.celsius = -300 # ValueError! 温度不能低于绝对零度
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
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
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
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
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
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
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
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
三、实现类属性计数器
class AccessCounter:
_access_count = 0
_modify_count = 0
def __init__(self, init_value):
self._value = init_value
def __get__(self, instance, owner):
AccessCounter._access_count += 1
return self._value
def __set__(self, instance, value):
AccessCounter._modify_count += 1
self._value = value
@classmethod
def get_access_count(cls):
return cls._access_count
@classmethod
def get_modify_count(cls):
return cls._modify_count
class MyClass:
value = AccessCounter(10) # 初始值为 10
obj = MyClass()
print(obj.value) # 10
print(obj.value) # 10
obj.value = 20
print(obj.value) # 20
# 查看访问和修改次数
print(AccessCounter.get_access_count()) # 3(被访问了 3 次)
print(AccessCounter.get_modify_count()) # 1(被修改了 1 次)
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
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
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
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
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
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
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
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
四、思考题
__get__ called, instance=<__main__.A object at 0x107654830>, owner=<class '__main__.A'>
42
__set__ called, instance=<__main__.A object at 0x107654830>, value=100
__get__ called, instance=<__main__.A object at 0x107654830>, owner=<class '__main__.A'>
42
{'x': 'instance'}
1
2
3
4
5
6
2
3
4
5
6
1
2
3
4
5
6
2
3
4
5
6
1
2
3
4
5
6
2
3
4
5
6
1
2
3
4
5
6
2
3
4
5
6