描述器Descriptors

2024-01-28 04:58
文章标签 描述 descriptors

本文主要是介绍描述器Descriptors,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.1 描述器的表现

用到三个魔术方法:get()、set()、delete()
方法的签名如下:
object.get(self, instance, owner)
object.set(self, instance, owner)
object.delete(self, instance)
self指代当前实例,调用者
instance是owner的实例
owner是属性所属的类

class A:  # 数据描述器的访问优先级要高于实例__dict__的访问;非数据描述器的访问,优先级要低于实例字典的访问def __init__(self):self.a1 = 'a1'print('A init')def __get__(self, instance, owner):  # instance为owner的实例print('get~~~~~~~~', self, instance, owner)# print(self.__dict__)return selfdef __set__(self, instance, value):  # 可以禁止修改实例的属性;主要看方法里的内容print('set~~~~~~~~~~~', value, instance)# self.data = value  # 保存在A的实例里了# setattr(instance, 'x', value)  # 无限递归# instance.data = value# instance.__dict__['x'] = valueif instance:raise Exception('不许改')def __set_name__(self, owner, name):  # python3.6新增的print(owner, name)self.name = nameclass B:  # 属主x = A()  # 类属性可以,描述器和属主类的类属性有关;解释器执行到这一句时,会调用__set_name__方法z = 5def __init__(self):# self.y = A()  # 实例属性不会,描述器与属主类的实例属性无关self.x = 'b.x'  # 动态增加属性print('B init')print('~~~~~~~~~~~~~~~~~')
print(B.x)  # 会调用A实例的__get__方法;instance为None,B.x或B().x才会调用描述器
print(B.x.a1)  # instance为None;注意是B.x调用了描述器
print('++++++++++++++++++')
b = B()
print(b.x)  # instance为<__main__.B object at 0x0000000001E89668>
print(b.x.a1)
print('~~~~~~~~~~~~~~~')
b = B()
print(b.x)  # <__main__.A object at 0x0000000000789630>
print(b.__dict__)  # {'x': 'b.x'}
print('~~~~~~~~~~~~~~~~~~~~~~')
b.x = 500  # 会调用描述器的__set__方法
print(b.x)  # <__main__.A object at 0x00000000027C9668>
print(b.__dict__)  # {'x': 500}
print("++++++++++++++++")
B.x = 600  # 如果类的类属性x是描述器,那么不要使用这样的赋值语句
print(b.x)  # 500
print(B.x)  # 600print('~~~~~~~~~~~~~~~')
b = B()
b.x = 100
print(b.x)

  因为定义了__get__方法,类A就是一个描述器,对类B或者类B的实例的x属性读取,成为对类A实例的访问,就会调用__get__方法。
   注意描述器与属主类的类属性有关,与实例的属性无关。数据描述器的访问优先级要高于实例字典的访问,非数据描述器的访问优先级要低于实例字典的访问。

1.2 描述器的定义

  python中,一个类实现了__get__、set、__delete__三个方法中的任意一个,就是描述器。实现这三个中的某些方法,就支持了描述器协议。

  • 仅实现了__get__,就是非数据描述器 non-data descriptor
  • 实现了__get__、__set__就是数据描述器 data descriptor

1.3python中的描述器

  python的方法(包括staticmethod()和classmethod())都实现为非数据描述器,因此实例可以重新定义和覆盖方法。property()函数实现为一个数据描述器。因此,实例不能覆盖属性的行为。

class A:@classmethoddef foo(cls):  # 非数据描述符——> foo = classmethod(foo)pass@staticmethoddef bar():  # 非数据描述符pass@propertydef z(self):  # 数据描述符return 5def get_foo(self):  # 非数据描述符return self.foodef __init__(self):  # 非数据描述符self.foo = 100self.bar = 200# self.z = 300  # 不能覆盖,会报错,因为z为数据描述符a = A()
print(a.__dict__)  # {'foo': 100, 'bar': 200}
print(A.__dict__)

注意:类的非数据描述符在实例中可以被重新定义和覆盖,类的数据描述符在实例中不能被重新定义和覆盖。

