第45讲:魔方方法——属性访问

一 属性访问相关的知识

1 几个常用的方法:

  • __getattr__(self,name):定义当用户试图获取一个不存在的属性时的行为
  • __getattribute__(self,name):定义当该类的属性被访问时的行为
  • __setattr__(self,name,value):定义当一个属性被设置时的行为
  • __delattr__(self,name):定义当一个属性被删除时的行为

2 举例:

 1 >>> class C(object):
 2 ...     def __getattribute__(self,name):
 3 ...             print("getattribute")
 4 ...             return super().__getattribute__(name)
 5 ...     def __getattr__(self,name):
 6 ...             print("getattr")
 7 ...     def __setattr__(self,name,value):
 8 ...             print("setattr")
 9 ...             super().__setattr__(name,value)
10 ...     def __delattr__(self,name):
11 ...             print("delattr")
12 ...             super().__delattr__(name)
13 ...
14 >>> c = C()
15 >>> c.x
16 getattribute
17 getattr
18 >>> c.x = object
19 setattr
20 >>> c.x
21 getattribute
22 <class 'object'>
23 >>> c.x = 5
24 setattr
25 >>> c.x
26 getattribute
27 5

3 死循环陷阱——课堂练习

练习要求:

  • 写一个矩形类,默认有宽和高两个属性;
  • 如果为一个叫square的属性赋值,那么说明这是一个正方形,值就是正方形的边长,此时宽和高都应该等于边长。

会引起死循环的代码:

 1 class Rectangle(object):
 2     def __init__(self,width=0,height=0):
 3         self.width = width            # 该句是赋值语句,执行过程中已经调用了类本身的__setattr__(self,name,value)方法
 4         self.height = height
 5         
 6     def __setattr__(self,name,value):
 7         if name == 'square':
 8             self.width = value
 9             self.height = value
10         else:
11             self.name = value          # 此时再次进行赋值,同样会调用类本身的__setattr__(self,name,value)方法,从而陷入死循环
12     
13     def getArea(self):
14         return self.width * self.height
15     
16     def getCirc(self):
17         return 2 * (self.width + self.height)

改正后的代码:

 1 class Rectangle(object):
 2     def __init__(self,width=0,height=0):
 3         self.width = width            # 该句是赋值语句,执行过程中已经调用了类本身的__setattr__(self,name,value)方法
 4         self.height = height
 5         
 6     def __setattr__(self,name,value):
 7         if name == 'square':
 8             self.width = value
 9             self.height = value
10         else:
11             super().__setattr__(name,value)  # 通过调用父类的__setattr__(self,name,value)方法完成重新赋值的操作,不会造成死循环
12             # self.__dict__[name] = value    # 通过__dict__属性以字典的方式进行赋值,此过程不会调用__setattr__(self,name,value)方法
13     
14     def getArea(self):
15         return self.width * self.height
16     
17     def getCirc(self):
18         return 2 * (self.width + self.height)

 

二 课后作业

测试题部分:

0. 请问以下代码的作用是什么?这样写正确吗?(如果不正确,请改正)

1 def __setattr__(self, name, value):
2         self.name = value + 1

答:这段代码试图在对象的属性发生赋值操作的时候,将实际的值 +1赋值给相应的属性。但这么写法是错误的,因为每当属性被赋值的时候, __setattr__() 会被调用,而里边的 self.name = value + 1 语句又会再次触发 __setattr__() 调用,导致无限递归。

代码应该这样写:

1 def __setattr__(self, name, value):
2         self.__dict__[name] = value + 1

或者:

1 def __setattr__(self, name, value):
2         super().__setattr__(name, value+1)

 

1. 自定义该类的属性被访问的行为,你应该重写哪个魔法方法?
答:__getattribute__(self, name)

2. 在不上机验证的情况下,你能推断以下代码分别会显示什么吗?

 1 >>> class C:
 2         def __getattr__(self, name):
 3                 print(1)
 4         def __getattribute__(self, name):
 5                 print(2)
 6         def __setattr__(self, name, value):
 7                 print(3)
 8         def __delattr__(self, name):
 9                 print(4)
10 
11                 
12 >>> c = C()
13 >>> c.x = 1
14 # 位置一,请问这里会显示什么?
15 >>> print(c.x)
16 # 位置二,请问这里会显示什么?

答:位置一会显示 3,因为 c.x = 1 是赋值操作,所以会访问 __setattr__() 魔法方法;

       位置二会显示 2 和 None,因为 x 是属于实例对象 c 的属性,所以 c.x 是访问一个存在的属性,因此会访问 __getattribute__() 魔法方法,但我们重写了这个方法,使得它不能按照正常的逻辑返回属性值,而是打印一个 2 代替,由于我们没有写返回值,所以紧接着返回 None 并被 print() 打印出来。

