新用户注册入口 老用户登录入口

[转载]python描述_Python描述符(Descriptor)入门

文章作者:转载 更新时间:2023-05-07 19:03:49 阅读数量:93
文章标签:Python__get____set__
本文摘要:这篇文章详细阐述了Python中描述符(Descriptor)的关键概念,它是属性访问背后的实现机制,并在新式类体系中起到核心作用。描述符是一种特殊对象,通过实现`__get__`、`__set__`和`__delete__`方法来控制属性的获取、设置和删除操作。文中以property装饰器为例引入描述符,并深入剖析其实现原理。文章对比了数据描述符(data descriptors)与非数据描述符(non data descriptors)的区别,强调了描述符在Python中对于属性、方法等特性的优先级控制及惰性计算属性(lazy property)等高级应用的重要价值。
转载文章

本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_39736934/article/details/112888600。

该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。

作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。

如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。

很久都没写 Flask 代码相关了,想想也真是惭愧,然并卵,这次还是不写 Flask 相关,不服你来打我啊(就这么贱,有本事咬我啊

这次我来写一下 Python 一个很重要的东西,即 Descriptor (描述符)

初识描述符

老规矩, Talk is cheap,Show me the code. 我们先来看看一段代码classPerson(object):

""""""

#----------------------------------------------------------------------

def__init__(self, first_name, last_name):

"""Constructor"""

self.first_name = first_name

self.last_name = last_name

#----------------------------------------------------------------------

@property

deffull_name(self):

"""

Return the full name

"""

return"%s %s"% (self.first_name, self.last_name)

if__name__=="__main__":

person = Person("Mike","Driscoll")

print(person.full_name)

# 'Mike Driscoll'

print(person.first_name)

# 'Mike'

这段代大家肯定很熟悉,恩, property 嘛,谁不知道呢,但是 property 的实现机制大家清楚么?什么不清楚?那还学个毛的 Python

啊。。。开个玩笑,我们看下面一段代码classProperty(object):

"Emulate PyProperty_Type() in Objects/descrobject.c"

def__init__(self, fget=None, fset=None, fdel=None, doc=None):

self.fget = fget

self.fset = fset

self.fdel = fdel

ifdocisNoneandfgetisnotNone:

doc = fget.__doc__

self.__doc__ = doc

def__get__(self, obj, objtype=None):

ifobjisNone:

returnself

ifself.fgetisNone:

raiseAttributeError("unreadable attribute")

returnself.fget(obj)

def__set__(self, obj, value):

ifself.fsetisNone:

raiseAttributeError("can't set attribute")

self.fset(obj, value)

def__delete__(self, obj):

ifself.fdelisNone:

raiseAttributeError("can't delete attribute")

self.fdel(obj)

defgetter(self, fget):

returntype(self)(fget, self.fset, self.fdel, self.__doc__)

defsetter(self, fset):

returntype(self)(self.fget, fset, self.fdel, self.__doc__)

defdeleter(self, fdel):

returntype(self)(self.fget, self.fset, fdel, self.__doc__)

看起来是不是很复杂,没事,我们来一步步的看。不过这里我们首先给出一个结论: Descriptors 是一种特殊 的对象,这种对象实现了 __get__ ,

__set__ , __delete__ 这三个特殊方法。

详解描述符

说说 Property

在上文,我们给出了 Propery 实现代码,现在让我们来详细说说这个classPerson(object):

""""""

#----------------------------------------------------------------------

def__init__(self, first_name, last_name):

"""Constructor"""

self.first_name = first_name

self.last_name = last_name

#----------------------------------------------------------------------

@Property

deffull_name(self):

"""

Return the full name

"""

return"%s %s"% (self.first_name, self.last_name)

if__name__=="__main__":

person = Person("Mike","Driscoll")

print(person.full_name)

# 'Mike Driscoll'

print(person.first_name)

# 'Mike'

首先,如果你对装饰器不了解的话,你可能要去看看这篇文章,简而言之,在我们正式运行代码之前,我们的解释器就会对我们的代码进行一次扫描,对涉及装饰器的部分进行替换。类装饰器同理。在上文中,这段代码@Property

deffull_name(self):

"""

Return the full name

"""

return"%s %s"% (self.first_name, self.last_name)

会触发这样一个过程,即 full_name=Property(full_name) 。然后在我们后面所实例化对象之后我们调用

person.full_name 这样一个过程其实等价于 person.full_name.__get__(person) 然后进而触发 __get__()

方法里所写的 return self.fget(obj) 即原本上我们所编写的 def full_name 内的执行代码。

这个时候,同志们可以去思考下 getter() , setter() ,以及 deleter()

的具体运行机制了=。=如果还是有问题,欢迎在评论里进行讨论。

关于描述符

还记得之前我们所提到的一个定义么: Descriptors 是一种特殊的对象,这种对象实现了 __get__ , __set__ , __delete__

这三个特殊方法 。然后在 Python 官方文档的说明中,为了体现描述符的重要性,有这样一段话:“They are the mechanism behind

properties, methods, static methods, class methods, and super(). They are used

throughout Python itself to implement the new style classes introduced in

version 2.2. ” 简而言之就是 先有描述符后有天,秒天秒地秒空气

。恩,在新式类中,属性,方法调用,静态方法,类方法等都是基于描述符的特定使用。

OK,你可能想问,为什么描述符是这么重要呢?别急,我们接着看

使用描述符

首先请看下一段代码

classA(object):#注:在 Python 3.x 版本中,对于 new class 的使用不需要显式的指定从 object 类进行继承,如果在

Python 2.X(x>2)的版本中则需要defa(self):

pass

if__name__=="__main__":

a=A()

a.a()

大家都注意到了我们存在着这样一个语句 a.a() ,好的,现在请大家思考下,我们在调用这个方法的时候发生了什么?

OK?想出来了么?没有?好的我们继续

首先我们调用一个属性的时候,不管是成员还是方法,我们都会触发这样一个方法用于调用属性 __getattribute__() ,在我们的

__getattribute__() 方法中,如果我们尝试调用的属性实现了我们的描述符协议,那么会产生这样一个调用过程

type(a).__dict__['a'].__get__(b,type(b))

。好的这里我们又要给出一个结论了:“在这样一个调用过程中,有这样一个优先级顺序,如果我们所尝试调用属性是一个 data descriptors

,那么不管这个属性是否存在我们的实例的 __dict__ 字典中,优先调用我们描述符里的 __get__ 方法,如果我们所尝试调用属性是一个 non data

descriptors ,那么我们优先调用我们实例里的 __dict__ 里的存在的属性,如果不存在,则依照相应原则往上查找我们类,父类中的 __dict__

中所包含的属性,一旦属性存在,则调用 __get__ 方法,如果不存在则调用 __getattr__()

方法”。理解起来有点抽象?没事,我们马上会讲,不过在这里,我们先要解释下 data descriptors 与 non data descriptors

,再来看一个例子。什么是 data descriptors 与 non data descriptors 呢?其实很简单,在描述符中同时实现了 __get__

与 __set__ 协议的描述符是 data descriptors ,如果只实现了 __get__ 协议的则是 non data descriptors

。好了我们现在来看个例子:importmath

classlazyproperty:

def__init__(self, func):

self.func = func

def__get__(self, instance, owner):

ifinstanceisNone:

returnself

else:

value = self.func(instance)

setattr(instance, self.func.__name__, value)

returnvalue

classCircle:

def__init__(self, radius):

self.radius = radius

pass

@lazyproperty

defarea(self):

print("Com")

returnmath.pi * self.radius *2

deftest(self):

pass

if__name__=='__main__':

c=Circle(4)

print(c.area)

好的,让我们仔细来看看这段代码,首先类描述符 @lazyproperty 的替换过程,前面已经说了,我们不在重复。接着,在我们第一次调用 c.area

的时候,我们首先查询实例 c 的 __dict__ 中是否存在着 area 描述符,然后发现在 c 中既不存在描述符,也不存在这样一个属性,接着我们向上查询

Circle 中的 __dict__ ,然后查找到名为 area 的属性,同时这是一个 non data descriptors ,由于我们的实例字典内并不存在

area 属性,那么我们便调用类字典中的 area 的 __get__ 方法,并在 __get__ 方法中通过调用 setattr 方法为实例字典注册属性

area 。紧接着,我们在后续调用 c.area 的时候,我们能在实例字典中找到 area 属性的存在,且类字典中的 area 是一个 non data

descriptors ,于是我们不会触发代码里所实现的 __get__ 方法,而是直接从实例的字典中直接获取属性值。

描述符的使用

描述符的使用面很广,不过其主要的目的在于让我们的调用过程变得可控。因此我们在一些需要对我们调用过程实行精细控制的时候,使用描述符,比如我们之前提到的这个例子classlazyproperty:

def__init__(self, func):

self.func = func

def__get__(self, instance, owner):

ifinstanceisNone:

returnself

else:

value = self.func(instance)

setattr(instance, self.func.__name__, value)

returnvalue

def__set__(self, instance, value=0):

pass

importmath

classCircle:

def__init__(self, radius):

self.radius = radius

pass

@lazyproperty

defarea(self, value=0):

print("Com")

ifvalue ==0andself.radius ==0:

raiseTypeError("Something went wring")

returnmath.pi * value *2ifvalue !=0elsemath.pi * self.radius *2

deftest(self):

pass

利用描述符的特性实现懒加载,再比如,我们可以控制属性赋值的值classProperty(object):

"Emulate PyProperty_Type() in Objects/descrobject.c"

def__init__(self, fget=None, fset=None, fdel=None, doc=None):

self.fget = fget

self.fset = fset

self.fdel = fdel

ifdocisNoneandfgetisnotNone:

doc = fget.__doc__

self.__doc__ = doc

def__get__(self, obj, objtype=None):

ifobjisNone:

returnself

ifself.fgetisNone:

raiseAttributeError("unreadable attribute")

returnself.fget(obj)

def__set__(self, obj, value=None):

ifvalueisNone:

raiseTypeError("You can`t to set value as None")

ifself.fsetisNone:

raiseAttributeError("can't set attribute")

self.fset(obj, value)

def__delete__(self, obj):

ifself.fdelisNone:

raiseAttributeError("can't delete attribute")

self.fdel(obj)

defgetter(self, fget):

returntype(self)(fget, self.fset, self.fdel, self.__doc__)

defsetter(self, fset):

returntype(self)(self.fget, fset, self.fdel, self.__doc__)

defdeleter(self, fdel):

returntype(self)(self.fget, self.fset, fdel, self.__doc__)

classtest():

def__init__(self, value):

self.value = value

@Property

defValue(self):

returnself.value

@Value.setter

deftest(self, x):

self.value = x

如上面的例子所描述的一样,我们可以判断所传入的值是否有效等等。

以上就是Python 描述符(Descriptor)入门,更多相关文章请关注PHP中文网(www.gxlcms.com)!

本条技术文章来源于互联网,如果无意侵犯您的权益请点击此处反馈版权投诉

本文系统来源:php中文网

本篇文章为转载内容。原文链接:https://blog.csdn.net/weixin_39736934/article/details/112888600。

该文由互联网用户投稿提供,文中观点代表作者本人意见,并不代表本站的立场。

作为信息平台,本站仅提供文章转载服务,并不拥有其所有权,也不对文章内容的真实性、准确性和合法性承担责任。

如发现本文存在侵权、违法、违规或事实不符的情况,请及时联系我们,我们将第一时间进行核实并删除相应内容。

相关阅读
文章标题:[转载][洛谷P1082]同余方程

更新时间:2023-02-18
[转载][洛谷P1082]同余方程
文章标题:[转载]webpack优化之HappyPack实战

更新时间:2023-08-07
[转载]webpack优化之HappyPack实战
文章标题:[转载]oracle 同时更新多表,在Oracle数据库中同时更新两张表的简单方法

更新时间:2023-09-10
[转载]oracle 同时更新多表,在Oracle数据库中同时更新两张表的简单方法
文章标题:[转载][Unity] 包括场景互动与射击要素的俯视角闯关游戏Demo

更新时间:2024-03-11
[转载][Unity] 包括场景互动与射击要素的俯视角闯关游戏Demo
文章标题:[转载]程序员也分三六九等?等级差异,一个看不起一个!

更新时间:2024-05-10
[转载]程序员也分三六九等?等级差异,一个看不起一个!
文章标题:[转载]海贼王 动漫 全集目录 分章节 精彩打斗剧集

更新时间:2024-01-12
[转载]海贼王 动漫 全集目录 分章节 精彩打斗剧集
名词解释
作为当前文章的名词解释,仅对当前文章有效。
描述符在Python编程中,描述符是一种特殊类型的对象,它实现了`__get__()`、`__set__()`和`__delete__()`这三个方法。当一个类的属性是描述符时,Python会根据这些方法自动管理对该属性的访问、修改和删除操作。描述符在Python面向对象编程中扮演着重要角色,例如,property装饰器就是基于描述符实现的,它可以控制对类属性的存取权限,并且可以用来创建计算属性或惰性属性。
数据描述符在Python中,数据描述符是指同时实现了`__get__()`和`__set__()`方法的描述符对象。数据描述符拥有比非数据描述符更高的优先级,这意味着即使实例字典中有同名的属性,Python解释器也会优先调用数据描述符的方法来处理属性的读取和设置操作。
非数据描述符非数据描述符是指只实现了`__get__()`方法但未实现`__set__()`方法的描述符。与数据描述符相比,非数据描述符不控制属性的赋值操作。当尝试访问一个非数据描述符属性时,如果该属性在实例字典中存在,则返回实例字典中的值,否则查找并调用类字典中描述符的`__get__()`方法获取值。
新式类在Python 2.2版本引入的新类定义方式,继承自内置的`object`基类,也称为面向对象编程的“新风格”类。新式类中提供了许多Python面向对象特性,如元类、描述符协议以及方法解析顺序等。在Python 3.x版本中,所有类默认都是新式类,无需显式继承自`object`。
惰性计算属性惰性计算属性是一种编程模式,通过描述符或其他机制实现,在真正需要属性值时才进行计算,并将结果缓存起来,以供后续访问直接使用,避免了不必要的重复计算。在文章中给出的`lazyproperty`例子中,只有首次访问`area`属性时才会触发面积计算,之后再次访问时则直接返回之前计算的结果。
延伸阅读
作为当前文章的延伸阅读,仅对当前文章有效。
在深入理解Python描述符这一核心机制后,进一步探究其在实际开发中的应用和最新进展显得尤为重要。近期,Python社区围绕着描述符的应用与优化展开了许多讨论和实践。
例如,在Django框架的2.2版本中,开发者更加广泛地运用描述符来实现模型字段的动态行为,如`django.db.models.fields.files.FieldFile`就是利用描述符实现文件字段的上传、下载及删除等功能。此外,针对数据验证和业务逻辑封装,一些高级ORM库也引入了自定义描述符设计模式,以提供更为灵活且安全的数据访问控制。
另一方面,Python 3.9引入了新的`__set_name__`方法,该方法适用于描述符对象,以便在描述符被绑定到类属性时通知其宿主类和名称,为描述符提供了更多的上下文信息,增强了其在复杂场景下的适用性和可读性。
同时,随着Python异步编程的发展,一些库也开始尝试将描述符应用于异步环境,比如通过实现异步描述符来控制异步属性的获取和设置,确保在处理并发请求时能够遵循正确的执行顺序,从而提高程序性能和稳定性。
综上所述,描述符作为Python面向对象编程的核心技术之一,其应用正不断拓展深化,并随着Python语言的演进保持着极高的时效性和实用性。对于开发者而言,掌握并合理运用描述符机制不仅能提升代码质量,还能有效应对各种复杂的业务场景需求。
知识学习
实践的时候请根据实际情况谨慎操作。
随机学习一条linux命令:
find /path/to/search -name "filename" - 在指定目录下递归查找文件。
随便看看
拉到页底了吧,随便看看还有哪些文章你可能感兴趣。
纯js实用T恤衫花纹图案预览特效 01-26 基于Bootstrap仿Github样式下拉列表框插件 08-08 jQuery电子邮件地址填写自动完成插件 04-30 Superset 数据源连接配置:精细化自定义SQLAlchemy URI实现数据分析与可视化,含SSL加密连接实例 03-19 jquery可任意拖动排序的导航图片效果 02-23 侧边栏个人图文简历HTML模板 12-09 Beego框架升级中的Bee工具版本兼容性问题与迁移策略:结构变更、功能接口变动及社区解决方案 12-07 Kibana无法启动:针对服务器内部错误的Elasticsearch连接、配置文件、端口冲突与资源排查解决(注:由于字数限制,未能完全包含所有关键词,但包含了核心问题描述及几个关键排查点) 11-01 ClickHouse外部表使用中文件权限与不存在问题的解决方案:错误提示、查询操作与文件路径管理实务 09-29 本次刷新还10个文章未展示,点击 更多查看。
Apache Atlas UI无法正常加载与样式丢失问题排查及解决方案:关注网络连接、浏览器缓存与开发者工具应用 09-25 Greenplum数据库中数据插入操作详解:单行多行插入与gpfdist实现大批量导入 08-02 [转载]html5 footer header,html-5 --html5教程article、footer、header、nav、section使用 07-16 [转载][GCC for C]编译选项---IDE掩盖下的天空 06-29 简洁大方珠宝钻石收藏网站模板下载 06-20 黑色高端精致汽车4s店美容html5模板下载 06-01 蓝色互联网项目融资管理平台网站模板 05-16 响应式游戏开发类企业前端cms模板下载 05-02 Beego框架动态路由实现:重定向与命令行参数驱动的路由设计实践 04-05 .NET 中字典操作避免 KeyNotFoundException:TryGetValue、ContainsKey 与 GetOrAdd 实践详解 04-04 [转载]2021/4/23爬虫第五次课(爬虫网络请求模块下下) 03-01
时光飞逝
"流光容易把人抛,红了樱桃,绿了芭蕉。"