练习
1. 实现StaticMethod装饰器
2. 实现ClassMethod装饰器
from functools import partialclass StaticMethod:def __init__(self, fn):self.fn = fndef __get__(self, instance, owner):return self.fnclass ClassMethod:def __init__(self, fn):self.fn = fndef __get__(self, instance, owner):return partial(self.fn, owner)  # 固定owner即属主类class D:@StaticMethod  # stmd = StaticMethod(stmd) 非数据描述器def stmd(x, y):print('static method', x, y)@ClassMethod  # foo = ClassMethod(foo)def foo(cls, x, y):print(cls.__name__, x, y)d = D()
d.stmd(4, 5)  # static method 4 5d2 = D()
d2.foo(5, 6)  # D 5 6

1.4 对实例的数据进行效验

思路:

  1. 写函数,在__init__中先检查,如果不合格,直接抛异常
  2. 装饰器,使用inspect模块完成
  3. 描述器
    第一种思路的实现代码:
class Person:def __init__(self, name: str, age: int):params = ((name, str), (age, int))if not self.check_data(params):raise TypeError('类型错误')self.name = nameself.age = agedef check_data(params):for p, typ in params:if not isinstance(p, typ):return Falsereturn True# p1 = Person('tom', '20')  # TypeError: 类型错误

第二种思路的代码实现:

from functools import wraps
import inspectdef type_check(cls):@wraps(cls)def wrapper(*args, **kwargs):sig = inspect.signature(cls)params = sig.parameters  # OrderedDict# print(params)# values = list(params.values())# keys = list(params.keys())# for i, p in enumerate(args):#     if values[i].annotation != inspect._empty and  not isinstance(p, values[i].annotation):#         raise TypeError('Wrong param={} {}'.format(keys[i], p))for p, (k, v) in zip(args, params.items()):if v.annotation is not v.empty and not isinstance(p, v.annotation):raise TypeError('Wrong param= {} {}'.format(k, p))for k, v in kwargs.items():if params[k].annotation is not v.empty:  # inspect._emptyif not isinstance(v, params[k].annotation):raise TypeError('Wrong param={} {}'.format(k, v))return cls(*args, **kwargs)return wrapper@type_check
class Person:def __init__(self, name: str, age: int):self.name = nameself.age = agep1 = Person('tony', 20)
p2 = Person('jacky', '18')  # 直接报错

第三种思路的代码实现:

class TypeCheck:  # 描述器def __init__(self, typ):self.type = typdef __get__(self, instance, owner):# print('get~~~~~~~~~~~~~~')if instance:return instance.__dict__[self.name]else:raise Exception  # 或者return self,总之不正常def __set__(self, instance, value):# print('set~~~~~~~~~~~~~')if instance:if not isinstance(value, self.type):raise TypeError(self.name, '+++++++++++')else:instance.__dict__[self.name] = value  # 存回到属主类的实例字典中,默默的检查,不出错的话,像什么都没有发生一样def __set_name__(self, owner, name):  # python3.6新增的方法print(name)self.name = nameclass Person:name = TypeCheck(str)  # 硬编码,不优雅age = TypeCheck(int)def __init__(self, name: str, age: int):self.name = name  # 会调用TypeCheck实例的__set__方法(描述器)self.age = agep3 = Person('curry', 31)
p4 = Person('durant', 29)  # 直接抛出异常
print(p3.__dict__)  # {'name': 'curry', 'age': 31}
print(p4.__dict__)  # {'name': 'durant', 'age': 29}

第三种思路代码实现的过程中,存在硬编码,不优雅,可以使用装饰器动态的给类增加方法。改进后的代码为:

