描述符是实现描述符协议方法的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. dataframe构建

    data=[[[0],1]]df = pd.DataFrame(data, columns=['col1', 'col2']) df = pd.DataFrame({‘col1’:‘’,‘col2’: ...

  2. 2019-2-2-VisualStudio-扩展开发-添加菜单

    title author date CreateTime categories VisualStudio 扩展开发 添加菜单 lindexi 2019-02-02 15:35:18 +0800 201 ...

  3. Laravel 5.5 将会要求 PHP 7.0+

    Laravel 5.5 都要用 PHP7 了呢!你还在用 PHP 5 吗? Laravel 一直是一个精 (sheng) 进 (ji) 不 (hen) 休 (kuai) 的框架.就在前几天,下图这位 ...

  4. 立足GitHub学编程:13个不容错过的Java项目

    立足GitHub学编程:13个不容错过的Java项目 今天我们将整理一大波干货满满的Java示例代码与能力展示素材. GitHub可谓一座程序开发的大宝库,有些素材值得fork,有些则能帮助我们改进自 ...

  5. html5在微信中不允许放大缩小页面

    在头部添加 <meta name="viewport" content="width=device-width, initial-scale=1, maximum- ...

  6. SpringSecurity认证流程详解

    SpringSecurity基本原理 在之前的文章<SpringBoot + Spring Security 基本使用及个性化登录配置>中对SpringSecurity进行了简单的使用介绍 ...

  7. 代码片段 修改Windows用户名

    cmd /c wmic useraccount where name=' 记录防备忘

  8. H3C 路由度量值(Metric)

  9. .map() .filter() .reduce() .includes() .some() .every()的用法

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  10. 深度学习——CNN

    整理自: https://blog.csdn.net/woaidapaopao/article/details/77806273?locationnum=9&fps=1 思想 filter尺寸 ...