您的位置:365bet体育备用网址器 > 应用 > 它可以通过方法重写属性的访问,这些方法包括

它可以通过方法重写属性的访问,这些方法包括

2019-12-26 18:32

Python描述符 (descriptor) 详解,pythondescriptor

1、什么是描述符?

   python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问。这些方法有 __get__(), __set__(), 和__delete__()。如果这些方法中的任何一个被定义在一个对象中,这个对象就是一个描述符。

  以上为官方定义,纯粹为了装逼使用,一般人看这些定义都有一种问候祖先的冲动!

  没关系,看完本文,你就会理解什么叫描述符了!

2、讲解描述符前,先看一下属性:__dict__ (每个对象均具备该属性)

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

对象属性的访问顺序:

①.实例属性

②.类属性

③.父类属性

④.__getattr__()方法

以上顺序,切记切记!

 1 class Test(object):
 2     cls_val = 1
 3     def __init__(self):
 4         self.ins_val = 10
 5 
 6         
 7 >>> t=Test()
 8 >>> Test.__dict__
 9 mappingproxy({'__module__': '__main__', 'cls_val': 1, '__init__': <function Test.__init__ at 0x0000000002E35D08>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None})
10 >>> t.__dict__
11 {'ins_val': 10}
12 
13 >>> type(x)==X
14 True
15 
16 #更改实例t的属性cls_val,只是新增了该属性,并不影响类Test的属性cls_val
17 >>> t.cls_val = 20
18 >>> t.__dict__
19 {'ins_val': 10, 'cls_val': 20}
20 >>> Test.__dict__
21 mappingproxy({'__module__': '__main__', 'cls_val': 1, '__init__': <function Test.__init__ at 0x0000000002E35D08>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None})
22 
23 #更改了类Test的属性cls_val的值,由于事先增加了实例t的cls_val属性,因此不会改变实例的cls_val值(井水不犯河水)
24 >>> Test.cls_val = 30
25 >>> t.__dict__
26 {'ins_val': 10, 'cls_val': 20}
27 >>> Test.__dict__
28 mappingproxy({'__module__': '__main__', 'cls_val': 30, '__init__': <function Test.__init__ at 0x0000000002E35D08>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None})

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

 3、魔法方法:__get__(), __set__(), __delete__()

   方法的原型为:

  ① __get__(self, instance, owner)

  ② __set__(self, instance, value)

  ③ __del__(self, instance)

  那么以上的 self, instance owner到底指社么呢?莫急莫急,听我慢慢道来!

  首先我们先看一段代码:

 1 #代码 1
 2 
 3 class Desc(object):
 4     
 5     def __get__(self, instance, owner):
 6         print("__get__...")
 7         print("self : tt", self)
 8         print("instance : t", instance)
 9         print("owner : t", owner)
10         print('='*40, "n")
11         
12     def __set__(self, instance, value):
13         print('__set__...')
14         print("self : tt", self)
15         print("instance : t", instance)
16         print("value : t", value)
17         print('='*40, "n")
18 
19 
20 class TestDesc(object):
21     x = Desc()
22 
23 #以下为测试代码
24 t = TestDesc()
25 t.x
26 
27 #以下为输出信息:
28 
29 __get__...
30 self :          <__main__.Desc object at 0x0000000002B0B828>
31 instance :      <__main__.TestDesc object at 0x0000000002B0BA20>
32 owner :      <class '__main__.TestDesc'>
33 ======================================== 

 

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

  ① self: Desc的实例对象,其实就是TestDesc的属性x

  ② instance: TestDesc的实例对象,其实就是t

  ③ owner: 即谁拥有这些东西,当然是 TestDesc这个类,它是最高统治者,其他的一些都是包含在它的内部或者由它生出来的

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

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

 

说到这里,我们的任务还远远没有完成,还存在很多很多的疑点?

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

    答:t为实例,访问t.x时,根据常规顺序,

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

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

      然后:进入类Desc的 __get__()方法,进行相应的操作

  问题2. 从上面 代码1 我们看到了,描述符的对象 x 其实是类 TestDesc  的类属性,那么可不可以把它变成实例属性呢?

    答:我说了你不算,你说了也不算,解释器说了算,看看解释器怎么说的。

 1 #代码 2
 2 
 3 class Desc(object):
 4     def __init__(self, name):
 5         self.name = name
 6     
 7     def __get__(self, instance, owner):
 8         print("__get__...")
 9         print('name = ',self.name) 