class TypeCheck:  # 描述器def __init__(self, name, typ):self.name = nameself.type = typdef __get__(self, instance, owner):# print('get~~~~~~~~~~~~~~')if instance:return instance.__dict__[self.name]else:raise Exception  # 或者return self,总之不正常def __set__(self, instance, value):# print('set~~~~~~~~~~~~~')if instance:if not isinstance(value, self.type):raise TypeError(self.name, '+++++++++++')else:instance.__dict__[self.name] = value  # 存回到属主类的实例字典中,默默的检查,不出错的话,像什么都没有发生一样# def __set_name__(self, owner, name):  # python3.6新增的方法#     print(name)#     self.name = namedef type_check(cls):sig = inspect.signature(cls)params = sig.parameters# print(params)  # 有序字典for name, param in params.items():if param.annotation is not param.empty:setattr(cls, name, TypeCheck(name, param.annotation))return cls# 注意动态给类增加属性(方法)时,__set_name__方法并没有调用,即此方法无效
@type_check  # Person = type_check(Person)
class Person:# name = TypeCheck(str)  # 硬编码,不优雅# age = TypeCheck(int)def __init__(self, name: str, age: int):self.name = name  # 会调用TypeCheck实例的__set__方法(描述器)self.age = agep3 = Person('curry', 31)
p4 = Person('durant', 29)  # 直接抛出异常
print(p3.__dict__)  # {'name': 'curry', 'age': 31}
print(p4.__dict__)  # {'name': 'durant', 'age': 29}
print(Person.__dict__)

既然可以使用函数装饰器给类动态增加属性,那么可否将函数装饰器改为类装饰器呢?答案是肯定的,请见详细代码:

class TypeCheck:  # 描述器def __init__(self, name, typ):self.name = nameself.type = typdef __get__(self, instance, owner):# print('get~~~~~~~~~~~~~~')if instance:return instance.__dict__[self.name]else:raise Exception  # 或者return self,总之不正常def __set__(self, instance, value):# print('set~~~~~~~~~~~~~')if instance:if not isinstance(value, self.type):raise TypeError(self.name, '+++++++++++')else:instance.__dict__[self.name] = value  # 存回到属主类的实例字典中,默默的检查,不出错的话,像什么都没有发生一样# def __set_name__(self, owner, name):  # python3.6新增的方法#     print(name)#     self.name = nameclass TypeInject:def __init__(self, cls):self.cls = clsdef __call__(self, *args, **kwarg):sig = inspect.signature(self.cls)params = sig.parameters# print(params)  # OrderedDict([('name', <Parameter "name:str">), ('age', <Parameter "age:int">)])for name, param in params.items():# print(name, param.annotation)if param.annotation != param.empty:  # inspect._emptysetattr(self.cls, name, TypeCheck(name, param.annotation))return self.cls(*args, **kwarg)@TypeInject  # Person = TypeInject(Person)
class Person:def __init__(self, name: str, age: int):self.name = nameself.age = agep5 = Person('Green', 28)

python中所有方法都是描述器

这篇关于描述器Descriptors的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/652536

相关文章

zblog自定义关键词和描述,zblog做seo优化必备插件

zblog自定义关键词和描述,zblog做seo优化必备插件     首先说下用到的一款插件:CustomMeta自定义数据字段 ,我们这里用到的版本是1.1,1.1+版增加了列表页标签支持!     插件介绍:文章,分类等添加自定义数据字段。1.1+版适用于 Z-Blog 2.0 B2以上版本。     在zblog2.0beta1里面,这个插件是集成到了程序里面,beta2里面默认没有了

【软件测试】软件测试-----什么是Bug?Bug是如何分级的?Bug的生命周期是怎样的?如何描述一个Bug?

博客目录 一.软件测试的生命周期二.BUG的定义和级别2.1 bug的概念.2.2 如何描述一个bug.2.3bug的级别2.3.1 bug分级的意义.2.3.2 bug的四种级别. 三.BUG的生命周期.四.当与开发人员发生冲突该如何处理(高频面试)五.总结 一.软件测试的生命周期 软件测试贯穿于软件的整个生命周期,针对这句话我们一起来看一下软件测试是如何贯穿软件的整个生命周

FPGA编程基础(二)--常用行为仿真描述

1、常用的行为仿真描述语句 利用循环完成遍历 for、while语句常用于完成遍历测试。当设计代码包含了多个工作模式,那么就需要对各种模式都机型遍历测试,如果手动完成每种模式的测试,则将造成非常大的工作量。利用for循环,通过循环下标来传递各种模式的配置,不仅可以有效减少工作量,还能保证验证的完备性,不会漏掉任何一种模式。 (1) for循环仿真 可综合文件: module sign

