Descriptor - Python 描述符协议
描述符(descriptor)
descriptor 是一个实现了 __get__、 __set__ 和 __delete__ 特殊方法中的一个或多个的.
与 descriptor 有关的几个名词解释,
描述符类(descriptor class)
实现描述符协议的类,被称作'描述符类'. 托管类(managed class)
把描述符实例声明为类属性的类,被称作'托管类'. 描述符实例(descriptor instance)
描述符类的各个实例,声明为托管类的类属性. 托管实例(managed instance)
托管类的实例。 储存属性(storage attribute)
托管实例中存储自身托管属性的属性,被称作'储存属性'.这种属性与描述符属性不同,描述符属性都是类属性(self.X) 托管属性(managed attribute)
托管类中由描述符实例处理的公开属性,值存储在储存属性中。
也就是说,描述符实例和储存属性为托管属性建立了基础,这一点一定要理解. *** 描述符的用法是,创建一个实例,作为另一个类的类属性(其实例作为另一个类-'托管类'的类属性).
描述符管理'托管类'中'托管属性'的存取和删除,数据通常存储在'托管实例'中.
描述符协议涉及的特殊方法,
__get__(self, instance, owner),
获取 'owner' class 的'属性'(class attribute access) 或 '实例'(instance attribute access)
'owner' 始终是 'attribute' 或 'instance' 的 owner class(即托管类); 'instance' 的值为'托管实例',
或者为 None 在不是通过'托管实例'调用的情况下(托管类直接调用'类属性').
该方法返回描述符所管理相应 'attribute' 或丢出 'AttributeError exception'. __set__(self, instance, value),
设置托管类的 'instance' 的属性为 'value' __delete__(self, instance),
删除托管类的 'instance' 的属性 __set_name__(self, owner, name)
在托管类实例别创建的时候调用, 用来设置描述符的名字 'name' (__dict__ key) 注, 以上对特殊方法的描述中, 'attribute' 指的是托管类的 '__dict__' 的属性(name / key in __dict__ of owner class) 描述符分类,
Python 存取属性的方式是非'对称'的。通过实例读取属性时,通常返回的是实例中定义的属性;
但是,如果实例中没有指定的属性,那么会获取类属性。
而为实例中的属性赋值时,如指定属性不存在通常会在实例中创建属性,根本不影响类属性。
这种不'对称'的处理方式对描述符也有影响。根据是否定义 __set__ 方法,描述符可分为两大类。 覆盖型描述符,
实现 __set__ 方法的描述符属于覆盖型描述符,因为虽然描述符是类属性,但是实现 __set__ 方法的话,会覆盖对实例属性的赋值操作。
(特性 'property' 是覆盖型描述符:如果没提供 __set__ 方法, property 类中的 __set__ 方法会抛出 AttributeError 异常,指明那个属性是只读的。)
例子,
class Override(object): # 覆盖性描述符
def __set__(self, instance, value):
print('Override - SET - args :',(self,instance,value)) def __get__(self, instance, owner):
print('Override - GET - args :', (self, instance, owner)) class Override_Noget(object): # 没有实现 __get__ 方法的覆盖性描述符
def __set__(self, instance, value):
print('Override_Noget - SET - args :', (self, instance, value))
return class NonOverride(object): # 非覆盖性描述符
def __get__(self, instance, owner):
print('NonOverride - GET - args :', (self, instance, owner)) class Registor(object): # 覆盖与非覆盖描述符示例
Override = Override()
Override_Noget = Override_Noget()
#NonOverride = NonOverride() if __name__ == '__main__':
print('*'*8 + ' Override ' + '*'*8)
regist6 = Registor()
regist7 = Registor() print(vars(regist6))
regist6.Override
Registor.Override #
regist6.Override = 789 #
print(vars(regist6))
regist6.Override #
regist6.__dict__['Override'] = 911 #4 绕过描述赋, 向 __dict__ 字典赋值
print(vars(regist6)) #
print(regist6.Override)
print(regist6.__dict__['Override']) print('*' * 8 + ' Override_Noget ' + '*' * 8)
print(vars(regist7))
print(regist7.Override_Noget) #
print(Registor.Override_Noget) # regist7.Override_Noget = 'WOW' #
print(regist7.Override_Noget) regist7.__dict__['Override_Noget'] = 'sos' #7 通过实例的 __dict__ 属性设置名为 Override_Noget 的实例属性
print(vars(regist7))
print(regist7.Override_Noget) # regist7.Override_Noget = 'MOM'
print(regist7.Override_Noget) #
print(vars(regist7)) Output,
# ******** Override ********
# {}
# Override - GET - args : (<__main__.Override object at 0x03A83C10>, <__main__.Registor object at 0x03A83CB0>, <class '__main__.Registor'>)
# Override - GET - args : (<__main__.Override object at 0x03A83C10>, None, <class '__main__.Registor'>)
#1 通过 Registor 类直接调用 '类属性', 触发 __get__ 方法, 'instance' 参数是 'None'
# Override - SET - args : (<__main__.Override object at 0x03A83C10>, <__main__.Registor object at 0x03A83CB0>, 789)
#2 regist6.Override = 'female' 赋值, 触发 __set__ 方法, 参数 'value' 是 '789'
# {}
# Override - GET - args : (<__main__.Override object at 0x03A83C10>, <__main__.Registor object at 0x03A83CB0>, <class '__main__.Registor'>)
#3 regist6.Override 取值,触发 __get__ 方法
# {'Override': 911}
#4 vars(regist6)
# Override - GET - args : (<__main__.Override object at 0x03A83C10>, <__main__.Registor object at 0x03A83CB0>, <class '__main__.Registor'>)
# None
# # ******** Override_Noget ********
# {}
# <__main__.Override_Noget object at 0x01B43C90>
# <__main__.Override_Noget object at 0x01B43C90>
#5 由于没有实现 __get__ 方法,通过实例获取描述符会返回描述符本身
# Override_Noget - SET - args : (<__main__.Override_Noget object at 0x01B43C90>, <__main__.Registor object at 0x01B43D10>, 'WOW')
#6 regist7.Override_Noget 赋值,触发 __set__ 方法
# <__main__.Override_Noget object at 0x01B43C90>
# {'Override_Noget': 'sos'}
# sos
#7 Override_Noget 实例属性会'遮盖'描述符,但是只有读操作是如此(没有 __get__ 方法)
# Override_Noget - SET - args : (<__main__.Override_Noget object at 0x01B43C90>, <__main__.Registor object at 0x01B43D10>, 'MOM')
# {'Override_Noget': 'sos'}
# sos
#8 读取时,只要有同名的实例属性,描述符就会被遮盖 非覆盖型描述符
没有实现 __set__ 方法的描述符是非覆盖型描述符。如果设置了同名的实例属性,描述符会被遮盖,致使描述符无法处理那个实例的那个属性。
(方法是以非覆盖型描述符实现的)
例子,
class NonOverride(object): # 非覆盖性描述符
def __get__(self, instance, owner):
print('NonOverride - GET - args :', (self, instance, owner)) class Registor(object):
NonOverride = NonOverride() if __name__ == '__main__':
regist8 = Registor()
print('*' * 8 + ' PersonalInfo_NonOverride ' + '*' * 8)
regist8.NonOverride # print(vars(regist8))
regist8.NonOverride = '' #2 NonOverride 是非覆盖型描述符,因此实现 __set__ 方法
print(vars(regist8)) #
print(regist8.NonOverride) # del regist8.NonOverride
Registor.NonOverride #
print(vars(regist8))
print(regist8.NonOverride) Output,
# NonOverride - GET - args : (<__main__.NonOverride object at 0x01143D10>, <__main__.Registor object at 0x01143D90>, <class '__main__.Registor'>)
#1 regist8.NonOverride 触发 __get__ 方法
# {}
# {'NonOverride': '333'}
#
#2 现有名为 NonOverride 的实例属性,把 Registor 类的同名描述符属性遮盖掉
# NonOverride - GET - args : (<__main__.NonOverride object at 0x01143D10>, None, <class '__main__.Registor'>)
#3 描述符依然存在
# {}
# NonOverride - GET - args : (<__main__.NonOverride object at 0x01143D10>, <__main__.Registor object at 0x01143D90>, <class '__main__.Registor'>)
# None # *** *** 注, 描述符的 __set__ 方法实现了对'同名实例属性' '写' 的 '遮盖'; __get__ 方法 实现了对'同名实例属性' '读' 的 '遮盖' 描述符例子(描述符 相关逻辑比较复杂, 请耐心阅读示例),
例子背景是,一款有关锻炼的 APP 中有这样一个类,它用来记录个人锻炼数据,输入每天的锻炼数据后,给处针对性的锻炼建议.
这个类被创建的时候需要用户注册基本的个人信息(身高,年龄,性别). 考虑到实际应用, 参数 '身高','年龄'需要是非负数,
'性别'是 'Male' 或者 'Female', 其他的值对这个3个参数来说都是非法值. class PersonalInfo_Override(object): def __init__(self,elment):
self.elment = elment def __set__(self, instance, value):
print('SET - args :',(self,instance,value))
if isinstance(value,int):
if value > 0:
instance.__dict__[self.elment] = value # 直接从instance.__dict__ 中处理,是为了跳过'特性',防止无限递归
# setattr(instance,self.elment,value) # 会引起无限递归
else:
raise ValueError('Invalid value setting - \'age\' and \'height\' must INT type with the value > 0')
elif isinstance(value,str):
if value.lower() == 'male' or value.upper() == 'FEMALE':
instance.__dict__[self.elment] = value
else:
raise ValueError('Invalid value setting - \'SEX\' must STR type - \'male\' or \'FEMALE\'')
else:
raise ValueError('Invalid value setting - unsupported type - \'INT\' or \'STR\' type is prefer') def __get__(self, instance, owner):
print('GET - args :', (self, instance, owner))
if instance is None:
return self
else:
return instance.__dict__[self.elment] class PersonalInfo_Override_Noget(object): def __init__(self, elment):
self.elment = elment def __set__(self, instance, value):
if isinstance(value,int):
if value > 0:
instance.__dict__[self.elment] = value
else:
raise ValueError('Invalid value setting - \'age\' and \'height\' must INT type with the value > 0')
elif isinstance(value,str):
if value.lower() == 'male' or value.upper() == 'FEMALE':
instance.__dict__[self.elment] = value
else:
raise ValueError('Invalid value setting - \'SEX\' must STR type - \'male\' or \'FEMALE\'')
else:
raise ValueError('Invalid value setting - unsupported type - \'INT\' or \'STR\' type is prefer') class PersonalInfo_NonOverride(object): def __init__(self, elment):
self.elment = elment def __get__(self, instance, owner):
if instance is None:
return self
else:
return instance.__dict__[self.elment] class ExerciseRecord(object):
sex = PersonalInfo_Override('sex')
age = PersonalInfo_Override_Noget('age')
height = PersonalInfo_NonOverride('height') def __init__(self,sex,age,height):
self.sex = sex
self.age = age
self.height = height def suggestion(self):
pass Output,
if __name__ == '__main__':
regist1 = ExerciseRecord('male',25,180)
regist2 = ExerciseRecord('lala', 25, 180) # ValueError: Invalid value setting - 'SEX' must STR type - 'male' or 'FEMALE'
regist3 = ExerciseRecord('FEMALE', 0, 10) # ValueError: Invalid value setting - 'age' and 'height' must INT type with the value > 0
regist4 = ExerciseRecord('MALE', 2, -10) # 'height' 属性应用的是'非覆盖描述符'(没有实现 __set__ 方法), 由于数据的有效性的验证是在 __set__ 方法中实现的,
# 所以对于 'height' 属性来说并没有起到数据验证的作用
regist5 = ExerciseRecord('male',-2,-5) # ValueError: Invalid value setting - 'age' and 'height' must INT type with the value > 0 ExerciseRecord.sex # GET - args : (<__main__.PersonalInfo_Override object at 0x00FA3B50>, None, <class '__main__.ExerciseRecord'>)
# 通过类直接调用 '类属性', 触发 __get__ 方法, 'instance' 参数是 'None' object.__get__(self, instance, owner)
Called to get the attribute of the 'owner' class (class attribute access) or of an instance of that class (instance attribute access).
'owner' is always the owner class, while 'instance' is the instance that the attribute was accessed through,
or 'None' when the attribute is accessed through the owner.
This method should return the (computed) attribute value or raise an AttributeError exception. object.__set__(self, instance, value)
Called to set the attribute on an instance 'instance' of the owner class to a new value, 'value'. object.__delete__(self, instance)
Called to delete the attribute on an instance 'instance' of the owner class. object.__set_name__(self, owner, name)
Called at the time the owning class owner is created. The descriptor has been assigned to name. *** Note,
In the examples below, “the attribute” refers to the attribute whose name is the key of the property in the owner class’ __dict__.
Descriptor - Python 描述符协议的更多相关文章
- 杂项之python描述符协议
杂项之python描述符协议 本节内容 由来 描述符协议概念 类的静态方法及类方法实现原理 类作为装饰器使用 1. 由来 闲来无事去看了看django中的内置分页方法,发现里面用到了类作为装饰器来使用 ...
- Iterator Protocol - Python 描述符协议
Iterator Protocol - Python 描述符协议 先看几个有关概念, iterator 迭代器, 一个实现了无参数的 __next__ 方法, 并返回 '序列'中下一个元素,在没有更多 ...
- 【转载】Python 描述符简介
来源:Alex Starostin 链接:www.ibm.com/developerworks/cn/opensource/os-pythondescriptors/ 关于Python@修饰符的文章可 ...
- python描述符descriptor(一)
Python 描述符是一种创建托管属性的方法.每当一个属性被查询时,一个动作就会发生.这个动作默认是get,set或者delete.不过,有时候某个应用可能会有 更多的需求,需要你设计一些更复杂的动作 ...
- Python 描述符(Descriptor) 附实例
在 Python 众多原生特性中,描述符可能是最少被自定义的特性之一,但它在底层实现的方法和属性却无时不刻被使用着,它优雅的实现方式体现出 Python 简洁之美. 定义 一个描述符是一个有" ...
- python描述符(descriptor)、属性(property)、函数(类)装饰器(decorator )原理实例详解
1.前言 Python的描述符是接触到Python核心编程中一个比较难以理解的内容,自己在学习的过程中也遇到过很多的疑惑,通过google和阅读源码,现将自己的理解和心得记录下来,也为正在为了该问题 ...
- python描述符 descriptor
descriptor 在python中,如果一个新式类定义了__get__, __set__, __delete__方法中的一个或者多个,那么称之为descriptor.descriptor通常用来改 ...
- Python描述符 (descriptor) 详解
1.什么是描述符? python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问.这些方法有 __get__(), __set__(), 和__delete__().如 ...
- 一文掌握 Python 的描述符协议
描述符介绍 描述符本质就是一个新式类,在这个新式类中,至少要实现了__get__(),__set__(),__delete__()中的一个.这也被称为描述符协议. class Myclass(obje ...
随机推荐
- ArcEngine 创建要素,删除要素,生成网格,渲染图层(VB)
示例代码:https://github.com/yu969890202/ArcEngine/tree/master/WinFrom_ArcEngine_PointDistribution博客后面有两张 ...
- spring boot通过@Bean注解定义一个Controller
功能需求 提供一个公共的jar包给其他业务模块依赖,需要在这个公共的jar中暴露一个restful API 采用spring auto config机制,在公共jar包中定义spring.factor ...
- bootstrap4popper.js报错Uncaught ReferenceError
这是因为bootstrap4需要umd版的popper.js <script src="https://cdn.bootcss.com/popper.js/1.15.0/umd/pop ...
- Nginx的踩坑实录
1.昨天在为一个新项目配置地址转发,搞了很久都没生效,日志也没有问题,但就是没到转发的目标机器上. nginx.conf 配置如下: location /prism{ proxy_pass http: ...
- Vue 编程式的导航
1.应用场景 在同一路由的情况下,不同的参数之间进行切换 注意:别忘记初始化路由页面 2.用法 a.定义方法 b.实现方法 c.初始化路由页面 3.案例 <template> <di ...
- 2017-10-28 noip模拟赛by WISCO 信息组
第一次做模拟赛,自我感觉良好(大概是这套题比较简单) T1 名称为“数据结构”,这也太坑了点……233 要维护一个数列(初始为零),支持区间加与查询. 查询的是一个区间中有多少数满足min<=( ...
- SpringMVC 中的异常处理
目录 1.搭建编码分析 2.编写异常类 3.编写自定义异常处理器 4.在springmvc.xml中配置异常处理器 5.编写Error.jsp.index.jsp页面 6.编写collector代码模 ...
- MySQL5.6绿色版安装
1.下载 MySQL绿色版安装需下载好三个文件 (1).MySQL5.6版本离线安装包 (2).Microsoft Visual C++ (3).Microsoft .NET Framework 1. ...
- c/python 的区别
c python ...
- 跟Evan学Sprign编程思想 | Spring注解编程模式【译】
Spring注解编程模式 概况 多年来,Spring Framework不断发展对注解.元注解和组合注解的支持. 本文档旨在帮助开发人员(Spring的最终用户以及Spring Framework和S ...