描述符是实现描述符协议方法的Python对象,当将其作为其他对象的属性进行访问时,该描述符使您能够创建具有特殊行为的对象。

通常,描述符是具有“绑定行为”的对象属性,其属性访问已被描述符协议中的方法所覆盖。这些方法是__get __(),__set __()和__delete __()。如果为对象定义了这些方法中的任何一种,则称其为描述符。属性访问的默认行为是从对象的字典中获取,设置或删除属性。例如,a.x具有一个查找链,查找链从a .__ dict __ ['x']开始,然后键入(a).__ dict __ ['x'],并继续遍历类型(a)的基类(不包括元类)。如果查找到的值是定义描述符方法之一的对象,则Python可能会覆盖默认行为并改为调用描述符方法。优先链在何处发生取决于定义了哪些描述符方法。描述符是功能强大的通用协议。它们是属性,方法,静态方法,类方法和super()背后的机制。在Python本身中使用它们来实现2.2版中引入的新样式类。

descr.__get__(self, obj, type=None) -> value
descr.__set__(self, obj, value) -> None
descr.__delete__(self, obj) -> None

定义这些方法中的任何一个,对象被视为描述符,并且在被视为属性时可以覆盖默认行为。

如果对象定义了__set __()或__delete __(),则将其视为数据描述符。仅定义__get __()的描述符称为非数据描述符(它们通常用于方法,但也可以用于其他用途)。数据和非数据描述符在实例字典中替代计算方式方面有所不同。如果实例的字典中的属性名称与数据描述符的名称相同,则以数据描述符为准。如果实例的字典中具有与非数据描述符同名的属性,则该字典属性优先。我们来看一下例子:

class lazy(object):
def __init__(self, func):
self.func = func def __get__(self, instance, owner):
val = self.func(instance)
setattr(instance, self.func.__name__, val)
return val class Circle(object):
def __init__(self, radius):
self.radius = radius @lazy
def area(self):
print('evalute')
return 3.14 * self.radius ** 2 def __getattr__(self, item):
return 1 c = Circle(4)
print(c.area)
print(c.area)

输出结果是

evalute
50.24
50.24

我们定义了一个描述符的类 lazy,它只实现了__get__方法,是一个非数据的描述符,我们用它定义了类Circle中的area方法,所以area方法成为了一个描述符的对象,可以看到,在第一次调用c.area的时候,执行了area的方法,打印了"evalute",在第二次的时间就直接输出了结果,没有指向area的方法,这是为什么呢?

那么重点来了,可以看到在lazy定义的__get__方法中,执行了被描述对象的方法,也就是这里的area函数,获取到结果之后,给当前的instance设置了一个同名的属性,并且设值为结果,这样下次在调用的时间,因为这是一个非数据的描述符,看上面的黑体字,实例的字典中的属性名称与数据描述符的名称相同,则以数据描述符为准。所以会取你刚刚设置的属性的值,不会再去取描述符的值。我们再来看看数据描述符的一个例子:

class lazy(object):
def __init__(self, func):
self.func = func def __get__(self, instance, owner):
val = self.func(instance)
setattr(instance, self.func.__name__, val)
return val def __set__(self, instance, value):
pass class Circle(object):
def __init__(self, radius):
self.radius = radius @lazy
def area(self):
print('evalute')
return 3.14 * self.radius ** 2 def __getattr__(self, item):
return 1 c = Circle(4)
print(c.area)
print(c.area)
 

我们看一下输出的结果:

evalute
50.24
evalute
50.24

  

 

同样的定义,只是在描述符中添加了__set__方法,就会执行调用描述符定义的属性,和非描述符的调用方式天壤之别。这就是这个高级特性的特别之处。我们可以使用非数据描述符做惰性加载,只计算一次,下次直接取值,我在工作中也是这样干的。

知其然,知其所以然,我们来看一下是为什么:

根据官方的解释,描述符可以通过其方法名称直接调用。例如,d .__ get __(obj)。另外,更常见的是在属性访问时自动调用描述符。例如,obj.d在obj的字典中查找d。如果d定义了方法__get __(),则根据下面列出的优先级规则调用d .__ get __(obj)。调用的细节取决于obj是对象还是类。

对于对象,机制位于object .__ getattribute __()中,它将b.x转换为type(b).__ dict __ ['x'] .__ get __(b,type(b))。该实现通过优先级链进行工作,该优先级链赋予数据描述符优先于实例变量的优先级,实例变量优先于非数据描述符的优先级,并为__getattr __()分配最低优先级。完整的C实现可在Objects / object.c中的PyObject_GenericGetAttr()中找到。

对于类,机制的类型为.__ getattribute __(),它将B.x转换为B .__ dict __ ['x'] .__ get __(无,B)。在纯Python中,它看起来像:

def __getattribute__(self, key):    "Emulate type_getattro() in Objects/typeobject.c"    v = object.__getattribute__(self, key)    if hasattr(v, '__get__'):        return v.__get__(None, self)    return v

