zoukankan      html  css  js  c++  java
  • 第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)
  • 相关阅读:
    Java EE企业应用发展
    黄金点游戏
    C++ Word Count 发布程序
    C++原创应用类库和工具类库
    软件之魂
    latex表格multirow的使用
    web service和ejb的区别
    RPC
    hashcode()和equals()方法
    JSON
  • 原文地址:https://www.cnblogs.com/luoxun/p/13636252.html
Copyright © 2011-2022 走看看