摘要:之所以想写这个文章是因为碰巧看到网上一篇关于中类属性及实例属性区别的帖子。中属性的获取对于属性,我们通常采用类属性或实例属性的形式调用。最关键的地方在于两点理解是如何利用查找树的机制来模仿类及实例之间的关系理解动态语言是可以动态设置属性的
标题名字有点长。
之所以想写这个文章是因为碰巧看到网上一篇关于Pyhon中类属性及实例属性区别的帖子。因为我之前也被这个问题困扰过,今天碰巧看到了这篇帖子,发现帖子的作者只是描述了现象,然后对原因的解释比较含糊,并没有从根本上解释这个问题,所以才想写一下我对这个问题的想法。
性子急的可以直接跳到最后看总结。
原帖子地址
为了方便对比,我还是使用原帖子的例子:
class AAA(): aaa = 10 # 情形1 obj1 = AAA() obj2 = AAA() print obj1.aaa, obj2.aaa, AAA.aaa # 情形2 obj1.aaa += 2 print obj1.aaa, obj2.aaa, AAA.aaa # 情形3 AAA.aaa += 3 print obj1.aaa, obj2.aaa, AAA.aaa
情形1的结果是:10 10 10;
情形2的结果是:12 10 10;
情形3的结果是:12 13 13;
首先为什么会有这个问题呢?
因为aaa属性被称为类属性,既然是类属性,那么根据从C++/Java这种静态语言使用的经验来判断,类属性应该是为其实例所共享的。很自然的,既然是共享关系,那么从类的层次改变aaa的值,自然其实例的aaa的值也要跟着变化了。
可是情形3的情况却说明,上面的说法是错的。
错哪里呢?
要从Python的类属性讲起
Python属于动态强类型的语言,在很多地方和静态语言不同,因此,不能把静态语言的规则套到动态语言上来。其中,类属性就是一个很好的例子。
Python中属性的获取
对于属性,我们通常采用类.属性或实例.属性的形式调用。
例如上例中的AAA.aaa属于类.属性形式,obj1.aaa属于实例.属性的形式
Python中属性的设置
对于属性的设置我们通常采用类.属性 = 值或实例.属性 = 值的形式
例如obj1.aaa = 3
上例中obj1.aaa += 2等价于obj1.aaa = obj1.aaa + 2,这句话包含了属性获取及属性设置两个操作
OK,重点来了,Python中属性的获取和设置的机制与静态语言是不同的,正是背后机制的不同,导致了Python中类属性不一定是为其实例所共享的
Python中属性查找机制Python中属性的获取存在一个向上查找机制,还是拿上面的例子做说明:
Python中一切皆对象,AAA属于类对象,obj1属于实例对象,从对象的角度来看,AAA与obj1是两个无关的对象,但是,Python通过下面的查找树建立了类对象AAA与实例对象obj1、obj2之间的关系。
如图所示
AAA | ----- | | obj1 obj2
(图画的不好,见谅 -.-!!!)
当调用AAA.aaa时,直接从AAA获取其属性aaa。
但是情形1中调用obj1.aaa时,Python按照从obj1到AAA的顺序由下到上查找属性aaa。
值得注意的这时候obj1是没有属性aaa的,于是,Python到类AAA中去查找,成功找到,并显示出来。所以,从现象上来看,AAA的属性aaa确实是共享给其所有实例的,虽然这里只是从查找树的形式模拟了其关系。
原帖子的作者也指出问题的关键在于情形2中obj1.aaa += 2。
为什么呢?
上面我们指出obj.aaa += 2包含了属性获取及属性设置两个操作。即obj1.aaa += 2等价于obj1.aaa = obj1.aaa + 2。
其中等式右侧的obj.aaa属于属性获取,其规则是按照上面提到的查找规则进行,即,这时候,获取到的是AAA的属性aaa,所以等式左侧的值为12。
第二个操作是属性设置,即obj.aaa = 12。当发生属性设置的时候,obj1这个实例对象没有属性aaa,因此会为自身动态添加一个属性aaa。
由于从对象的角度,类对象和实例对象属于两个独立的对象,所以,这个aaa属性只属于obj1,也就是说,这时候类对象AAA和实例对象aaa各自有一个属性aaa。
那么,在情形3中,再次调用obj1.aaa时,按照属性调用查找规则,这个时候获取到的是实例对象obj1的属性aaa,而不是类对象AAA的属性aaa。
到这里就可以完满解释上面的问题:
1. Python中属性的获取是按照从下到上的顺序来查找属性;
2. Python中的类和实例是两个完全独立的对象;
3. Python中的属性设置是针对对象本身进行的;
因为Python中的属性获取是按照从下到上的顺序来查找的,所以在情形1:
obj1 = AAA() obj2 = AAA()
实例对象obj1和obj2不存在属性aaa。
证明如下:
>>> obj1.__dict__ {} >>> obj2.__dict__ {}
所以,此时,obj1.aaa, obj2.aaa, AAA.aaa实质上都是指AAA.aaa。因此,输出同样的结果。
对情形2的解释因为Python中的类和实例是两个完全独立的对象且Python中的属性设置是针对对象本身进行的,所以在情形2:
obj1.aaa += 2
实质上是对实例对象obj1设置了属性aaa,并赋值为12。证明如下:
>>> obj1.aaa = 3 >>> obj1.__dict__ {"aaa": 3} >>> obj2.__dict__ {}
因此,再次调用obj1.aaa时,将获取到的是实例对象obj1的属性aaa,而不是类对象AAA的属性aaa。而对于实例对象obj2,由于其并没有属性aaa,所以调用obj2.aaa时,获取到的是AAA的属性aaa。
对情形3的解释顺利理解了前两个情形,那么第3个情形就很容易了,改变AAA的属性aaa只能影响到类对象AAA和实例对象obj2,不能影响obj1,因为,obj1存在aaa,在获取时,不会获取到AAA的属性。
写在最后的话问题本身很简单,但是通过对这个问题的探讨,可以深入理解Python作为一个动态语言,在OOP的机制上与静态语言的差别。
最关键的地方在于两点:
1. 理解Python是如何利用查找树的机制来模仿类及实例之间的关系;
2. 理解动态语言是可以动态设置属性的
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/37519.html
摘要:本篇内容将从鸭子类型的动态协议,逐渐过渡到使接口更明确能验证实现是否符合规定的抽象基类。抽象基类介绍完动态实现接口后,现在开始讨论抽象基类,它属于静态显示地实现接口。标准库中的抽象基类从开始,标准库提供了抽象基类。 《流畅的Python》笔记。本篇是面向对象惯用方法的第四篇,主要讨论接口。本篇内容将从鸭子类型的动态协议,逐渐过渡到使接口更明确、能验证实现是否符合规定的抽象基类(Abst...
摘要:中的类都是单例模式一天,一同事问我这样一个问题。与方法属于新式类,即属于类。方法在实例被创建之后被调用,该方法仅仅是对方法创建的实例进行一些初始化操作。需要注意的是,在重写方法与方法的参数应该保持一致,否则会有发生。 Python 中的类都是单例模式? 一天,一同事问我这样一个问题。这是一个奇怪的问题,可能你也这么认为。这里先不做解释,我们先来看看 __new__ 和 __init__...
摘要:本篇继续学习之路,实现更多的特殊方法以让自定义类的行为跟真正的对象一样。之所以要让向量不可变,是因为我们在计算向量的哈希值时需要用到和的哈希值,如果这两个值可变,那向量的哈希值就能随时变化,这将不是一个可散列的对象。 《流畅的Python》笔记。本篇是面向对象惯用方法的第二篇。前一篇讲的是内置对象的结构和行为,本篇则是自定义对象。本篇继续Python学习之路20,实现更多的特殊方法以让...
摘要:什么是元类刚才说了,元类就是创建类的类。类上面的属性,相信愿意了解元类细节的盆友,都肯定见过这个东西,而且为之好奇。使用了这个魔法方法就意味着就会用指定的元类来创建类了。深刻理解中的元类 (一) python中的类 今天看到一篇好文,然后结合自己的情况总结一波。这里讨论的python类,都基于python2.7x以及继承于object的新式类进行讨论。 首先在python中,所有东西都...
阅读 2616·2021-09-28 09:36
阅读 2228·2021-09-07 09:58
阅读 1491·2019-08-26 13:53
阅读 1275·2019-08-23 17:53
阅读 3023·2019-08-23 15:34
阅读 1849·2019-08-23 15:34
阅读 2862·2019-08-23 12:04
阅读 3716·2019-08-23 10:56