10         print('='*40, "n")
11 
12 class TestDesc(object):
13     x = Desc('x')
14     def __init__(self):
15         self.y = Desc('y')
16 
17 #以下为测试代码
18 t = TestDesc()
19 t.x
20 t.y
21 
22 #以下为输出结果:
23 __get__...
24 name =  x
25 ======================================== 

 

    咦,为啥没打印 t.y 的信息呢?

    因为没有访问 __get__() 方法啊,哈哈,那么为啥没有访问 __get__() 方法呢?(问题真多)

    因为调用 t.y 时刻,首先会去调用TestDesc(即Owner)的 __getattribute__() 方法,该方法将 t.y 转化为TestDesc.__dict__['y'].__get__(t, TestDesc), 但是呢,实际上 TestDesc 并没有 y这个属性,y 是属于实例对象的,所以,只能忽略了。

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

    答:还是让解释器来解释一下吧。

 1 #代码 3
 2 
 3 class Desc(object):
 4     def __init__(self, name):
 5         self.name = name
 6         print("__init__(): name = ",self.name)
 7         
 8     def __get__(self, instance, owner):
 9         print("__get__() ...")
10         return self.name
11 
12     def __set__(self, instance, value):
13         self.value = value
14         
15 class TestDesc(object):
16     _x = Desc('x')
17     def __init__(self, x):
18         self._x = x
19 
20 
21 #以下为测试代码
22 t = TestDesc(10)
23 t._x
24 
25 #输入结果
26 __init__(): name =  x
27 __get__() ...

 

    不对啊,按照惯例,t._x 会去调用 __getattribute__() 方法,然后找到了 实例t 的 _x 属性就结束了,为啥还去调用了描述符的 __get__() 方法呢?

    这就牵扯到了一个查找顺序问题:当Python解释器发现实例对象的字典中,有与描述符同名的属性时,描述符优先,会覆盖掉实例属性。

    不信?来看一下 字典 :

1 >>> t.__dict__
2 {}
3 
4 >>> TestDesc.__dict__
5 mappingproxy({'__module__': '__main__', '_x': <__main__.Desc object at 0x0000000002B0BA20>, '__init__': <function TestDesc.__init__ at 0x0000000002BC59D8>, '__dict__': <attribute '__dict__' of 'TestDesc' objects>, '__weakref__': <attribute '__weakref__' of 'TestDesc' objects>, '__doc__': None})

 

    怎么样,没骗你吧?我这人老好了,从来不骗人!

    我们再将 代码3 改进一下, 删除 __set__() 方法试试看会发生什么情况?

 1 #代码 4
 2 
 3 class Desc(object):
 4     def __init__(self, name):
 5         self.name = name
 6         print("__init__(): name = ",self.name)
 7         
 8     def __get__(self, instance, owner):
 9         print("__get__() ...")
10         return self.name
11         
12 class TestDesc(object):
13     _x = Desc('x')
14     def __init__(self, x):
15         self._x = x
16 
17 
18 #以下为测试代码
19 t = TestDesc(10)
20 t._x
21 
22 #以下为输出:
23 __init__(): name =  x

 

    我屮艸芔茻,咋回事啊?怎么木有去 调用 __get__() 方法?

    其实,还是 属性 查找优先级惹的祸,只是定义一个 __get__() 方法,为非数据描述符,优先级低于实力属性的!!

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

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

  问题5. 天天提属性查询优先级,就不能总结一下吗?

    答:好的好的,客官稍等!

    ① __getattribute__(), 无条件调用

    ② 数据描述符:由 ① 触发调用 (若人为的重载了该 __getattribute__() 方法,可能会调职无法调用描述符)

    ③ 实例对象的字典(若与描述符对象同名,会被覆盖哦)

    ④ 类的字典

    ⑤ 非数据描述符

    ⑥ 父类的字典

    ⑦ __getattr__() 方法

 

(descriptor) 详解,pythondescriptor 1、什么是描述符? python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方...

1、描述符的定义

  描述符是与特定属性互相绑定的一种协议,通过方法被触发修改属性,这些方法包括__get__(),__set__(),__delete__().将这些方法定义在类中,即可实现描述符

2、属性与__dict__

  Python中类有属于自己的字典属性,经过类的实例化的对象也同样有自己的字典属性,__dict__

 1 class Foo: 2     x=10 3     def f: 4         print('f') 5     def __init__(self,name,id): 6         self.name=name 7         self.id=id 8 f=Foo('alex','1001') 9 print(Foo.__dict__)10 print(f.__dict__)11 {'__module__': '__main__', 'x': 10, 'f': <function Foo.f at 0x000002677119A950>, '__init__': <function Foo.__init__ at 0x000002677119A840>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}12 {'name': 'alex', 'id': '1001'}

  

