什么是描述符类?

根据鸭子模型理论,只要具有__get__方法的类就是描述符类。

如果一个类中具有__get____set__两个方法,那么就是数据描述符,。

如果一个类中只有__get__方法,那么是非数据描述符。

__get__:当我们用类或者实例来调用该属性时,Python会返回__get__函数的结果。

__set__:当我们用实例来设置属性值时,Python会调用该函数。对类没有限制作用。

__delete__:当我们用实例试图删除该属性时,Python会调用该函数。对类没有限制作用。

非数据描述类

class Desc:
def __init__(self, value=22):
self.value= value
def __get__(self, ins, cls):
return self.value class A:
v=Desc()
a=A()

上面的描述符类只有一个__get__属性,所以是非数据描述符。

>>> a.v             #由于实例中没有v属性,所以找到了类的属性,而类的属性是一个描述符类实例,所以调用其__get__方法的结果。
22
>>> a.__dict__ #实例的__dict__空空如也。
{}
>>> A.__dict__ #类的__dict__中确实存在v属性,且是一个Desc object对象。
mappingproxy({'__module__': 'b', '__weakref__': <attribute '__weakref__' of 'A' objects>, '__dict__': <attribute '__dict__' of 'A' objects>, '__doc__': None, 'v': <b.Desc object at 0x7ff010f5f550>})
>>> a.v=30 #我们通过实例设置v属性,发现成功了。
>>> a.__dict__ #我们发现实例的__dict__中存入了我们刚才设置的属性
{'v': 30}
>>> A.__dict__ #类的__dict__没有发生任何变化
mappingproxy({'__module__': 'b', '__weakref__': <attribute '__weakref__' of 'A' objects>, '__dict__': <attribute '__dict__' of 'A' objects>, '__doc__': None, 'v': <b.Desc object at 0x7ff010f5f550>})
>>> a.v #如我们所料,访问到了a.__dict__中的内容。
30
>>> del a.v #我们删除实例的属性v后发现居然还是可以调用a.v,返回的是我们设置之前的值。
>>> a.v
22
>>> A.__dict__ #和前面一样,没有发生变化。
mappingproxy({'__module__': 'b', '__weakref__': <attribute '__weakref__' of 'A' objects>, '__dict__': <attribute '__dict__' of 'A' objects>, '__doc__': None, 'v': <b.Desc object at 0x7ff010f5f550>})

通过上面的测试,我们发现非数据描述类有如下特点:

  • 如果实例__dict__没有设置同名属性,那么返回描述类的__get__方法的结果。
  • 如果实例__dict__中存在同名属性,那么返回实例__dict__中的内容。
  • 对我们设置实例的__dict__中的行为并不做阻止。所以我说这是查看级别的描述类。

数据描述类

class Desc:
def __init__(self, value=22):
self.value= value
def __get__(self, ins, cls):
return self.value
def __set__(self, ins, value):
self.value=value
#raise AttributeError class A:
v=Desc()
a=A()

运行结果如下:

>>> a.v
22
>>> a.v=10
>>> a.__dict__ #我们设置a.v后,发现实例的__dict__中仍然空空如也。因为此时调用的是__set__方法,值10存入到了Desc实例的value属性上了。
{}
>>> A.__dict__
mappingproxy({'__module__': 'b', '__doc__': None, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, 'v': <b.Desc object at 0x7f0d2a4de5c0>})
>>> a.v #此时得到的还是Desc的__get__方法返回的结果。
10
>>> del a.v #不允许我们删除
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: __delete__
>>> A.v=30
>>> A.__dict__
mappingproxy({'__module__': 'b', '__doc__': None, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, 'v': 30})
#我们把__set__方法的原来语句注销,添加raise AttribeError语句,再次运行
>>> a.v=30 #我们在__set__中手动添加了AttributeError异常,所以我们再也不能设置a.v的值了,因此该属性鞭策了只读属性。
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/aaa/proj/b.py", line 8, in __set__
raise AttributeError
AttributeError
>>> A.v=20 #通过类,仍然可以改变属性
>>> A.__dict__ #改变后,变成了普通属性20了,这时甚至都已经不再是描述符类了。
mappingproxy({'__doc__': None, '__dict__': <attribute '__dict__' of 'A' objects>, '__module__': 'b', '__weakref__': <attribute '__weakref__' of 'A' objects>, 'v': 20})
>>> del A.v

说明如下:

  • __set__方法存在后,实例设置同名属性时,完全需要看__set__的脸色。
  • 如果描述类中__set__方法存在但是__delete__方法不存在,那么不能删除客户类中的属性。
  • 即使在__set__方法中做了限制,这个限制只是对实例而言的,对类没有起到作用。

把属性存在描述符类中

class Desc:
def __init__(self, value):
self.value = value
def __get__(self, ins, cls):
return self.value
def __set__(self, ins, value):
self.value = value
def __delete__(self, ins):
raise AttributeError('not allowed to delete attribute name ' ) class A:
name=Desc('JS')
a=A()

执行结果如下:

>>> del a.name
>>> a=A()
>>> b=A()
>>> a.name
'JS'
>>> b.name
'JS'
>>> a.name='CC'
>>> b.name
'CC'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/aaa/proj/b.py", line 10, in __delete__
raise AttributeError('not allowed to delete attribute name ' )
AttributeError: not allowed to delete attribute name

缺点显而易见,如果有多个实例,那么他们共享一个描述符,所以当一个实例的该属性发生改变后,其他实例的该属性也会发生变化。

改善方法:

存入一个字典,把实例的hash作为健存入,这样可以解决问题。

class Desc:
def __init__(self, value):
self.values={}
def __get__(self, ins, cls):
return self.values[hash(ins)]
def __set__(self, ins, value):
self.values[hash(ins)]=value
def __delete__(self, ins):
raise AttributeError('not allowed to delete attribute name ' )

