python的__get__方法看这一篇就足够了
get类型函数
直接上代码:
class TestMain:
def __init__(self):
print('TestMain:__init__')
self.a = 1 if __name__ == '__main__':
t = TestMain()
print(t.a)
在没有任何get函数的情况下很简单,打印结果是:
TestMain:__init__
但是如果访问一个不存在的属性:
if __name__ == '__main__':
t = TestMain()
print(t.a)
print(t.b) # 访问了一个不存在的属性
结果是:
TestMain:__init__
Traceback (most recent call last): File "C:/Users/sk-leilin/Desktop/mpdp-code-master/test.py", line , in <module>
print(t.b)
AttributeError: 'TestMain' object has no attribute 'b'
可以看见报错了,下载我们来测试一下__getattr__函数:
class TestMain:
def __init__(self):
print('TestMain:__init__')
self.a = 1 def __getattr__(self, item):
print('TestMain:__getattr__')
return 2 if __name__ == '__main__':
t = TestMain()
print(t.a)
print(t.b)
打印结果是:
TestMain:__init__ TestMain:__getattr__
我们仍然访问了一个本来不存在的t.b,为什么这里没有报错呢,因为我们定义了__getattr__函数,而且让它直接返回了2,也就是说,如果定义了这个函数后,访问不存在的属性,会自动调用这个函数作为返回值。
接下来我们看一下__getattribute__这个函数:
class TestMain:
def __init__(self):
print('TestMain:__init__')
self.a = 1 def __getattr__(self, item):
print('TestMain:__getattr__')
return 2 def __getattribute__(self, item):
print('TestMain:__getattribute__')
return 3 if __name__ == '__main__':
t = TestMain()
print(t.a)
print(t.b)
打印结果是:
TestMain:__init__
TestMain:__getattribute__ TestMain:__getattribute__
可以看到,无论是访问存在的t.a还是不存在的t.b,都访问到了__getattribute__这个函数,也就是说,只要定义了这个函数,那么属性的访问,都会走到这个函数里面。
我们在看下面的代码:
class TestMain:
def __init__(self):
print('TestMain:__init__')
self.a = 1 def __getattr__(self, item):
print('TestMain:__getattr__')
return 2 def __getattribute__(self, item):
print('TestMain:__getattribute__')
if item == 'c':
raise AttributeError
return 3 if __name__ == '__main__':
t = TestMain()
print(t.a)
print(t.b)
print(t.c)
我们知道只要定义了__getattribute__函数,就肯定执行这个函数来获取属性,这次我们增加了判断如果访问c这个属性,我们抛出异常,最后的结果是:
TestMain:__init__
TestMain:__getattribute__ TestMain:__getattribute__ TestMain:__getattribute__
TestMain:__getattr__
也就是说,如果__getattribute__抛出了AttributeError异常,那么会继续访问__getattr__函数的。
总结:
- 如果定义了
__getattribute__,那么无论访问什么属性,都是通过这个函数获取,包括方法,t.f()这种也是访问的这个函数,此时这个函数应该放回一个方法,如果像例子中,仍然返回一个数字,你会获得一个TypeError: 'int' object is not callable错误- 只要定义了
__getattribute__方法,不管你访问一个存在的还是不存在的属性,都由这个方法返回,比如访问t.a,虽然a存在,但是只要定义了这个访问,那么就不是访问最开始的a了- 如果
__getattribute__抛出了AttributeError异常,并且定了了__getattr__函数,那么会调用__getattr__这个函数,不论这个属性到底是不是存在- 也就是说属性访问的一个大致优先级是:
__getattribute__>__getattr__>__dict__
单独说一说__get__函数
上面说了__getattribute__和__getattr__,这里单独说一下__get__,因为这个涉及到其它的概念,就是描述器(Descriptor)。
一个类只要实现了
__get__,__set__,__delete__中任意一个方法,我们就可以叫它描述器(descriptor)。如果只定义了__get__我们叫非资料描述器(non-data descriptor),如果__set__,__delete__任意一个/或者同时出现,我们叫资料描述器(data descriptor)。
首先明确一点,拥有这个方法的类,应该(也可以说是必须)产生一个实例,并且这个实例是另外一个类的类属性(注意一定是类属性,通过self的方式产生就不属于__get__范畴了)。
也就是说拥有这个方法的类,那么它的实例应该属于另外一个类/对象的一个属性。 直接看代码吧:
class TestDes:
def __get__(self, instance, owner):
print(instance, owner)
return 'TestDes:__get__' class TestMain:
des = TestDes() if __name__ == '__main__':
t = TestMain()
print(t.des)
print(TestMain.des)
其中TestDes定义了__get__方法,在TestMain中,定义了一个类属性des,是TestDes的一个实例,我们访问t.des或者TestMain.des的时候访问的就是访问了TestDes的__get__方法。
打印结果是:
<__main__.TestMain object at 0x0000022563D5D3C8> <class '__main__.TestMain'>
TestDes:__get__
None <class '__main__.TestMain'>
TestDes:__get__
其中,__get__方法的第一个参数是实际拥有者的实例,如果没有则为None,第二个参数是实际所属的类。
看一下下面的代码:
class TestDes:
def __get__(self, instance, owner):
print(instance, owner)
return 'TestDes:__get__' class TestMain:
def __init__(self):
self.des = TestDes() if __name__ == '__main__':
t = TestMain()
print(t.des)
# print(TestMain.des) #很明显这里会报错
我们通过__init__来产生了一个实例的des属性,这时候,print(t.des)访问的就不是__get__函数了,实际打印结果是:
<__main__.TestDes object at 0x00000165A77ECCF8>
也就是当成一个普通的实例来处理的。
非资料描述器,也就是只有
__get__,不管是类还是实例去访问,默认都获得的是__get__的返回值,但是,如果中间有任何一次重新赋值,那么,这个实例获得的是新的值(对象),已经和原来的描述器完全脱离了关系
资料描述器,比如有__set__方法,后期通过实例对描述器进行赋值,那么访问的是__set__,并且永远关联起来。但是如果通过修改类属性的方式复制,那么也会被重新获取新的值(对象)。
看下面的代码:
class TestDes:
def __get__(self, instance, owner):
print('TestDes:__get__', instance, owner)
return 'TestDes:__get__' class TestMain:
des = TestDes() if __name__ == '__main__':
t = TestMain()
print(t.des)
print(TestMain.des) print() t.des = 1
print(t.des)
print(TestMain.des) print() TestMain.des = 1
print(t.des)
print(TestMain.des)
上面是一个非资料描述器,打印结果是:
TestDes:__get__ <__main__.TestMain object at 0x000002C9BCCF0080> <class '__main__.TestMain'>
TestDes:__get__
TestDes:__get__ None <class '__main__.TestMain'>
TestDes:__get__ TestDes:__get__ None <class '__main__.TestMain'>
TestDes:__get__
具体根据上面的描述行为进行分析,就可以得出结果了。
我们在看一下资料描述器:
class TestDes:
def __get__(self, instance, owner):
print('TestDes:__get__', instance, owner)
return 'TestDes:__get__' def __set__(self, instance, value):
print('TestDes:__set__', instance, value) # 其它代码没有修改
打印结果如下:
TestDes:__get__ <__main__.TestMain object at 0x000002140A46D390> <class '__main__.TestMain'>
TestDes:__get__
TestDes:__get__ None <class '__main__.TestMain'>
TestDes:__get__ TestDes:__set__ <__main__.TestMain object at 0x000002140A46D390>
TestDes:__get__ <__main__.TestMain object at 0x000002140A46D390> <class '__main__.TestMain'>
TestDes:__get__
TestDes:__get__ None <class '__main__.TestMain'>
TestDes:__get__
总结
__getattribute__和__getattr__用于实例访问属性使用,拥有__get__方法的类是只能其实例属于类属性的时候生效- 只要有
__getattribute__,任何属性访问都是这个的返回值,以下都是在__getattribute__不存在或者有AttributeError异常发生的情况下描述的 - 访问不存在的属性,
__getattr__生效 - 访问存在的属性,如果是描述器,描述器生效
- 如果通过实例对描述器进行赋值操作,又有资料和非资料描述器的区分,如果定义了
__set__,那么此方法生效,并且仍然是原始的资料描述器,否则被赋值为新对象 - 描述器赋值如果是通过类的属性方式赋值,而不是类的实例方式赋值,描述器失效
针对描述器的说明: 描述器是被
__getattribute__调用的,如果重写了这个方法,将会阻止自动调用描述器,资料描述器总是覆盖了实例的__dict__, 非资料描述器可能覆盖实例的__dict__。
python的__get__方法看这一篇就足够了的更多相关文章
- Python 变量详解[学习 Python 必备基础知识][看此一篇就够了]
您的"关注"和"点赞",是信任,是认可,是支持,是动力...... 如意见相佐,可留言. 本人必将竭尽全力试图做到准确和全面,终其一生进行修改补充更新. 目录 ...
- Mybatis-Plus常用的查询方法--看这一篇就够了!!!
前言: Mybatis-Plus作为Mybatis的增强,自己封装了很多简单还用的方法,来解脱自己写sql! 对于项目的搭建小编就不在说了,可以参考: SpringBoot+Mybatis-Plus的 ...
- 手写Promise看着一篇就足够了
目录 概要 博客思路 API的特性与手写源码 构造函数 then catch Promise.resolved Promise.rejected Promise.all Promise.race 概要 ...
- 有关Spring事务,看这一篇就足够了
本文将按照声明式事务的五个特性进行介绍: 事务传播机制 事务隔离机制 只读 事务超时 回滚规则 Spring事务传播机制 事务的特性 原子性(Atomicity):事务是一个原子操作,由一系列动作组成 ...
- Java多线程看这一篇就足够了(吐血超详细总结)
进程与线程 进程是程序的一次动态执行过程,它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,这个过程也是进程本身从产生,发展到最终消亡的过程.多进程操作系统能同时达运行多个进程(程序),由于 ...
- java开发环境配置,看这一篇就足够了!
可能平时大家对于安装环境的需求不是那么强烈,但是当你换了一台新电脑时,你就会发现怎么也得花费你几个小时乃至半天一天的时间.故此整理此篇文章,给有需要的小伙伴 注:本文皆win10环境 (1).JDK的 ...
- python面试看这一篇就够了
python-面试通关宝典 有面Python开发方向的,看这一个repo就够啦? 语言特性 1.谈谈对 Python 和其他语言的区别 Python属于解释型语言,当程序运行时,是一行一行的解释,并运 ...
- 看完100篇Python技术精华文章,平均涨薪30%!
一个以技术为立身根基的教育机构做出来的微信号,干货程度会有多高? 马哥Linux运维公众号运营五年,从一开始的定位就是给技术人分享加薪干货的地方.这五年里,公众号运营最重的任务就是做内容.内容并不好做 ...
- 传说中Python最难理解的点,看这完篇就够了
本文转载自简书,作者为菜鸟,感谢作者的辛苦付出. 这不是我第一次学Python入门课,去年.前年我都学过Python入门.所以文章的标题一点都没有标题党的意思.但是整个入门篇还有一个最难的东西没有讲, ...
随机推荐
- Docker基本命令及工作原理
第一个Docker容器 1.首先确保Docker运行正常:docker info
- python实现从文件夹随机拷贝出指定数量文件到目标文件夹
为了方便倒腾数据,功能如题,该脚本和操作目录在同一根目录 实际运行时要手动修改程序中:cpfile_rand('img', 'outfile', 10) # 操作目录,输出目录,输出数量 import ...
- plsql启动报 Using filter for all users can lead to poor perform
首先,这个与Oracle配置无关,就是在使用pl/sql左侧树形目录时会看到非常多的和你当前工作无关的表,视图,序列等,导致打开速度慢. 解决办法:Tools-->Object browser ...
- socket 建立网络连接,client && server
client代码: package socket; import java.io.IOException; import java.net.Socket; /** * 客户端_聊天室 * * @aut ...
- Spring系列.Bean简介
Bean属性配置 Spring在读取配置文件中bean的metadata后会构造一个个BeanDefination对象.后面Spring会根据这些BeanDefinition创建对象.在配置一个bea ...
- 堆/题解 P3378 【【模板】堆】
概念: 堆就是一颗二叉树,满足父亲节点总是比儿子节点大(小).因此,堆也分为大根堆和小根堆,大根堆就是父亲节点比儿子节点大,小根堆正好相反.注意加粗的地方,是每一个节点哦!!!!! 还是直接看例题吧, ...
- 5000+字硬核干货!Redis 分布式集群部署实战
原理: Redis集群采用一致性哈希槽的方式将集群中每个主节点都分配一定的哈希槽,对写入的数据进行哈希后分配到某个主节点进行存储. 集群使用公式(CRC16 key)& 16384计算键key ...
- Linux服务器定时脚本
crontab -e 进入编辑模式,同vi编辑器操作. 用户所建立的crontab文件中,每一行都代表一项任务,每行的每个字段代表一项设置,它的格式共分为六个字段,前五段是时间设定段,第六段是要执行的 ...
- 看看有哪些 Web 认证技术.
BASIC 认证 BASIC 认证(基本认证)是从 HTTP/1.0 就定义的认证方式. BASIC 认证会将"用户名:密码"经过 Base64 加密后放入请求头部的 Author ...
- 理解并使用CSS3中的单位rem vh vw vmin vmax
rem vh vw vmin vmax做为CSS3中的新单位,其实都出来挺久的了,这篇文章将总结并理解下它们. rem 如果你给body设置了font-size字体大小,那么body的任何子元素的1e ...