根据网上的一些博客和自己的理解,说一下自己的想法,某些地方加了一些自己的理解,某些地方直接摘抄他人的,具体可以直接看例子,如有不准确非常希望大家指正,如有表达不好请大家见谅。
学习地址:dreamfor, 伯乐在线, fanyuchen
情景一:
# -*- coding: utf-8 -*-
class A(object):
t = []
t1 = 0
def add(self, c):
print c,'int id',id(self.t),c,'list id', id(self.t1)
self.t1 += 1
self.t.append(1)
print c,'int id',id(self.t),c,'list id', id(self.t1)
print self.t1, self.t
def sss(self, c):
print self.t1, self.t
a = A()
a1 = a.add('a')
print id(a1)
a2 = a.add('a')
print id(a2)
a.add('a')
a.sss('a')
b = A()
b1 = b.add('b')
print id(b1)
b.add('b’)
b.sss('b')
a.sss('a')
结果:
a int id 4377846200 a list id 140559058517120
a int id 4377846200 a list id 140559058517096
1 [1]
4410593176
a int id 4377846200 a list id 140559058517096
a int id 4377846200 a list id 140559058517072
2 [1, 1]
4410593176
a int id 4377846200 a list id 140559058517072
a int id 4377846200 a list id 140559058517048
3 [1, 1, 1]
3 [1, 1, 1]
b int id 4377846200 b list id 140559058517120
b int id 4377846200 b list id 140559058517096
1 [1, 1, 1, 1]
b int id 4377846200 b list id 140559058517096
b int id 4377846200 b list id 140559058517072
2 [1, 1, 1, 1, 1]
2 [1, 1, 1, 1, 1]
3 [1, 1, 1, 1, 1]
情景二:
class Test():
test = 10
# 情形1
obj1 = Test()
obj2 = Test()
print obj1.test, obj2.test, Test.test
# 情形2
obj1.test += 2
print obj1.test, obj2.test, Test.test
# 情形3
Test.test += 3
print obj1.test, obj2.test, Test.test
# 情形4
obj2.test += 2
print obj1.test, obj2.test, Test.test
# 情形5
Test.test += 3
print obj1.test, obj2.test, Test.test
结果:
10 10 10
12 10 10
12 13 13
12 15 13
12 15 16
首先看 情景一:
原因是因为 t,t1属性被称为类属性,既然是类属性,那么根据从C++/Java这种静态语言使用的经验来判断,类属性应该是为其实例所共享的。
其中 int类型为不可变对象,可以看到实例方法a每次调用其id一直是一样,但是看最后获取实例a和实例b的int对象时:看到虽然ID相同,但是值不同,而实例方法每次调用的ID相同
也就是说 类属性如果是不可变对象:类属性是被实例所共享的,不可变对象每次修改都是重新赋值, 在对类实例属性为不可变对象进行赋值的时候,实际上根据规则,每次引用或者修改的都是上一级的固定地址
而list类型为可变对象,实例方法a每次调用都改变ID,列表内容增加,而在调用实例b 看到list的ID 又变成了第一次调用实力方法a时的ID
根据Python中属性的获取存在一个向上查找机制, 每一个实例的每次调用,都要记录他的上一级关系,如果根据可变对象内存地址不变是没办法做到的,所以每次调用要创开辟的内存地址
接着看情景二:
Python属于动态强类型的语言,在很多地方和静态语言不同, Python中属性的获取存在一个向上查找机制
Python中一切皆对象,Test属于类对象,obj1属于实例对象,从对象的角度来看,Test与obj1是两个无关的对象,但是,Python通过下面的查找树建立了类对象Test与实例对象obj1、obj2之间的关系。
如图所示
Test
|
-----
| |
obj1 obj2
当调用Test.test时,直接从Test获取其属性test。
但是情形1中调用obj1.test时,Python按照从obj1到Test的顺序由下到上查找属性test。
值得注意的这时候obj1是没有属性test的,于是,Python到类Test中去查找,成功找到,并显示出来。所以,从现象上来看,Test的属性test确实是共享给其所有实例的,虽然这里只是从查找树的形式模拟了其关系。
Python中的属性设置
原帖子的作者也指出问题的关键在于情形2中obj1.test += 2。
为什么呢?
上面我们指出test += 2包含了属性获取及属性设置两个操作。即obj1.test+= 2等价于obj1.test = obj1.test + 2。
其中等式右侧的obj.test属于属性获取,其规则是按照上面提到的查找规则进行,即,这时候,获取到的是Test的属性test,所以等式左侧的值为12。
第二个操作是属性设置,即obj.test = 12。当发生属性设置的时候,obj1这个实例对象没有属性test,因此会为自身动态添加一个属性test。
由于从对象的角度,类对象和实例对象属于两个独立的对象,所以,这个test属性只属于obj1,也就是说,这时候类对象Test和实例对象test各自有一个属性test。
那么,在情形3中,再次调用obj1.test时,按照属性调用查找规则,这个时候获取到的是实例对象obj1的属性test,而不是类对象Test的属性test。