1、什么是描述符?

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

 

2、描述符及其相关属性的简单定义

2.0 属性:__dict__

作用:字典类型,存放本对象的属性,key(键)即为属性名,value(值)即为属性的值,形式为{attr_key : attr_value}。
__dict__是对象的默认属性,所以每个类对象和实例化对象都有这个属性。

对象属性的访问顺序:

(1)实例对象/类对象的属于描述符的属性

(2)实例属性

(3)类属性

(3)父类属性

(4)__getattr__()方法

2.1 魔法方法__get__(), __getattr__(), __getattribute__()

作用:查找类对象或者实例对象的属性(也就是用于获取对象的__dict__属性中的值)

这三个魔法方法的调用顺序如下:

如果 obj = Clz(), 那么obj.attr 顺序如下:

(1)如果“attr”是出现在Clz或其父类的__dict__中, 且attr是data descriptor, 那么调用其__get__方法, 否则

(2)如果“attr”出现在obj的__dict__中, 那么直接返回 obj.__dict__['attr'], 否则

(3)如果“attr”出现在Clz或其父类的__dict__中

(3.1)如果attr是non-data descriptor,那么调用其__get__方法, 否则

(3.2)返回 __dict__['attr']

(4)如果Clz有__getattr__方法,调用__getattr__方法,否则

(5)抛出AttributeError

实际上还是上面那个调用顺序。只是结合描述符进行了一些补充关于描述符的补充

2.2 魔法方法:__get__(), __set__(), __delete__() 与descriptor 的简单定义

描述符本质上是一个类属性,实现描述符的类被称为描述符类。

其中只实现了__set__()方法的被当做方法描述符,或者是非数据描述符。

那些同时实现了__set__()__get__()方法的类被称作数据描述符。

而魔法方法__get__(), __set__(), __delete__() 就用于定义和调用类属性 __dict__

__get__(self, object, type)                  # 用于得到一个属性的值
__set__(self, obj, val) # 用于为一个属性赋值
__delete__(self, obj) # 删除某个属性时被调用,但很少用到

 

2.3 描述符的定义和调用初体验

# 描述符类的定义
class MyDescriptor(object):
def __init__(self, value):
self.value = value # 描述符value的访问
def __get__(self, instance, owner):
return self.value # 描述符value的定义
def __set__(self, instance, value):
self.value = value class MyClass(object):   mydescriptor = MyDescriptor(5)
   # 在MyClass类中创建一个描述符mydescriptor,重申一下,这是一个类属性。
# #同时可以看到,mydescriptor不仅仅是MyClass类的一个类属性,同时还是MyDescriptor的一个实例对象。
# #这样就将一个类的类属性定义成了另一个类的实例对象。 if __name__ == '__main__':
print (MyClass.mydescriptor) # 输出为 5

发现访问 MyClass 的 mydescriptor 属性时,调用了描述符的__get__()方法,访问了描述符类的实例属性value

这就达到了描述符的作用:可以改变了类对象属性的访问。

调用原理:对于类属性描述符,如果解析器发现属性x是一个描述符的话,在内部通过type.__getattribute__()(访问属性时无条件调用,最先调用),它能把Class.x转换成Class.__dict__[‘x’].__get__(None, Class)来访问

 

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

上面简单说了几个定义,接下来我们来解决一些实际使用中的细节问题。

1) 首先我们先看一段代码:

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} # 更改实例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})

以上这段代码证明:

在实例化对象时,类属性并不被实例继承。只有__init__()函数中的self.属性 也就是实例属性可以被继承。

在实例化结束之后,类属性和实例属性互不影响。

2) 下面我们仔细看看__get__()方法的调用过程

class Desc(object):
def __init__(self, value):
self.value = value def __get__(self, instance, owner):
print("...__get__...")
print("self : \t\t", self)
print("instance : \t", instance)
print("owner : \t", owner)
print('-'*40)
return self.value def __set__(self, instance, value):
print('...__set__...')
print("self : \t\t", self)
print("instance : \t", instance)
print("value : \t", value)
print('-'*40)
self.value = value class TestDesc(object):
desc = Desc(666) # 以下为测试代码
testdesc = TestDesc() print('testdesc.desc:%s' %testdesc.desc)
print('='*40)
print('TestDesc.desc:%s' %TestDesc.desc) # 以下为输出结果
...__get__...
self : <__main__.Desc object at 0x00000238491959B0>
instance : <__main__.TestDesc object at 0x000002384AFECD68>
owner : <class '__main__.TestDesc'>
----------------------------------------
testdesc.desc:666
========================================
...__get__...
self : <__main__.Desc object at 0x00000238491959B0>
instance : None
owner : <class '__main__.TestDesc'>
----------------------------------------
TestDesc.desc:666

以上代码说明:

1. 调用实例属性和调用类属性的是同一个对象,实际上他们都是由描述符类调用的。

2. 不管是类对象的类属性还是实例对象的实例属性  其实际属性都是描述符的类属性。

3. 被描述的类属性在被实例化时是被实例对象继承的。示例中testdesc.desc和TestDesc.desc有相同的值,而且是实例化之前的值。

3) 描述符是不能定义成实例属性的

