• Python描述符详解


    一,什么是描述符?

    • 参考:https://www.cnblogs.com/Jimmy1988/p/6808237.html
    • 官方定义:python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问。这些方法由__get__()__set__()__delete()。如果这些方法中的任何一个被定义在一个对象中,这个对象就是一个描述符。

    二,__dict__属性

    • 作用:字典类型,存放本对象的属性,key(键)即为属性名,value(值)即为属性的值,形式为{attr_key : attr_value}

    • 对象属性的访问顺序:

      • 实例属性
      • 类属性
      • 父类属性
      • __getattr__()方法
    class Test:
        cls_val = 1
    
        def __init__(self):
            self.ins_val = 10
    
    t = Test()
    print(Test.__dict__)
    """
    {'__module__': '__main__', 'cls_val': 1, '__init__': <function Test.__init__ at 0x000001FA72913E18>,
     '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, 
     '__doc__': None}
    """
    print(t.__dict__)
    """
    {'ins_val': 10}
    """
    
    # 更改实例t的属性cls_val,只是新增了该属性,并不影响类Test的属性cls_val
    t.cls_val = 20
    print(t.__dict__)
    """
    {'ins_val': 10, 'cls_val': 20}
    """
    print(Test.__dict__)
    """
    {'__module__': '__main__', 'cls_val': 1, '__init__': <function Test.__init__ at 0x000001FA72913E18>,
     '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, 
     '__doc__': None}
    """
    
    # 更改了类Test的属性cls_val的值,由于事先增加了实例t的cls_val属性,因此不会改变实例的cls_val值(井水不犯河水)
    Test.cls_val = 30
    print(t.__dict__)
    """
    {'ins_val': 10, 'cls_val': 20}
    """
    print(Test.__dict__)
    """
    {'__module__': '__main__', 'cls_val': 30, '__init__': <function Test.__init__ at 0x000001FA72913E18>,
     '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, 
     '__doc__': None}
    """
    

    从以上代码可以看出,实例t的属性并不包含cls_valcls_val是属于类Test的。

    三,魔法方法:__get__(), __set__(), __delete__()

    • 方法的原型为:
      • __get__(self, instance, owner)
      • __set__(self, instance, value)
      • __del__(self, instance)
    class Desc:
    
        def __get__(self, instance, owner):
            print("__get__...")
            print("self : 		", self)
            print("instance : 	", instance)
            print("owner : 	", owner)
            print('=' * 40, "
    ")
    
        def __set__(self, instance, value):
            print('__set__...')
            print("self : 		", self)
            print("instance : 	", instance)
            print("value : 	", value)
            print('=' * 40, "
    ")
    
    
    class TestDesc:
        x = Desc()
    
    
    t = TestDesc()
    print(t.x)
    """
    __get__...
    self : 		 <__main__.Desc object at 0x000001F28742C8D0>
    instance : 	 <__main__.TestDesc object at 0x000001F287458E10>
    owner : 	 <class '__main__.TestDesc'>
    ======================================== 
    """
    

    可以看到,实例化类TestDesc后,调用对象t访问其属性x,会自动调用类Desc __get__方法,由输出信息可以看出:

    • self: Desc的实例对象,其实就是TestDesc的属性x
    • instance: TestDesc的实例对象,其实就是t
    • owner: 即谁拥有这些东西,当然是TestDesc这个类,它是最高统治者,其他的一些都是包含在它的内部或者由它生出来的,是instance所属的类

    到此,我可以揭开小小的谜底了,其实,Desc类就是是一个描述符(描述符是一个类哦),为啥呢?因为类Desc定义了方法 __get__, __set__.

    所以,某个类,只要是内部定义了方法 __get__, __set__, __delete__ 中的一个或多个,就可以称为描述符

    问题1. 为什么访问 t.x的时候,会直接去调用描述符的 __get__() 方法呢?

    • 访问Owner__getattribute__()方法(其实就是 TestDesc.__getattribute__()),首先访问实例属性,发现没有,然后去访问类TestDesc的属性,找到了!

    • 判断属性 x 为一个描述符时,它就会做一些变动了,将 TestDesc.x 转化为 TestDesc.__dict__['x'].__get__(t, TestDesc) 来访问

    • 进入类Desc__get__()方法,进行相应的操作

    问题2. 描述符的对象 x 其实是类 TestDesc 的类属性,那么可不可以把它变成实例属性呢?

    class Desc(object):
        def __init__(self, name):
            self.name = name
    
        def __get__(self, instance, owner):
            print("__get__...")
            print('name = ', self.name)
            print('=' * 40, "
    ")
    
        def __set__(self, instance, value):
            print("__set__...")
    
    
    class TestDesc(object):
        x = Desc('x')
    
        def __init__(self):
            self.x = Desc('x')
            self.y = Desc('y')
    
        def __getattribute__(self, item):
            print(item,'1111')
            return super(TestDesc, self).__getattribute__(item)
    
    t = TestDesc()
    print(t.x)
    print(t.y)
    print(t.__dict__)
    """
    __set__...
    __get__...
    name =  x
    ======================================== 
    
    <__main__.Desc object at 0x000001A44CA58E10>
    {'y': <__main__.Desc object at 0x000001A44CA58E10>}
    """
    """
    为什么没有打印t.y的信息呢?
    因为没有访问__get__() 方法啊。
    因为实例化t的时候,并没有y属性。而是在__init__方法中创建的实例属性。
    所以在第一步查找实例属性的时候就找到了y属性。
    
    而x属性是TestDesc的类属性,在t实例化之前就保存在TestDesc的类属性中。
    在__init__方法中给实例x属性赋值时,发现TestDesc的类属性中有x,而且x是一个描述符。
    那么会触发x的__set__方法。而不会创建实例属性。(如果没有__set__方法,则会成功创建实例属性。)
    """
    

    问题3. 如果 类属性的描述符对象 和 实例属性描述符的对象 同名时,咋整?

    class Desc(object):
        def __init__(self, name):
            self.name = name
            print("__init__(): name = ", self.name)
    
        def __get__(self, instance, owner):
            print("__get__() ...")
            return self.name
    
        def __set__(self, instance, value):
            print("__set__() ...")
            self.value = value
    
    
    class TestDesc(object):
        _x = Desc('x')
    
        def __init__(self, x):
            self._x = x
    
    
    # 以下为测试代码
    t = TestDesc(10)
    print(t._x)
    """
    __init__(): name =  x
    __set__() ...
    __get__() ...
    x
    """
    
    
    print(t.__dict__) # {}
    print(TestDesc.__dict__)
    """
    {'__module__': '__main__', 
    '_x': <__main__.Desc object at 0x000001D3BD1DC898>,
     '__init__': <function TestDesc.__init__ at 0x000001D3BD26A598>, 
     '__dict__': <attribute '__dict__' of 'TestDesc' objects>,
      '__weakref__': <attribute '__weakref__' of 'TestDesc' objects>, 
      '__doc__': None}
    """
    
    """
    不对啊,按照惯例,t._x 会去调用 __getattribute__() 方法,然后找到了 实例t 的 _x 属性就结束了,
    为啥还去调用了描述符的 __get__() 方法呢?
    
    这是因为,_x属性是TestDesc的类属性,在t实例化之前就保存在TestDesc的类属性中。
    在__init__方法中给实例_x属性赋值时,发现TestDesc的类属性中有_x,而且_x是一个描述符。
    那么会触发x的__set__方法。而不会创建实例属性。(如果没有__set__方法,则会成功创建实例属性。)
    """
    

    如果删除__set__()方法会发生什么?

    class Desc(object):
        def __init__(self, name):
            self.name = name
            print("__init__(): name = ", self.name)
    
        def __get__(self, instance, owner):
            print("__get__() ...")
            return self.name
    
    
    class TestDesc(object):
        _x = Desc('x')
    
        def __init__(self, x):
            self._x = x
    
    
    t = TestDesc(10)
    print(t._x)
    """
    __init__(): name =  x
    10
    """
    
    
    print(t.__dict__) # {'_x': 10}
    print(TestDesc.__dict__)
    """
    {'__module__': '__main__', '_x': <__main__.Desc object at 0x000001E4EA63C8D0>,
     '__init__': <function TestDesc.__init__ at 0x000001E4EA6396A8>,
      '__dict__': <attribute '__dict__' of 'TestDesc' objects>,
     '__weakref__': <attribute '__weakref__' of 'TestDesc' objects>,
      '__doc__': None}
    """
    
    """
    这次没有调用__get__()方法。这是因为在TestDesc的__init__中创建实例属性时,发现_x是描述符。
    但是_x是非数据描述符(只有__get__),依然创建了实例属性。
    """
    

    问题4. 什么是数据描述符,什么是非数据描述符?

    • 一个类,如果只定义了 __get__() 方法,而没有定义 __set__(), __delete__() 方法,则认为是非数据描述符; 反之,则成为数据描述符

    四,总结

    • 实例化t时,如果在__init__中尝试创建和类属性中的描述符同名的属性;如果是数据描述符,则触发描述符的__set__方法,不创建实例属性;如果是非数据描述符,则正常创建实例属性。
    • 查找属性的顺序:
      • __getattribute__(), 无条件调用
      • 实例属性(实例的__dict__
      • 类属性(类的__dict__
      • 如果在类属性中发现要查找的属性,尝试调用这个属性的__get__方法。没有就返回这个属性。
      • 父类的属性
      • __getattr__() 方法
  • 相关阅读:
    FTDI端口或ISP端口编程方式的比较
    PhantomX Pincher Arm---设置ArbotiX和Arduino软件
    PhantomX Pincher机器人机械臂那些事儿
    使用aplay实现音频播放
    TurtleBot3-讲在前面的话
    ROS-ROS命令(五) rosservice:ROS服务
    ROS-ROS命令(四)rostopic: ROS话题
    ROS-ROS命令(三)ROS 信息命令
    jquery下拉框应用
    jquery的attr获取表单checked 布尔值问题
  • 原文地址:https://www.cnblogs.com/zyyhxbs/p/13492159.html
Copyright © 2020-2023  润新知