1、什么是描述符?

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

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

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

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

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

对象属性的访问顺序:

①.实例属性

②.类属性

③.父类属性

④.__getattr__()方法

以上顺序,切记切记!

 class Test(object):
cls_val = 1
def __init__(self):
self.ins_val = 10 >>> t=Test()
>>> Test.__dict__
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})
>>> t.__dict__
{'ins_val': 10} >>> type(x)==X
True #更改实例t的属性cls_val,只是新增了该属性,并不影响类Test的属性cls_val
>>> t.cls_val = 20
>>> t.__dict__
{'ins_val': 10, 'cls_val': 20}
>>> Test.__dict__
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}) #更改了类Test的属性cls_val的值,由于事先增加了实例t的cls_val属性,因此不会改变实例的cls_val值(井水不犯河水)
>>> Test.cls_val = 30
>>> t.__dict__
{'ins_val': 10, 'cls_val': 20}
>>> Test.__dict__
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

 class Desc(object):

     def __get__(self, instance, owner):
print("__get__...")
print("self : \t\t", self)
print("instance : \t", instance)
print("owner : \t", owner)
print('='*40, "\n") def __set__(self, instance, value):
print('__set__...')
print("self : \t\t", self)
print("instance : \t", instance)
print("value : \t", value)
print('='*40, "\n") class TestDesc(object):
x = Desc() #以下为测试代码
t = TestDesc()
t.x #以下为输出信息: __get__...
self : <__main__.Desc object at 0x0000000002B0B828>
instance : <__main__.TestDesc object at 0x0000000002B0BA20>
owner : <class '__main__.TestDesc'>
========================================

可以看到,实例化类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  的类属性,那么可不可以把它变成实例属性呢?

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

 #代码 2

 class Desc(object):
def __init__(self, name):
self.name = name def __get__(self, instance, owner):
print("__get__...")
print('name = ',self.name)
print('='*40, "\n") class TestDesc(object):
x = Desc('x')
def __init__(self):
self.y = Desc('y') #以下为测试代码
t = TestDesc()
t.x
t.y #以下为输出结果:
__get__...
name = x
========================================

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

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

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

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

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

 #代码 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):
self.value = value class TestDesc(object):
_x = Desc('x')
def __init__(self, x):
self._x = x #以下为测试代码
t = TestDesc(10)
t._x #输入结果
__init__(): name = x
__get__() ...

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

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

    不信?来看一下 字典 :

 >>> t.__dict__
{} >>> TestDesc.__dict__
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__() 方法试试看会发生什么情况?

 #代码 4

 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)
t._x #以下为输出:
__init__(): name = x

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

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

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

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

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

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

    ① __getattribute__(), 无条件调用

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

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

    ④ 类的字典

    ⑤ 非数据描述符

    ⑥ 父类的字典

    ⑦ __getattr__() 方法

Python描述符 (descriptor) 详解的更多相关文章

  1. python描述符(descriptor)、属性(property)、函数(类)装饰器(decorator )原理实例详解

     1.前言 Python的描述符是接触到Python核心编程中一个比较难以理解的内容,自己在学习的过程中也遇到过很多的疑惑,通过google和阅读源码,现将自己的理解和心得记录下来,也为正在为了该问题 ...

  2. Python 描述符(descriptor) 杂记

    转自:https://blog.tonyseek.com/post/notes-about-python-descriptor/ Python 引入的“描述符”(descriptor)语法特性真的很黄 ...

  3. python描述符descriptor(一)

    Python 描述符是一种创建托管属性的方法.每当一个属性被查询时,一个动作就会发生.这个动作默认是get,set或者delete.不过,有时候某个应用可能会有 更多的需求,需要你设计一些更复杂的动作 ...

  4. python描述符 descriptor

    descriptor 在python中,如果一个新式类定义了__get__, __set__, __delete__方法中的一个或者多个,那么称之为descriptor.descriptor通常用来改 ...

  5. Python 描述符(Descriptor) 附实例

    在 Python 众多原生特性中,描述符可能是最少被自定义的特性之一,但它在底层实现的方法和属性却无时不刻被使用着,它优雅的实现方式体现出 Python 简洁之美. 定义 一个描述符是一个有" ...

  6. Python 描述符 (descriptor)

    1.什么是描述符? 描述符是Python新式类的关键点之一,它为对象属性提供强大的API,你可以认为描述符是表示对象属性的一个代理.当需要属性时,可根据你遇到的情况,通过描述符进行访问他(摘自Pyth ...

  7. python描述符descriptor(二)

    python内置的描述符 python有些内置的描述符对象,property.staticmethod.classmethod,python实现如下: class Property(object): ...

  8. Linux-进程描述符 task_struct 详解

    为了描述控制进程的运行,系统中存放进程的管理和控制信息的数据结构称为进程控制块 PCB(Process Control Block),它是进程实体的一部分,是操作系统中最重要的记录性数据结构.它是进程 ...

  9. 【python】描述符descriptor

    开始看官方文档,各种看不懂,只看到一句Properties, bound and unbound methods, static methods, and class methods are all ...

随机推荐

  1. 从零开始学 Web 之 jQuery(八)each,多库共存,包装集,插件

    大家好,这里是「 从零开始学 Web 系列教程 」,并在下列地址同步更新...... github:https://github.com/Daotin/Web 微信公众号:Web前端之巅 博客园:ht ...

  2. SQL 必知必会·笔记<10>联结表

    可伸缩(scale) 能够适应不断增加的工作量而不失败.设计良好的数据库或应用程序 称为可伸缩性好(scale well). 联结(JOIN) 联结(JOIN)是一种机制,用来在一条SELECT 语句 ...

  3. BizTalk 新增/修改/删除 XmlDocument 名字空间的高效方法

    新增一个名字空间 public class AddXmlNamespaceStream : XmlTranslatorStream { private String namespace_; priva ...

  4. loggin(日志模块)

    这是一个提供日志功能的模块,它可以让你更敏捷的为你程序提供日志功能 一.常用日志记录场景及最佳解决方案: 日志记录方式 最佳记录日志方案 普通情况下,在控制台显示输出 print()报告正常程序操作过 ...

  5. 排版-标题及table

    排版-标题 在标题中还可以包含small标签,可以用来标记副标题 副标题灰色,比主标题小    <h1>我是标题 <small>我是小标题</small></ ...

  6. 【协议】3、HTTP 协议入门

    HTTP 协议是互联网的基础协议,也是网页开发的必备知识,最新版本 HTTP/2 更是让它成为技术热点. 本文介绍 HTTP 协议的历史演变和设计思路. 一.HTTP/0.9 HTTP 是基于 TCP ...

  7. vue VNode如何使用,是什么东西?

    前言:element ui 中有些 demo 使用了 vue 的 VNode,但是 demo 毕竟不全,所以想要了解 VNode 到底是什么,如何写出符合自己业务的代码. vue 官网介绍:渲染函数 ...

  8. 关于git的一个错误提示

    一般clone仓库后如果,remote -v或者add出现如下的错误的话: Not a git repository (or any of the parent directories): .git需 ...

  9. Python全栈学习_day001知识点

    今日大纲: . 变量. ***** . 常量.** . 注释.*** . 基础数据类型初识(int,str,bool). ***** . 用户输入 input ***** . 流程控制语句if. ** ...

  10. JavaScript基础要点

    一.值和类型及运算 JavaScript中的六种基本值类型 数字(number).字符串(string).布尔值(boolean).对象(object).函数(function).未定义类型(unde ...