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. Spark指标项监控

    监控配置 spark的监控主要分为Master.Worker.driver.executor监控.Master和Worker的监控在spark集群运行时即可监控,Driver和Excutor的监控需要 ...

  2. 分布式-信息方式-ActiveMQ支持的传输协议和配置

                             ActiveMQ支持的传输协议和配置■ Connector: ActiveMQ提供的,用来实现连接通讯的功能.包括: client-to-broker ...

  3. LeetCode82----删除排序链表中的重复元素 II

    给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字. 示例 1: 输入: 1->2->3->3->4->4->5 输出: 1-&g ...

  4. D5(太长了md没写完)

    动态规划 三种常见实现方法 对于一个斐波那契数列,我们想要求第n项的值,就需要一项一项的递归来求 来看代码 f[o] = 0; f[1] = 1; for (int i = 2; i <= n; ...

  5. tensorflow service部署

    tensorflow+tensorflow-serving+docker+grpc模型上线部署(不需bazel编译,有代码)[https://blog.csdn.net/u013714645/arti ...

  6. Android地图开发获取sHA1值方法

    public static String sHA1(Context context) { try { PackageInfo info = context.getPackageManager().ge ...

  7. C# Selenium FireFox 接入阿布云

    业务需要购买http隧道,发现阿布云还行,使用Selenium本来想要用谷歌浏览器的,但是发现不能直接设置账号,所以选用火狐. 按照官方JAVA示例的改编,其中WebDriver实例化不能直接添加Fi ...

  8. 转载 STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发

    STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发  本文转载自 https://www.cnblogs.com/xingboy/p/9913963.html 这里我主要说一 ...

  9. Hibernate初了解

    Hibernate是一个开放源代码的对象关系映射框架, 它对JDBC进行了非常轻量级的对象封装,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库. Hibernate可以应用在任何使用JD ...

  10. 打开VMware提示该虚拟机似乎正在使用中该怎么办?

    一,当出现虚拟机无法使用时 解决办法: 1,找到虚拟机安装路径. 2,然后,将后缀为.lck的文件夹删除 二,VMware虚拟机配置文件(.vmx)损坏修复 1,找到后缀vmx的文件,记事本打开: 2 ...