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属性描述符(二)相关推荐

  1. python面试自我介绍_python自我描述

    广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! 请用python做一个自我介绍经过上面的学习,相信你已经可以用python完成一 ...

  2. python里的属性是什么_python属性 python 里的属性是什么意思?

    python 里的属性是什么意思? 刚开始看python教程,不是很明白."属性,属性方法,方法"这些是什么意你说的是对象吧 Python是面向对象的语言,在python中一切皆对 ...

  3. python @修饰符_python函数修饰符@的使用方法解析

    python函数修饰符@的作用是为现有函数增加额外的功能,常用于插入日志.性能测试.事务处理等等. 创建函数修饰符的规则: (1)修饰符是一个函数 (2)修饰符取被修饰函数为参数 (3)修饰符返回一个 ...

  4. python使用什么作为续行符_python的换行符是什么?

    python换行符是什么? python换行符是"\n". Windows换行符是'\r\n', Unix/Linux的换行符为'\n', Mac的换行符为'\r', 在pytho ...

  5. python中空位符_python中空位符_Python之路 - Python - 字符编码 - 期权论坛

    字符编码 python解释器在加载 .py 文件中的代码时,会对内容进行编码(默认ascill) ASCII(American Standard Code for Information Interc ...

  6. python基础代码技巧_Python 代码优化技巧(二)

    Python 是一种脚本语言,相比 C/C++ 这样的编译语言,在效率和性能方面存在一些不足,但是可以通过代码调整来提高代码的执行效率.本文整理一些代码优化技巧. 代码优化基本原则代码正常运行后优化. ...

  7. python顺序结构实验_Python程序设计实验报告二:顺序结构程序设计

    安徽工程大学 Python程序设计 实验报告 班级 物流192 姓名方伟虎学号3190505205 成绩 日期 2020.3.20 指导老师修宇 [实验名称] 实验二 顺序结构程序设计(验证性实验) ...

  8. python顺序结构实验_Python程序设计实验报告二:顺序结构程序设计(验证性实验)...

    安徽工程大学 Python程序设计 实验报告 班级 物流191 姓名姚彩琴学号3190505129 成绩 日期 2020.3.3 指导老师修宇 [实验名称] 实验二 顺序结构程序设计(验证性实验) [ ...

  9. python顺序结构实验设计_Python程序设计实验报告二:顺序结构程序设计

    安徽工程大学 Python程序设计实验报告 班级 物流192 姓名 周立 学号 3190505227成绩 日期 3月4日 指导老师修宇 实验二 顺序结构程序设计(验证性实验) [实验目的] (1)掌握 ...

最新文章

  1. 机器人产业的前途取决于人工智能关键技术的发展
  2. android Binary XML file line #1: Binary XML file line #1: Error inflating class x 问题详解
  3. 【IBM Tivoli Identity Manager 学习文档】11 TIM设计思路介绍
  4. POJ 2420 A Star not a Tree?【爬山法】
  5. ngod规范_NGOD的架构说明与比较
  6. java开发属于后端吗,值得一读!
  7. 使用python和javascript进行数据可视化
  8. 大厂运维必备技能:PB级数据仓库性能调优
  9. 17. QTreeView 简单用法
  10. python xlwings api_Python - xlwings基本使用
  11. 【Flink】Flink Elasticsearch client is not connected to any Elasticsearch nodes
  12. Java Colections 集合类 —— List、ArrayList、Set(HashSet)
  13. activiti 启动tomcat乱码_使用 IntelliJ IDEA 创建 Web 工程以及启动 Tomcat 乱码问题处理...
  14. js:数据结构笔记10--图和图算法
  15. 计算机音乐东京不太热,洛天依 - 东京不太热[FLAC格式]
  16. java服装销售系统_java服装管理销售系统
  17. Springboot毕业设计毕设作品,网上图书商城系统 开题报告
  18. 教你在M1芯片的imac一体机上安装PS2021 附教程和方法适用于所有Mac
  19. IOS漏洞频出!世界上真的存在没有漏洞的手机吗?
  20. 递归实现求最大公约数

热门文章

  1. cesium的clock开始,结束,控制速率
  2. 织梦留言板guestbook.htm加入头部导航
  3. js 获取url参数(QueryString)
  4. 第8章 硬盘和显卡的访问与控制
  5. (JAVA)CollectionDemo3
  6. 现代计算机应用特点,现代计算机的特点和计算机的发展
  7. 【算法设计与分析】05 有关函数的渐进的界的定理
  8. python字典遍历的几种方法(转)
  9. centos安装多个tomcat
  10. Mybatis入门---一对多、多对多