# coding=utf-8
class Descriptor(object):
def __init__(self, value):
self.value = value def __get__(self, instance, owner):
print ("访问属性")
return self.value def __set__(self, instance, value):
print ("设置属性值")
self.value = value class TestDesc(object):
classdesc = Descriptor(888) def __init__(self):
self.insdesc = Descriptor(666) # 以下为测试代码
testdesc = TestDesc()
print(TestDesc.classdesc)
print(testdesc.classdesc)
print(testdesc.insdesc) # 以下为输出结果
访问属性
888
访问属性
888
<__main__.Descriptor object at 0x0000025041A64940>

可以看到,实例对象testdesc的 实例属性insdesc 并没有调用__get__()方法,只是说他是一个Descriptor对象。

这是因为当访问实例描述符对象时,obj.__getattribute__()会将myclass.desc转换为type(myclass).__dict__['desc'].__get__(myclass, type(myclass)),即到类属性中去寻找desc,并调用他的__get__()方法。而Myclass类中没有desc属性,所以无法访调用到__get__方法.
描述符是一个类属性,必须定义在类的层次上, 而不能单纯的定义为对象属性。

4. python的property方法

通过使用 property(),可以轻松地为任意属性创建可用的描述符。

property内建函数有四个参数:property(fget=None, fset=None, fdel=None, doc=None)

这四个参数都接受函数类型

class PropertyDesc(object):
def __init__(self):
self.__name = '' def fget(self):
print ("Getting: %s" % self.__name)
return self.__name def fset(self, value):
self.__name = value
print ("Setting: %s" % value) def fdel(self):
print ("Deleting: %s" % self.__name)
del self.__name name = property(fget, fset, fdel, "I'm the property.") if __name__ == '__main__':
pro = PropertyDesc()
pro.name = "hellokitty"
print(pro.name)
del pro.name # 以下为输出结果
Setting: hellokitty
Getting: hellokitty
hellokitty
Deleting: hellokitty

当然也可以使用装饰器的方式实现以上内容:

class PropertyDesc(object):
def __init__(self):
self._name = '' @property
def name(self):
print ("Getting: %s" % self._name)
return self._name @name.setter
def name(self, value):
print ("Setting: %s" % value)
self._name = value @name.deleter
def name(self):
print ("Deleting: %s" %self._name)
del self._name if __name__ == '__main__':
pro = PropertyDesc()
pro.name = "hello world"
print(pro.name)
del pro.name # 以下为输出内容
Setting: hello world
Getting: hello world
hello world
Deleting: hello world

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

  1. Python 描述符(descriptor) 杂记

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

  2. python描述符descriptor(一)

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

  3. python描述符 descriptor

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

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

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

  5. Python描述符 (descriptor) 详解

    1.什么是描述符? python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问.这些方法有 __get__(), __set__(), 和__delete__().如 ...

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

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

  7. python描述符descriptor(二)

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

  8. 【python】描述符descriptor

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

  9. 杂项之python描述符协议

    杂项之python描述符协议 本节内容 由来 描述符协议概念 类的静态方法及类方法实现原理 类作为装饰器使用 1. 由来 闲来无事去看了看django中的内置分页方法,发现里面用到了类作为装饰器来使用 ...

随机推荐

  1. perl基础-1

    基础 向函数中传递两个数组,使用指针 sub getSql{(my a,my b)=@_;my @array=@$a;} my @a;my @b; getSql(\@a,\@b); length($s ...

  2. 数据库事务ACID与隔离级别

    如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性: 原子性(Atomicity) 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,这和前面两篇博客介绍事务的功能是一样的 ...

  3. 【转】diamond专题(四)—— 容灾机制

    特别提示:本人博客部分有参考网络其他博客,但均是本人亲手编写过并验证通过.如发现博客有错误,请及时提出以免误导其他人,谢谢!欢迎转载,但记得标明文章出处:http://www.cnblogs.com/ ...

  4. 【Linux】GDB用法详解(5小时快速教程)

    GDB是一个强大的命令行调试工具.虽然X Window提供了GDB的图形版DDD,但是我仍然更钟爱在命令行模式下使用GDB.大家知道命令行的强大就是在于,其可以形成执行序列,形成脚本. UNIX下的软 ...

  5. laravel 浏览器图标的设置方式

    <head> <meta charset="UTF-8"> <title>叮叮书店</title> <link href=&q ...

  6. JAVA-retry 重试

    在看 ThreadPoolExecutor 源码时看到这么一段代码 retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Ch ...

  7. 一、基础篇--1.1Java基础-什么是java的序列化和反序列化

     什么是序列化和反序列化 序列化:把堆内存中的java对象数据,通过某种方式把对象存储到磁盘文件或者传递给其他网络节点.这个过程称为序列化.简单来说呢,就是将对象转换成二进流的过程. 反序列化:把磁盘 ...

  8. ajaxform和ajaxgird中添加数据

    ajaxform添加数据 ajaxform.setRecord(response.getAjaxDataWrap("dataWrapBill").getData()); ajaxg ...

  9. Android中@id与@+id区别和sharedUserId属性详解

    Android中的组件需要用一个int类型的值来表示,这个值也就是组件标签中的id属性值. id属性只能接受资源类型的值,也就是必须以@开头的值,例如,@id/abc.@+id/xyz等. 如果在@后 ...

  10. leetcode 123. 买卖股票的最佳时机 III

    使用动态规划的解法,空间复杂度O(2*2)如果交易k次则为O(2*k),时间复杂度O(2n),交易k次为O(n*k), 因此本题实际上可以退化为买卖一次的情况:去掉buy2和sell2,即leetco ...