要记住的重要点是:

  • 描述符由__getattribute __()方法调用

  • 重写__getattribute __()防止自动描述符调用

  • object .__ getattribute __()和type .__ getattribute __()对__get __()进行不同的调用。

  • 数据描述符始终会覆盖实例字典。非数据描述符可以被实例字典覆盖。

具体的可以查看Python的c源码。

以上就是今天要和大家一起学习的内容。

代码地址

https://github.com/oldman1991/testdemo/blob/master/0028_python_descriptor.py

更多问题欢迎关注微信公众号

聊聊Python中的描述符的更多相关文章

  1. python2.7高级编程 笔记二(Python中的描述符)

    Python中包含了许多内建的语言特性,它们使得代码简洁且易于理解.这些特性包括列表/集合/字典推导式,属性(property).以及装饰器(decorator).对于大部分特性来说,这些" ...

  2. 详解python中的描述符

    描述符介绍 总所周知,python声明变量的时候,不需要指定类型.虽然现在有了注解,但这只是一个规范,在语法层面是无效的.比如: 这里我们定义了一个hello函数,我们要求name参数传入str类型的 ...

  3. Python系列之 - 描述符

    描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议 __get__():调用一个属性时,触 ...

  4. 操作系统中的描述符和GDT

    在操作系统中,全局描述符是什么?GDT又是什么?在进入保护模式之前,准备好GDT和GDT中的描述符是必须的吗?用汇编代码怎么创建描述符?本文解答上面几个问题. 在实模式下,CPU是16位的,意思是,寄 ...

  5. Python中的描述器

    21.描述器:Descriptors 1)描述器的表现 用到三个魔术方法.__get__()   __set__()  __delete__() 方法签名如下: object.__get__(self ...

  6. Python核心编程-描述符

    python中,什么描述符.描述符就是实现了"__get__"."__set__"或"__delete__" 方法中至少一个的对象.什么是非 ...

  7. Linux中文件描述符fd和文件指针flip的理解

    转自:http://www.cnblogs.com/Jezze/archive/2011/12/23/2299861.html 简单归纳:fd只是一个整数,在open时产生.起到一个索引的作用,进程通 ...

  8. [转载] linux中文件描述符fd和文件指针flip的理解

    转载自http://www.cnblogs.com/Jezze/archive/2011/12/23/2299861.html 简单归纳:fd只是一个整数,在open时产生.起到一个索引的作用,进程通 ...

  9. python之属性描述符与属性查找规则

    描述符 import numbers class IntgerField: def __get__(self, isinstance, owner): print('获取age') return se ...

随机推荐

  1. oracle函数 ASCII(x1)

    [功能]:返回字符表达式最左端字符的ASCII 码值. [参数]:x1,字符表达式 [返回]:数值型 [示例] SQL> select ascii('A') A,ascii('a') a,asc ...

  2. Top 10 Free IT Certification Training Resources

    1. Cybrary Cybrary takes the open source concept and applies it to IT training. Many of the courses ...

  3. Android 高仿微信(QQ)滑动弹出编辑、删除菜单效果,增加下拉刷新功能

    不可否认,微信.QQ列表的滑动删除.编辑功能着实很经典(从IOS那边模仿过来的),然.Android这边,对列表的操作,其实大多还停留上下文菜单来实现. Android如何实现list item的滑动 ...

  4. Project Euler Problem 24-Lexicographic permutations

    全排列的生成,c++的next_permutation是O(n)生成全排列的.具体的O(n)生成全排列的算法,在 布鲁迪 的那本组合数学中有讲解(课本之外,我就看过这一本组合数学),冯速老师翻译的,具 ...

  5. SaltStack之用户账户管理

    在Salt.state中,user 模块是用来创建用户和管理用户设定的,用户可以被设置成 present 状态或者 absent 状态 注释: present:添加用户 absent   : 删除用户 ...

  6. 2019-6-23-win10-uwp-解决-SerialDevice.FromIdAsync-返回空

    title author date CreateTime categories win10 uwp 解决 SerialDevice.FromIdAsync 返回空 lindexi 2019-6-23 ...

  7. codeforces 1214

    D 比赛的时候居然看漏了条件... 若在(x, y)格子,那么只能移动到(x+1, y)或(x, y+1) 这样的话就好做了,直接dp,然后统计每一种路径长度经过的点数. #include<cs ...

  8. 2018-7-31-C#-判断两条直线距离

    title author date CreateTime categories C# 判断两条直线距离 lindexi 2018-07-31 14:38:13 +0800 2018-05-08 10: ...

  9. Git的使用--如何将本地项目上传到Github(两种简单、方便的方法..)

    https://blog.csdn.net/u014135752/article/details/79951802 总结:其实只需要进行下面几步就能把本地项目上传到Github 1.在本地创建一个版本 ...

  10. 2019-5-21-NuGet-符号服务器

    title author date CreateTime categories NuGet 符号服务器 lindexi 2019-05-21 11:34:40 +0800 2019-05-08 21: ...