标题名字有点长。

之所以想写这个文章是因为碰巧看到网上一篇关于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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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属于动态强类型的语言,在很多地方和静态语言不同,因此,不能把静态语言的规则套到动态语言上来。其中,类属性就是一个很好的例子。
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属于实例对象,从对象的角度来看,AAAobj1是两个无关的对象,但是,Python通过下面的查找树建立了类对象AAA与实例对象obj1obj2之间的关系。
如图所示

 
AAA
|
-----
| |
obj1 obj2
1
2
3
4
5
        AAA
         |
       -----
      |     |  
    obj1   obj2

(图画的不好,见谅 -.-!!!)
当调用AAA.aaa时,直接从AAA获取其属性aaa
但是情形1中调用obj1.aaa时,Python按照从obj1AAA的顺序由下到上查找属性aaa
值得注意的这时候obj1是没有属性aaa,于是,Python到类AAA中去查找,成功找到,并显示出来。所以,从现象上来看,AAA的属性aaa确实是共享给其所有实例的,虽然这里只是从查找树的形式模拟了其关系。

Python中的属性设置

原帖子的作者也指出问题的关键在于情形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中的属性设置是针对对象本身进行的;

对情形1的解释

因为Python中的属性获取是按照从下到上的顺序来查找的,所以在情形1:

 
obj1 = AAA()
obj2 = AAA()
1
2
obj1 = AAA()  
obj2 = AAA()

实例对象obj1obj2不存在属性aaa
证明如下:

 
>>> obj1.__dict__
{}
>>> obj2.__dict__
{}
1
2
3
4
>>> obj1.__dict__
{}
>>> obj2.__dict__
{}

所以,此时,obj1.aaa, obj2.aaa, AAA.aaa实质上都是指AAA.aaa。因此,输出同样的结果。

对情形2的解释

因为Python中的类和实例是两个完全独立的对象Python中的属性设置是针对对象本身进行的,所以在情形2:

 
obj1.aaa += 2
1
obj1.aaa += 2  

实质上是对实例对象obj1设置了属性aaa,并赋值为12。证明如下:

 
>>> obj1.aaa = 3
>>> obj1.__dict__
{'aaa': 3}
>>> obj2.__dict__
{}
1
2
3
4
5
>>> 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. 理解动态语言是可以动态设置属性的

关于Python类属性与实例属性的讨论的更多相关文章

  1. Python类属性,实例属性

    1.Python类数据属性:定义在类里面但在函数外面的变量,它们都是静态的. #一段很简单的代码,但反应了很多 >>> class A(): a=1 #一个类里面有个属性a > ...

  2. python 类属性和实例属性

    class AAA(): aaa = 10 # 情形1 obj1 = AAA() obj2 = AAA() print obj1.aaa, obj2.aaa, AAA.aaa # 情形2 obj1.a ...

  3. python 类属性 、实例属性,可变数据结构作为类属性需要注意的地方

    1.一些经典的python错误不去阅读和不重视,就会把错误的做法带入到实际项目中来,甚至造成难以排查问题. 2.有一个大笨猪,按java写观察者模式,java写得是直接在类名下声明一个实例属性(不加s ...

  4. Python中的类属性、实例属性与类方法、静态方法

    1.什么是类对象,实例对象 类对象:类名 实例对象:类创建的对象 2.类属性就是类对象所拥有的属性,它被所有类对象的实例对象所共有,在内存中只存在一个副本,这个和C++.Java中类的静态成员变量有点 ...

  5. Python的程序结构[0] -> 属性/Property[0] -> 类属性、实例属性和私有属性

    类属性.实例属性和私有属性 Python中类的属性主要包括类属性,实例属性和私有属性,下面是对三种属性的简单介绍 类属性 / Class Property 类属性在__init__()之外初始化,在外 ...

  6. Python类属性与实例属性理解

    按理讲,类属性改变,类的实例对象这个属性也应该被改变,但是在python中实际却不是这样 class test(): name = 111 a = test() b = test() a.name = ...

  7. Python之路-面向对象&继承和多态&类属性和实例属性&类方法和静态方法

    一.面向对象 编程方式 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数进行分类和封装,让开发“更快更好更强…” 什么是面 ...

  8. Python基础:17类和实例之一(类属性和实例属性)

    1:类通常在一个模块的顶层进行定义.对于Python来说,声明与定义类是同时进行的. 2:类属性仅与其类相绑定,类数据属性仅当需要有更加“静态”数据类型时才变得有用,这种属性是静态变量.它们表示这些数 ...

  9. Python - 面向对象编程 - 类变量、实例变量/类属性、实例属性

    什么是对象和类 https://www.cnblogs.com/poloyy/p/15178423.html 什么是 Python 类.类对象.实例对象 https://www.cnblogs.com ...

随机推荐

  1. Matlab——filter函数用法

    filter:滤波函数,可用来解差分方程. y = filter(b,a,X) [y,zf] = filter(b,a,X) [y,zf] = filter(b,a,X,zi) y = filter( ...

  2. 《转》Python学习(17)-python函数基础部分

    http://www.cnblogs.com/BeginMan/p/3171977.html 一.什么是函数.方法.过程 推荐阅读:http://www.cnblogs.com/snandy/arch ...

  3. cnBlogs博客推荐

      数据结构和算法若可以称为为编程的细胞结构,那设计模式就是编程的灵魂气脉. 一个从是编程的微观演绎,一个是编程的宏观设计.这个从技术和艺术的结合体,毫无疑问是在世界末日之前的很伟大的一项发明. 设计 ...

  4. 【Python】协程

    协程,又称微线程,纤程.英文名Coroutine. 协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用. 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在 ...

  5. php判断正常访问和外部访问

    php判断正常访问和外部访问 <?php session_start(); if(isset($_POST['check'])&&!empty($_POST['name'])){ ...

  6. JS基础---->javascript的基础(一)

    记录一些javascript的基础知识.只是一起走过一段路而已,何必把怀念弄的比经过还长. javascript的基础 一.在检测一个引用类型值和 Object 构造函数时, instanceof 操 ...

  7. android基础---->WidGet的使用

    Widget是一个可以添加在别的应用程序中的”小部件”,我们可以使用自定义的Widget远程控制我们的程序做一些事情.一般用于在桌面上添加一个小部件,现在我们开始小部件的学习. 目录导航: WidGe ...

  8. Qt编写视频播放器(vlc内核)

    在研究qt+vlc的过程中,就想直接做个播放器用于独立的项目,vlc还支持硬件加速,不过部分电脑硬件不支持除外.用vlc的内核写播放器就是快,直接调用api就行,逻辑处理和ui展示基本上分分钟的事情, ...

  9. 【BZOJ2331】[SCOI2011]地板 插头DP

    [BZOJ2331][SCOI2011]地板 Description lxhgww的小名叫“小L”,这是因为他总是很喜欢L型的东西.小L家的客厅是一个的矩形,现在他想用L型的地板来铺满整个客厅,客厅里 ...

  10. 在jmeter测试中模拟不同的带宽环境

    怎么去测试在手机app中和在web的不同的连接速度对服务器的影响呢? 大部分情况下,手机终端用户通过移动网络访问网站.所以在不同的网络连接数据下,我们的网站或程序可以同时处理多少链接?今天,这篇文件就 ...