《数据结构-用C语言描述第三版》课后答案 第五章

撰写匆忙,如有错误,尽情指正 1.选择题 (1)设有一个二维数组 A [ m ][ n ],假设 A [0][0]存放地址为644, A [2][2]存放地址为676,每个元素占一个空间,则 A [3][3]的存放地址为()。  A .688  B .678 C .692  D .696 答: A[2][2]的地址等于 (2*n+2)*1 +644 = 676 则n = 15 则A[

【简历】25届武汉某二本JAVA简历:项目描述真是难为学生想这么偏

注:为保证用户信息安全,姓名和学校等信息已经进行同层次变更,内容部分细节也进行了部分隐藏 简历说明 这是一份 25 届武汉某二本学校的JAVA简历。校招第一要点是上来必须先确定自己的求职层次,大中小厂要求不一样。二本同学主体就是小公司。虽然有些重点二本可以冲一下中厂,但这个同学学校是比较普通的本科,另外主项目类似一个管理平台,基本上没有什么亮点,没有什么提问点,竞争力比较差,虽然重复度可能没那

Halcon!!!最新!! 从零认识标定板——制作描述文件和自己的标定板

一.标定板简介 ‌标定板是一种带有固定间距图案阵列的几何模型,主要用于机器视觉、图像测量、摄影测量、三维重建等领域。‌它的主要功能包括校正镜头畸变、确定物理尺寸和像素间的转换关系,以及确定空间物体表面某点的三维几何位置与其在图像中对应点之间的相互关系。通过相机拍摄标定板并计算校准算法,可以获得高精度的测量和重建结果。标定板的这个过程涉及误差测量与评估以及误差图像的矫正与补偿,通过这些步骤,可以更

排序方法总结——Java语言描述

排序总结——Java语言描述 各种排序方法Java源代码链接:各种排序方法Java源代码链接 一 排序概述 1.1 排序的定义 排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。 1.2 排序的分类 排序分为内部排序和外部排序 内部排序:若整个排序过程不需要访问外存便能完成(如软盘、硬盘),则称此类排序问题为内部排序; 外部排序:若

【译】PCL官网教程翻译(22):全局对齐空间分布(GASD)描述符 - Globally Aligned Spatial Distribution (GASD) descriptors

英文原文查看 全局对齐空间分布(GASD)描述符 本文描述了全局对齐的空间分布(GASD)全局描述符,用于有效的目标识别和姿态估计。 GASD基于表示对象实例的整个点云的参考系的估计,该实例用于将其与正则坐标系对齐。然后,根据对齐后的点云的三维点在空间上的分布情况计算其描述符。这种描述符还可以扩展到整个对齐点云的颜色分布。将匹配点云的全局对齐变换用于目标姿态的计算。更多信息请参见GASD。

【译】PCL官网教程翻译(20):惯性矩和偏心距描述符 - Moment of inertia and eccentricity based descriptors

英文原网页查看。 基于惯性矩和偏心距的描述符 在本教程中,我们将学习如何使用pcl::MomentOfInertiaEstimation类来获得基于偏心量和惯性矩的描述符。这个类还允许提取轴对齐和有向的点云包围框。但是请记住,提取的OBB可能并不是最小的边界框。 理论基础 特征提取方法的思想如下。首先计算点云的协方差矩阵,提取点云的特征值和特征向量。可以考虑得到的特征向量是归一化的,并且总

【译】PCL官网教程翻译(17):快速点特征直方图(FPFH)描述符 -Fast Point Feature Histograms (FPFH) descriptors

英文原文阅读 快速点特征直方图(FPFH)描述符 计算复杂度直方图(见点特征直方图(PFH)描述符)对于一个给定的有 n n n个点的点云 P P P为 O ( n k 2 ) O (nk ^ 2) O(nk2), k k k是每个点P的最邻近点个数。对于要求实时或接近实时的应用程序,密集点的特征直方图的计算效率是一个一个主要问题。 本教程描述了PFH公式的简化,称为快速点特征直方图(FPF