3. 在不上机验证的情况下,你能推断以下代码分别会显示什么吗?

 1 >>> class C:
 2         def __getattr__(self, name):
 3                 print(1)
 4                 return super().__getattr__(name)
 5         def __getattribute__(self, name):
 6                 print(2)
 7                 return super().__getattribute__(name)
 8         def __setattr__(self, name, value):
 9                 print(3)
10                 super().__setattr__(name, value)
11         def __delattr__(self, name):
12                 print(4)
13                 super().__delattr__(name)
14                 
15 >>> c = C()
16 >>> c.x

答:在不上机的情况下,我相信80%以上的鱼油很难猜到正确的答案T_T

 1 >>> c = C()
 2 >>> c.x
 3 2
 4 1
 5 Traceback (most recent call last):
 6   File "<pyshell#31>", line 1, in <module>
 7     c.x
 8   File "<pyshell#29>", line 4, in __getattr__
 9     return super().__getattr__(name)
10 AttributeError: 'super' object has no attribute '__getattr__'

为什么会如此显示呢?我们来分析下:首先 c.x 会先调用 __getattribute__() 魔法方法,打印 2;然后调用 super().__getattribute__(),找不到属性名 x,因此会紧接着调用 __getattr__() ,于是打印 1;但是你猜到了开头没猜到结局……当你希望最后以 super().__getattr__() 终了的时候,Python 竟然告诉你 AttributeError,super 对象木有 __getattr__ !!

求证:

1 >>> dir(super)
2 ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__self_class__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__thisclass__']

 

4. 请指出以下代码的问题所在:

1 class Counter:
2         def __init__(self):
3                 self.counter = 0
4         def __setattr__(self, name, value):
5                 self.counter += 1
6                 super().__setattr__(name, value)
7         def __delattr__(self, name):
8                 self.counter -= 1
9                 super().__delattr__(name)

答:初学者重写属性魔法方法很容易陷入的一个误区就是木有“观前顾后”。
以下注释:

 1 class Counter:
 2         def __init__(self):
 3                 self.counter = 0 # 这里会触发 __setattr__ 调用
 4         def __setattr__(self, name, value):
 5                 self.counter += 1
 6 “““既然需要 __setattr__ 调用后才能真正设置 self.counter 的值,所以这时候 self.counter 还没有定义,所以没法 += 1,错误的根源。”””
 7                 super().__setattr__(name, value)
 8         def __delattr__(self, name):
 9                 self.counter -= 1
10                 super().__delattr__(name)

 

动动手部分:

0. 按要求重写魔法方法:当访问一个不存在的属性时,不报错且提示“该属性不存在!”
代码清单:

1 class Demo(object):
2     def __getattr__(self,name):
3         return "该属性不存在!"
4 
5 demo = Demo()
6 print(demo.x)

 

1. 编写 Demo 类,使得下边代码可以正常执行:

1 >>> demo = Demo()
2 >>> demo.x
3 'FishC'
4 >>> demo.x = "X-man"
5 >>> demo.x
6 'X-man'

代码清单:

 1 class Demo(object):
 2     def __getattr__(self,name):
 3         self.name = 'FishC'
 4         return self.name
 5 
 6 demo = Demo()
 7 print(demo.x)
 8 
 9 demo.x = "X-man"
10 print(demo.x)

 

2. 修改上边【测试题】第 4 题,使之可以正常运行:编写一个 Counter 类,用于实时检测对象有多少个属性。
程序实现如下:

 1 >>> c = Counter()
 2 >>> c.x = 1
 3 >>> c.counter
 4 1
 5 >>> c.y = 1
 6 >>> c.z = 1
 7 >>> c.counter
 8 3
 9 >>> del c.x
10 >>> c.counter
11 2

代码清单:WP"OqI3%

 1 class Counter(object):
 2     k = []
 3     def __init__(self):
 4         self.counter = 0
 5     def __setattr__(self,name,value):
 6         if name != 'counter':
 7             if name not in self.k:
 8                 self.counter += 1
 9                 print(f"set: {self.counter}")
10                 self.k.append(name)
11         super().__setattr__(name,value)
12     def __delattr__(self,name):
13         if name in self.k:
14             self.counter -= 1
15             print(f"del:{self.counter}")
16             self.k.remove(name)
17         super().__delattr__(name)
18 
19 
20 c = Counter()
21 c.x = 1
22 print(c.counter)
23 
24 c.y = 2
25 c.z = 3
26 print(c.counter)
27 
28 del c.x 
29 print(c.counter)

 

posted @ 2020-09-08 23:00  洛兰123  阅读(242)  评论(0编辑  收藏  举报