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 ...
随机推荐
- windows I/O设备
当外部设备连接到windows后,设备所连接到的集线器驱动程序将为设备分配硬件ID,然后Windows 使用硬件 Id 查找设备与包含设备驱动程序的驱动程序包之间最近的匹配项. 如果查找到,设备就可以 ...
- Java 多线程安全问题简单切入详细解析
线程安全 假如Java程序中有多个线程在同时运行,而这些线程可能会同时运行一部分的代码.如果说该Java程序每次运行的结果和单线程的运行结果是一样的,并且其他的变量值也都是和预期的结果是一样的,那么就 ...
- C++ 排序引用的优化
链接:https://www.nowcoder.com/acm/contest/83/B来源:牛客网 题目描述 第一次期中考终于结束啦!沃老师是个语文老师,他在评学生的作文成绩时,给每位学生的分数都是 ...
- 《C# 爬虫 破境之道》:第一境 爬虫原理 — 第四节:同步与异步请求方式
前两节,我们对WebRequest和WebResponse这两个类做了介绍,但两者还相对独立.本节,我们来说说如何将两者结合起来,方式有哪些,有什么不同. 1.4.1 说结合,无非就是我们如何发送一个 ...
- 在Vue+element 开发中报: The template root requires exactly one elemen 错的解决和原因
一.我正准备使用Vue + Element进行新的项目开发,然后在进行添加下一个组件时报错 二.解决及原因: 原来template中只允许模板里存在一个根节点,在 template 中添加一个 &l ...
- Flask登录认证
login函数 @app.route('/login/', methods=['GET', 'POST']) def login(): if request.method == 'GET': retu ...
- .Net core路由高级用法
先说startup中的路由 这里是我们现在用的默认路由,但是在使用当中也有麻烦.总而言之 用的不爽. 使用属性路由:RouteAttribute特性 默认的HomeController下面的Index ...
- Element中(Notification)通知组件字体修改(Vue项目中Element的Notification修改字体)
这个问题纠结很久,一样的写的为啥有的页面就可以,有的就不行: 后来才发现: 先说一下怎么设置: 先定义customClass一个属性,用来写class属性值: 之后还需要修改一下组件里style标签的 ...
- 团队项目-Beta冲刺(第一周)
团队项目-Beta冲刺(第一周) 一. 作业描述 这个作业属于哪个课程 这个作业要求在哪里 团队名称 CTRL-IKun 这个作业的目标 合理安排时间完成接下来的任务,剩余任务预估,分配任务(开发,测 ...
- 个人第四次作业:Alpha项目测试
个人第四次作业:Alpha项目测试 格式描述 详情 这个作业属于哪个课程 http://edu.cnblogs.com/campus/xnsy/GeographicInformationScience ...