对象调用属性的方法查找顺序:对象字典,类字典,父类字典,__getattr__方法

3、描述符方法

__set__(self,instance,value)

__get__(self,instance,owner)

 __delete__(self,instance)

    #测试self,instance,value,owner各是何方神圣

 1 class Foo: 2     def __set__(self, instance, value): 3         print('set') 4         print 5         print(instance,value) 6     def __get__(self, instance, owner): 7         print('get') 8         print 9         print(instance,owner)10     def __delete__(self, instance):11         print('delete')12         print13         print14 class Test:15     x=Foo()    16     def __init__:17         self.x=y18 t=Test(10)19 t.x

#输出结果20 set21 <__main__.Foo object at 0x000002D26D10A438>22 <__main__.Test object at 0x000002D26D10A470> 1023 get24 <__main__.Foo object at 0x000002D26D10A438>25 <__main__.Test object at 0x000002D26D10A470> <class '__main__.Test'>

第15行x=Foo()说明x属性被Foo类所代理一般,涉及对x属性的操作可能会触发Foo类中的三个方法,t为Test实例化的对象,触发构造方法init,执行self.x=y,实际类属性与实例新增属性x是井水不犯河水,无相关联,但是类属性x是描述符属性,被Foo代理,python解释器会发现实例字典中的x属性名与类属性同名,类属性会优先覆盖。对x的操作交给Foo()代理,触发其中的set函数,打印其中self——类Foo类的信息,instance——被代理的对象信息,value——被代理的值被修改。

第19行对x的属性访问,理所应当,触发其代理的get方法,self——类Foo类的信息,instance——被代理的对象信息,owner——见名知意,被代理属性的最高拥有着,即Test类

其实当一个类中定义了set,get,delete的一个或多个,就可以把这个类称为描述符类。当没有set方法,有其他2个任意或所有时,又被称为非数据描述符。至少有get和set,称为数据描述符

  

4、描述符对象是实例属性

  从上述可知描述符对象是类属性。当描述符对象是实例属性又会怎么样呢?

 1 class Foo: 2     def __set__(self, instance, value): 3         print('set') 4         print(instance,value) 5     def __get__(self, instance, owner): 6         print('get') 7         print(instance,owner) 8     def __delete__(self, instance): 9         print('delete')10         print11 class Test:12     x=Foo()13     def __init__:14         self.y=Foo()15 t=Test()16 t.x17 t.y
 #输出18 get19 <__main__.Test object at 0x00000175F2FABF28> <class '__main__.Test'>

  咦?为什么只触发了一个get。t.y并没有触发get方法。why???

  因为调用 t.y 时,首先会去调用Test的 __getattribute__() 方法,该方法将 t.y 转化为Test.__dict__['y'].__get__, 但是呢,实际上 Test 并没有 y这个属性,y 是属于实例对象的,so,忽略。

5、类描述符对象属性与实例描述符对象属性同名

 1 class Foo: 2     def __set__(self, instance, value): 3         print('set') 4         print(instance,value) 5     def __get__(self, instance, owner): 6         print('get') 7         print(instance,owner) 8     def __delete__(self, instance): 9         print('delete')10         print11 class Test:12     x=Foo()13     def __init__:14         self.x=Foo()15 t=Test()16 t.x17 -----------------------------------------------------18 get19 <__main__.Test object at 0x00000246E4ACBF28> <class '__main__.Test'>

大家应该会想,实例属性通过__getattribute__()已经在自己字典中可以找到x,为什么还会触发get方法?

  这涉及到优先级的顺序问题,当解释器发现实例字典中有与描述符属性同名的属性时,描述符优先与实例属性,会覆盖掉实例属性。可以通过类字典验证

1 print(Test.__dict__)2 -------------3 {'__module__': '__main__', 'x': <__main__.Foo object at 0x000002757C138550>, '__init__': <function Test.__init__ at 0x000002757C1DA9D8>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None}

x所对应的value值是其本类对象,而t.__dict__则是个空字典。

6、描述符优先级别顺序

  上面已经提到,当无set方法的描述符称为非数据描述符,有set和get为数据描述符。这2者有啥区别?优先级别的大区别!!!

  类属性>>>数据描述符>>>实例属性>>>>非数据属性>>>>找不到此属性即__getattribute__`  

本文由365bet体育备用网址器发布于应用,转载请注明出处:它可以通过方法重写属性的访问,这些方法包括

关键词: