python 属性描述符_Python属性描述符(二)
Python存取属性的方式特别不对等,通过实例读取属性时,通常返回的是实例中定义的属性,但如果实例未曾定义过该属性,就会获取类属性,而为实例的属性赋值时,通常会在实例中创建属性,而不会影响到类本身。这种不对等的方式对描述符类也有影响。
def cls_name(obj_or_cls): # 传入一个实例,返回类名
cls = type(obj_or_cls)
if cls is type:
cls = obj_or_cls
return cls.__name__.split('.')[-1]
def display(obj):
cls = type(obj)
if cls is type: # 如果obj是一个类,则进入该分支
return ''.format(obj.__name__)
elif cls in [type(None), int]: # 如果obj是None或者数值,进入该分支
return repr(obj)
else: # 如果obj是一个实例
return ''.format(cls_name(obj))
def print_args(name, *args):
pseudo_args = ', '.join(display(x) for x in args)
print('-> {}.__{}__({})'.format(cls_name(args[0]), name, pseudo_args))
class Overriding: # <1>
"""a.k.a. data descriptor or enforced descriptor"""
def __get__(self, instance, owner):
print_args('get', self, instance, owner)
def __set__(self, instance, value):
print_args('set', self, instance, value)
class OverridingNoGet: # <2>
"""an overriding descriptor without ``__get__``"""
def __set__(self, instance, value):
print_args('set', self, instance, value)
class NonOverriding: # <3>
"""a.k.a. non-data or shadowable descriptor"""
def __get__(self, instance, owner):
print_args('get', self, instance, owner)
class Managed: # <4>
over = Overriding()
over_no_get = OverridingNoGet()
non_over = NonOverriding()
def spam(self): # <5>
print('-> Managed.spam({})'.format(display(self)))
有__get__和__set__方法的典型覆盖型描述符,在这个示例中,各个方法都调用了print_args()函数
没有__get__方法的覆盖型描述符
没有__set__方法,所以这是非覆盖型描述符
托管类,使用各个描述符类的一个实例
spam方法放这里是为了对比,因为方法也是描述符
覆盖型描述符
实现__set__方法的描述符属于覆盖型描述符,虽然描述符是类属性,但是实现了__set__方法的话,会覆盖对实例属性的赋值操作。特性也是覆盖型描述符,见Python动态属性和特性(二),如果没有提供设值函数,property类中的fset方法就会抛出AttributeError异常,指明那个属性时只读的
下面我们来看下覆盖型描述符的行为:
>>> obj = Managed() # <1>
>>> obj.over # <2>
-> Overriding.__get__(, , )
>>> Managed.over # <3>
-> Overriding.__get__(, None, )
>>> obj.over = 8 # <4>
-> Overriding.__set__(, , 8)
>>> obj.over # <5>
-> Overriding.__get__(, , )
>>> obj.__dict__["over"] = 9 # <6>
>>> vars(obj) # <7>
{'over': 9}
>>> obj.over # <8>
-> Overriding.__get__(, , )
创建托管实例Managed对象
obj.over触发描述符的__get__方法,__get__方法第一个参数是描述符实例,第二个参数的值是托管实例obj,第三个参数是托管类对象
Managed.over 触发描述符的__get__方法,第二个参数(instance)的值是 None,第一个和第三个同上
为obj.over赋值,触发描述符的__set__方法,最后一个参数的值是8
读取 obj.over,仍会触发描述符的__get__方法
跳过描述符,直接通过obj.__dict__属性设值
确认值在obj.__dict__属性中,在over键名下
然而,即使是名为over的实例属性,Managed.over描述符仍会覆盖读取 obj.over 这个操作
没有__get__方法的覆盖型描述符
>>> obj.over_no_get # <1>
>>> Managed.over_no_get # <2>
>>> obj.over_no_get = 8 # <3>
-> OverridingNoGet.__set__(, , 8)
>>> obj.over_no_get # <4>
>>> obj.__dict__['over_no_get'] = 6 # <5>
>>> obj.over_no_get # <6>
6
>>> obj.over_no_get = 7 # <7>
-> OverridingNoGet.__set__(, , 7)
>>> obj.over_no_get # <8>
6
描述符实例没有__get__方法,所以obj.over_no_get从类中获取描述符实例
直接从托管类中读取over_no_get属性,也就是描述符实例
为obj.over_no_get赋值hui会触发描述符类的__set__方法
因为__set__方法没有修改属性,所以在此读取obj.over_no_get获取的仍然是托管类中的描述符实例
通过实例的__dict__属性设置名为over_no_get的实例属性
现在,over_no_get实例属性会覆盖描述符实例
为obj.over_no_get实例属性赋值,仍然会经过__set__方法
读取时,只要有同名的实例属性,描述符实例就会被覆盖
非覆盖型描述符:没有实现__set__方法的描述符称为是非覆盖型描述符,如果设置了同名的实例属性,描述符会被覆盖,致使描述符无法处理那个实例的那个属性
>>> obj.non_over # <1>
-> NonOverriding.__get__(, , )
>>> obj.non_over = 6 # <2>
>>> obj.non_over # <3>
6
>>> Managed.non_over # <4>
-> NonOverriding.__get__(, None, )
>>> del obj.non_over # <5>
>>> obj.non_over # <6>
-> NonOverriding.__get__(, , )
obj.non_over触发描述符的__get__方法,第二个参数的值是obj
Managed.non_over是非覆盖型描述符,因此没有干涉赋值操作的__set__方法
obj有个名为non_over的实例属性,把Managed类的同名描述符属性遮盖掉
Managed.non_over描述符依然存在,会通过类截获这次访问
删除non_over实例属性
读取obj.non_over时,会触发类中描述符的__get__方法
覆盖类中的描述符:不管描述符是不是覆盖型的,为类属性赋值都能覆盖描述符
>>> obj = Managed() # <1>
>>> Managed.over = 1 # <2>
>>> Managed.over_no_get = 2
>>> Managed.non_over = 3
>>> obj.over, obj.over_no_get, obj.non_over # <3>
(1, 2, 3)
新建一个Managed实例
覆盖类中的描述符属性
通过实例访问描述符属性,新的值覆盖描述符
方法是描述符:在类中定义的函数属于绑定方法,用户定义的函数都有__get__方法,所以依附到类上时,就相当于描述符,方法是非覆盖型描述符
>>> obj = Managed()
>>> obj.spam # <1>
>
>>> Managed.spam # <2>
>>> obj.spam = 7 # <3>
>>> obj.spam
7
obj.spam获取的是绑定方法对象
Managed.spam获取的是函数
如果为obj.spam赋值,会遮盖类属性,导致无法通过obj实例访问spam方法
函数没有__set__方法,因此是非覆盖型描述符,从上面的例子来看,obj.spam和Managed.spam获取的是不同对象。与描述符一样,通过托管类访问时,函数__get__方法会返回自身的引用,但是通过实例访问时,函数的__get__方法返回的是绑定方法对象:一种可调用的对象,里面包装着函数,并把托管实例(如obj)绑定给函数的第一个参数(即self),这与functools.partial函数的行为一致
为了了解这种机制,让我们看下面一个例子:
import collections
class Text(collections.UserString):
def __repr__(self):
return 'Text({!r})'.format(self.data)
def reverse(self):
return self[::-1]
测试Text类:
>>> word = Text("forward")
>>> word # <1>
Text('forward')
>>> word.reverse() # <2>
Text('drawrof')
>>> Text.reverse(Text('backward')) # <3>
Text('drawkcab')
>>> type(Text.reverse), type(word.reverse) # <4>
(, )
>>> list(map(Text.reverse, ['repaid', (10, 20, 30), Text('stressed')])) # <5>
['diaper', (30, 20, 10), Text('desserts')]
>>> func = Text.reverse.__get__(word) # <6>
>>> func() # <7>
Text('drawrof')
>>> Text.reverse.__get__(None, Text) # <8>
>>> Text.reverse
>>> word.reverse # <9>
>>> Text.reverse.__get__(word)
>>> word.reverse.__self__ # <10>
Text('forward')
>>> word.reverse.__func__ is Text.reverse # <11>
True
Text实例的repr方法返回一个类似Text构造方法调用的字符串,可用于创建相同的实例
reverse方法返回反向拼写的单词
在类上调用方法并传入一个实例相当于调用实例的函数
从类获取方法和从实例获取方法的类型是不同的,一个是function,一个是method
Text.reverse相当于函数,甚至可以处理Text实例之外的其他对象
函数都是非覆盖型描述符。在函数上调用__get__方法时传入实例,得到的是绑定到那个实例上的方法
我们执行第六个步骤得到的func对象,与在实例上调用函数效果一样
调用函数的__get__方法时,如果instance 参数的值是None,那么得到的是函数本身
word.reverse表达式其实会调用Text.reverse.__get__(word),返回对应的绑定方法
绑定方法对象有个__self__属性,其值是调用这个方法的实例引用
绑定方法的__func__属性是依附在托管类上那个原始函数的引用
绑定方法对象还有个__call__方法,用于处理真正的调用过程,这个方法调用__func__属性引用的原始函数,把函数的第一个参数设置为绑定方法的__self__属性,这就是形参self的隐式绑定方式
描述符用法建议:
使用特性以保持简单:内置的property类创建的其实是覆盖型描述符,__set__方法和__get__方法都实现了,即便不定义设值方法也是如此。特性的__set__方法默认抛出AttributeError: can't set attribute异常,因此创建只读属性最简单的方式是使用特性
只读描述符必须有__set__方法:如果使用描述符类实现只读属性,__get__和__set__两个方法都必须定义,否则实例的同名属性会遮盖住描述符,只读属性的__set__方法只需要抛出AttributeError异常,并提供合适的错误信息
用于验证的描述符可以只有__set__方法:对仅用于验证的描述符来说,__set__方法应该检查value参数获得的值,如果有效,使用描述符实例的名称为键,直接在实例的__dict__ 属性中设置。这样,从实例中读取同名属性的速度很快,因为不用经过 __get__方法处理
仅有__get__方法的描述符可以实现高效缓存:如果只编写了__get__方法,那么创建的是非覆盖型描述符。这种描述符可用于执行某些耗费资源的计算,然后为实例设置同名属性,缓存结果。 同名实例属性会遮盖描述符,因此后续访问会直接从实例的__dict__ 属性中获取值,而不会再触发描述符的__get__方法
非特殊的方法可以被实例属性遮盖:由于函数和方法只实现了__get__方法,它们不会处理同名实例属性的赋值操作。因此,像 my_obj.the_method = 7这样简单赋值之后, 后续通过该实例访问the_method得到的是数字7——但是不影响类或其他实例。然而,特殊方法不受这个问题的影响。解释器只会在类中寻找特殊的方法,也就是说,repr(x)执行的其实是x.__class__.__repr__(x),因此x的__repr__属性对repr(x)方法调用没有影响。出于同样的原因,实例的_getattr__属性不会破坏常规的属性访问规则
python 属性描述符_Python属性描述符(二)相关推荐
- python面试自我介绍_python自我描述
广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! 请用python做一个自我介绍经过上面的学习,相信你已经可以用python完成一 ...
- python里的属性是什么_python属性 python 里的属性是什么意思?
python 里的属性是什么意思? 刚开始看python教程,不是很明白."属性,属性方法,方法"这些是什么意你说的是对象吧 Python是面向对象的语言,在python中一切皆对 ...
- python @修饰符_python函数修饰符@的使用方法解析
python函数修饰符@的作用是为现有函数增加额外的功能,常用于插入日志.性能测试.事务处理等等. 创建函数修饰符的规则: (1)修饰符是一个函数 (2)修饰符取被修饰函数为参数 (3)修饰符返回一个 ...
- python使用什么作为续行符_python的换行符是什么?
python换行符是什么? python换行符是"\n". Windows换行符是'\r\n', Unix/Linux的换行符为'\n', Mac的换行符为'\r', 在pytho ...
- python中空位符_python中空位符_Python之路 - Python - 字符编码 - 期权论坛
字符编码 python解释器在加载 .py 文件中的代码时,会对内容进行编码(默认ascill) ASCII(American Standard Code for Information Interc ...
- python基础代码技巧_Python 代码优化技巧(二)
Python 是一种脚本语言,相比 C/C++ 这样的编译语言,在效率和性能方面存在一些不足,但是可以通过代码调整来提高代码的执行效率.本文整理一些代码优化技巧. 代码优化基本原则代码正常运行后优化. ...
- python顺序结构实验_Python程序设计实验报告二:顺序结构程序设计
安徽工程大学 Python程序设计 实验报告 班级 物流192 姓名方伟虎学号3190505205 成绩 日期 2020.3.20 指导老师修宇 [实验名称] 实验二 顺序结构程序设计(验证性实验) ...
- python顺序结构实验_Python程序设计实验报告二:顺序结构程序设计(验证性实验)...
安徽工程大学 Python程序设计 实验报告 班级 物流191 姓名姚彩琴学号3190505129 成绩 日期 2020.3.3 指导老师修宇 [实验名称] 实验二 顺序结构程序设计(验证性实验) [ ...
- python顺序结构实验设计_Python程序设计实验报告二:顺序结构程序设计
安徽工程大学 Python程序设计实验报告 班级 物流192 姓名 周立 学号 3190505227成绩 日期 3月4日 指导老师修宇 实验二 顺序结构程序设计(验证性实验) [实验目的] (1)掌握 ...
最新文章
- 机器人产业的前途取决于人工智能关键技术的发展
- android Binary XML file line #1: Binary XML file line #1: Error inflating class x 问题详解
- 【IBM Tivoli Identity Manager 学习文档】11 TIM设计思路介绍
- POJ 2420 A Star not a Tree?【爬山法】
- ngod规范_NGOD的架构说明与比较
- java开发属于后端吗,值得一读!
- 使用python和javascript进行数据可视化
- 大厂运维必备技能:PB级数据仓库性能调优
- 17. QTreeView 简单用法
- python xlwings api_Python - xlwings基本使用
- 【Flink】Flink Elasticsearch client is not connected to any Elasticsearch nodes
- Java Colections 集合类 —— List、ArrayList、Set(HashSet)
- activiti 启动tomcat乱码_使用 IntelliJ IDEA 创建 Web 工程以及启动 Tomcat 乱码问题处理...
- js:数据结构笔记10--图和图算法
- 计算机音乐东京不太热,洛天依 - 东京不太热[FLAC格式]
- java服装销售系统_java服装管理销售系统
- Springboot毕业设计毕设作品,网上图书商城系统 开题报告
- 教你在M1芯片的imac一体机上安装PS2021 附教程和方法适用于所有Mac
- IOS漏洞频出!世界上真的存在没有漏洞的手机吗?
- 递归实现求最大公约数