把数据存入实例中

class Desc:
def __get__(self, ins, cls):
return ins._name
def __set__(self, ins, value):
ins._name=value
def __delete__(self, ins):
raise AttributeError('not allowed to delete attribute name ' ) class A:
name=Desc()
a=A()

执行结果如下:

>>> a=A()
>>> a.name='JS'
>>> a.name
'JS'
>>> a._name='CC'
>>> a.name
'CC'

缺点:我们设置在实例中的变量私密性不太好,可以很容易被改变。

当然,可以做一个私有性的装饰器,或者利用属性扩张来解决,这是我在后面会介绍的内容。

补充解释

__get__(self, ins, cls):其中ins为实例对象,在我们上面的例子中是a或者b,cls为a或者b的类,为A

__set____delete__:ins和上面的含义相同

Python属性、方法和类管理系列之----描述符类的更多相关文章

  1. Python属性、方法和类管理系列之----__slots__属性

    一句话说明 __slots__是用来限制实例的属性的,__slots__可以规定实例是否应该有__dict__属性:__slots__不能限制类的属性. 只有__slots__列表内的这些变量名可赋值 ...

  2. Python系列之 - 描述符

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

  3. 11.python描述符---类的装饰器---@property

    描述符1.描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()这三个内置方法中的一个,描述符也被称为描述符协议(1):__ ...

  4. Python属性、方法和类管理系列之----属性初探

    在学习dict的时候,肯定听过dict是Python中最重要的数据类型,但是不一定知道为什么.马上你就会明白原因了. Python中从模块.到函数.到类.到元类,其实主要管理方法就是靠一个一个的字典. ...

  5. Python属性、方法和类管理系列之----元类

    元类的介绍 请看位于下面网址的一篇文章,写的相当好. http://blog.jobbole.com/21351/ 实例补充 class Meta(type): def __new__(meta, c ...

  6. Python 属性方法、类方法、静态方法、 特殊属性__doc__ (内建属性)

    总结:和类的关联性讲:属性方法>类方法>静态方法 属性方法@property:仅仅是调用方式不用+括号. 类方法@classmethod:访问不了累的属性变量,只可以访问类变量. 静态方法 ...

  7. python小知识-__call__和类装饰器的结合使用,数据描述符__get__\__set__\__delete__(描述符类是Python中一种用于储存类属性值的对象)

    class Decorator(): def __init__(self, f): print('run in init......') self.f = f def __call__(self, a ...

  8. Python大法之告别脚本小子系列—信息资产收集类脚本编写(下)

    作者:阿甫哥哥 原文来自:https://bbs.ichunqiu.com/article-1618-1.html 系列文章专辑:Python大法之告别脚本小子系列目录: 0×05 高精度字典生成脚本 ...

  9. USB学习小记-HID类键盘的报告描述符的理解

    前言 断断续续的学习了将近三个月,才把USB的HID类搞明白,速度真是够慢的.利用晚上+周末的时间学习自己的东西确实是必要的,不过效率是有点低,以后要更专注一些才行,希望自己能做到吧. 在学习过程中, ...

随机推荐

  1. httperf ---linux web站点压力测试

    一.工具下载&&安装 软件获取 ftp://ftp.hpl.hp.com/pub/httperf/ 这里使用的是如下的版本 ftp://ftp.hpl.hp.com/pub/httpe ...

  2. 【排障】nginx在reload时候报错invalid PID number

    nginx在reload时候报错invalid PID number nginx重新加载配置文件时候报错,提示无效的PID: 解决的办法有二: 第一种思路是因为是加载配置文件报的错,所以用-c 选项指 ...

  3. 关于Git的merge和rebase命令解析

    git rebase是对提交执行变基的操作.即可以实现将指定范围的提交"嫁接"到另外一个提交智商. 其常用的命令格式有: 用法1:git rebase --onto <new ...

  4. 【转载】Spark SQL之External DataSource外部数据源

    http://blog.csdn.net/oopsoom/article/details/42061077 一.Spark SQL External DataSource简介 随着Spark1.2的发 ...

  5. Linux命令行程序和内建指令

    摘录百度分类 文件系统 cat cd chmod chown chgrp cksum cmp cp du df fsck fuser ln ls lsattr lsof mkdir mount mv ...

  6. SQLSERVER2000以上 Ad Hoc Distributed Queries的启用与关闭

    SQLSERVER2000以上的版本在查询分析器中查询ACCESS数据时提示:“ 訊息 15281,層級 16,狀態 1,行 1SQL Server 已封鎖元件 'Ad Hoc Distributed ...

  7. [转] sql数据类型 varchar与nvarchar的区别

    SQL Server提供两种数据类型来存储字符信息.在如何在SQL Server或应用程序中使用方面,这两种数据类型大致是一样的.差别在于nvarchar是用于存储处理数据库图表中多语言数据的Unic ...

  8. 一步一步创建一个简单的Package(1)

    创建Package之前首先我们理解需求: 数据源是一组历史货币数据包含在平面文件SampleCurrencyData.txt中,源数据中有四列. 下面是SampleCurrencyData.txt文件 ...

  9. 设计模式------STRATEGY(策略模式)

    http://blog.csdn.net/wuzhekai1985/article/details/6665197.仅供参考. 策略模式:实现替换功能,如cache替换算法:当发生Cache缺失时,C ...

  10. php正则贪婪匹配与非贪婪匹配一些例子

    http://www.111cn.net/phper/210/55600.htm 贪婪模式匹配的原则是: 在可匹配也可不匹配的情况下, 优先匹配,直到不能匹配成功的情况下,记录备选状态,并把匹配控制交 ...