只想回答一个问题: 当编译器要读取obj.field时, 发生了什么?
看似简单的属性访问, 其过程还蛮曲折的. 总共有以下几个step:
1. 如果obj 本身(一个instance )有这个属性, 返回. 如果没有, 执行 step 2
2. 如果obj 的class 有这个属性, 返回. 如果没有, 执行step 3.
3. 如果在obj class 的父类有这个属性, 返回. 如果没有, 继续执行3, 直到访问完所有的父类. 如果还是没有, 执行step 4.
4. 执行obj.__getattr__方法.
通过以下代码可以验证:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class A( object ): a = 'a' class B(A): b = 'b' class C(B): class_field = 'class field' def __getattr__( self , f): print ( 'Method {}.__getattr__ has been called.' . format ( self .__class__.__name__)) return f c = C() print c.a print c.b print c.class_field print c.c |
输出:
1
2
3
4
5
|
a b class field Method C.__getattr__ has been called. c |
PS: python里的attribute与property不同, 当使用了property里, property的解析优先级最高. 详见blog:从attribute到property.
补充知识:深入理解python对象及属性
类属性和实例属性
首先来看看类属性和类实例的属性在python中如何存储,通过__dir__方法来查看对象的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
>>> class Test( object ): pass >>> test = Test() # 查看类属性 >>> dir (Test) [ '__class__' , '__delattr__' , '__dict__' , '__doc__' , '__format__' , '__getattribute__' , '__hash__' , '__init__' , '__module__' , '__new__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__setattr__' , '__sizeof__' , '__str__' , '__subclasshook__' , '__weakref__' ] # 查看实例属性 >>> dir (test) [ '__class__' , '__delattr__' , '__dict__' , '__doc__' , '__format__' , '__getattribute__' , '__hash__' , '__init__' , '__module__' , '__new__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__setattr__' , '__sizeof__' , '__str__' , '__subclasshook__' , '__weakref__' ] |
我们主要看一个属性__dict__,因为 __dict__保存的对象的属性,看下面一个例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
>>> class Spring( object ): ... season = "the spring of class" ... # 查看Spring类保存的属性 >>> Spring.__dict__ dict_proxy({ '__dict__' : <attribute '__dict__' of 'Spring' objects>, 'season' : 'the spring of class' , '__module__' : '__main__' , '__weakref__' : <attribute '__weakref__' of 'Spring' objects>, '__doc__' : None }) # 通过两种方法访问类属性 >>> Spring.__dict__[ 'season' ] 'the spring of class' >>> Spring.season 'the spring of class' |
发现__dict__有个'season'键,这就是这个类的属性,其值就是类属性的数据.
接来看,看看它的实例属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
|
>>> s = Spring() # 实例属性的__dict__是空的 >>> s.__dict__ {} # 其实是指向的类属性 >>> s.season 'the spring of class' # 建立实例属性 >>> s.season = "the spring of instance" # 这样,实例属性里面就不空了。这时候建立的实例属性和类属性重名,并且把它覆盖了 >>> s.__dict__ { 'season' : 'the spring of instance' } >>> s.__dict__[ 'season' ] 'the spring of instance' >>> s.season 'the spring of instance' # 类属性没有受到实例属性的影响 >>> Spring.__dict__[ 'season' ] 'the spring of class' >>> Spring.__dict__ dict_proxy({ '__dict__' : <attribute '__dict__' of 'Spring' objects>, 'season' : 'the spring of class' , '__module__' : '__main__' , '__weakref__' : <attribute '__weakref__' of 'Spring' objects>, '__doc__' : None }) # 如果将实例属性删除,又会调用类属性 >>> del s.season >>> s.__dict__ {} >>> s.season 'the spring of class' # 自定义实例属性,对类属性没有影响 >>> s.lang = "python" >>> s.__dict__ { 'lang' : 'python' } >>> s.__dict__[ 'lang' ] 'python' # 修改类属性 >>> Spring.flower = "peach" >>> Spring.__dict__ dict_proxy({ '__module__' : '__main__' , 'flower' : 'peach' , 'season' : 'the spring of class' , '__dict__' : <attribute '__dict__' of 'Spring' objects>, '__weakref__' : <attribute '__weakref__' of 'Spring' objects>, '__doc__' : None }) >>> Spring.__dict__[ 'flower' ] 'peach' # 实例中的__dict__并没有变化 >>> s.__dict__ { 'lang' : 'python' } # 实例中找不到flower属性,调用类属性 >>> s.flower 'peach' |
下面看看类中包含方法,__dict__如何发生变化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
# 定义类 >>> class Spring( object ): ... def tree( self , x): ... self .x = x ... return self .x ... # 方法tree在__dict__里面 >>> Spring.__dict__ dict_proxy({ '__dict__' : <attribute '__dict__' of 'Spring' objects>, '__weakref__' : <attribute '__weakref__' of 'Spring' objects>, '__module__' : '__main__' , 'tree' : <function tree at 0xb748fdf4 >, '__doc__' : None }) >>> Spring.__dict__[ 'tree' ] <function tree at 0xb748fdf4 > # 建立实例,但是__dict__中没有方法 >>> t = Spring() >>> t.__dict__ {} # 执行方法 >>> t.tree( "xiangzhangshu" ) 'xiangzhangshu' # 实例方法(t.tree('xiangzhangshu'))的第一个参数(self,但没有写出来)绑定实例 t,透过 self.x 来设定值,即给 t.__dict__添加属性值。 >>> t.__dict__ { 'x' : 'xiangzhangshu' } # 如果没有将x 赋值给 self 的属性,而是直接 return,结果发生了变化 >>> class Spring( object ): ... def tree( self , x): ... return x >>> s = Spring() >>> s.tree( "liushu" ) 'liushu' >>> s.__dict__ {} |
需要理解python中的一个观点,一切都是对象,不管是类还是实例,都可以看成是对象,符合object.attribute ,都会有自己的属性
使用__slots__优化内存使用
默认情况下,python在各个实例中为名为__dict__的字典里存储实例属性,而字典会消耗大量内存(字典要使用底层散列表提升访问速度), 通过__slots__类属性,在元组中存储实例属性,不用字典,从而节省大量内存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
# 在类中定义__slots__属性就是说这个类中所有实例的属性都在这儿了,如果几百万个实例同时活动,能节省大量内存 >>> class Spring( object ): ... __slots__ = ( "tree" , "flower" ) ... # 仔细看看 dir() 的结果,还有__dict__属性吗?没有了,的确没有了。也就是说__slots__把__dict__挤出去了,它进入了类的属性。 >>> dir (Spring) [ '__class__' , '__delattr__' , '__doc__' , '__format__' , '__getattribute__' , '__hash__' , '__init__' , '__module__' , '__new__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__setattr__' , '__sizeof__' , '__slots__' , '__str__' , '__subclasshook__' , 'flower' , 'tree' ] >>> Spring.__slots__ ( 'tree' , 'flower' ) # 实例化 >>> t = Spring() >>> t.__slots__ ( 'tree' , 'flower' ) # 通过类赋予属性值 >>> Spring.tree = "liushu" # tree这个属性是只读的, 实例不能修改 >>> t.tree = "guangyulan" Traceback (most recent call last): File "<stdin>" , line 1 , in <module> AttributeError: 'Spring' object attribute 'tree' is read - only >>> t.tree 'liushu' # 对于用类属性赋值的属性,只能用来修改 >>> Spring.tree = "guangyulan" >>> t.tree 'guangyulan' # 对于没有用类属性赋值的属性,可以通过实例来修改 >>> t.flower = "haitanghua" >>> t.flower 'haitanghua' # 实例属性的值并没有传回到类属性,你也可以理解为新建立了一个同名的实例属性 >>> Spring.flower <member 'flower' of 'Spring' objects> # 如果再给类属性赋值 >>> Spring.flower = "ziteng" >>> t.flower 'ziteng' |
如果使用的当,__slots__可以显著节省内存,按需要注意一下问题
在类中定义__slots__之后,实例不能再有__slots__所列名称之外的其他属性
每个子类都要定义__slots__熟悉,因为解释器会忽略继承__slots__属性
如果不把__werkref__加入__slots__,实例不能作为弱引用的目标
属性的魔术方法
来看几个魔术方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
__setattr__( self ,name,value):如果要给 name 赋值,就调用这个方法。 __getattr__( self ,name):如果 name 被访问,同时它不存在的时候,此方法被调用。 __getattribute__( self ,name):当 name被访问时自动被调用(注意:这个仅能用于新式类),无论 name 是否存在,都要被调用。 __delattr__( self ,name):如果要删除 name,这个方法就被调用。 >>> class A( object ): ... def __getattr__( self , name): ... print "You use getattr" ... def __setattr__( self , name, value): ... print "You use setattr" ... self .__dict__[name] = value # a.x,按照本节开头的例子,是要报错的。但是,由于在这里使用了__getattr__(self, name) 方法,当发现 x 不存在于对象的__dict__中的时候,就调用了__getattr__,即所谓“拦截成员”。 >>> a = A() >>> a.x You use getattr # 给对象的属性赋值时候,调用了__setattr__(self, name, value)方法,这个方法中有一句 self.__dict__[name] = value,通过这个语句,就将属性和数据保存到了对象的__dict__中 >>> a.x = 7 You use setattr # 测试__getattribute__(self,name) >>> class B( object ): ... def __getattribute__( self , name): ... print "you are useing getattribute" ... return object .__getattribute__( self , name) # 返回的内容用的是 return object.__getattribute__(self, name),而没有使用 return self.__dict__[name]。因为如果用这样的方式,就是访问 self.__dict__,只要访问这个属性,就要调用`getattribute``,这样就导致了无限递归 # 访问不存在的成员,可以看到,已经被__getattribute__拦截了,虽然最后还是要报错的。 >>> b = B() >>> b.y you are useing getattribute Traceback (most recent call last): File "<stdin>" , line 1 , in <module> File "<stdin>" , line 4 , in __getattribute__ AttributeError: 'B' object has no attribute 'y' |
Property函数
porperty可以作为装饰器使用把方法标记为特性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class Vector( object ): def __init__( self , x, y): # 使用两个前导下划线,把属性标记为私有 self .__x = float (x) self .__y = float (y) # porperty装饰器把读值方法标记为特性 @property def x( self ): return self .__x @property def y( self ): return self .__y vector = Vector( 3 , 4 ) print (vector.x, vector.y) |
使用property可以将函数封装为属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class Rectangle( object ): """ the width and length of Rectangle """ def __init__( self ): self .width = 0 self .length = 0 def setSize( self , size): self .width, self .length = size def getSize( self ): return self .width, self .length if __name__ = = "__main__" : r = Rectangle() r.width = 3 r.length = 4 print r.getSize() # (3,4) r.setSize( ( 30 , 40 ) ) print r.width # 30 print r.length # 40 |
这段代码可以正常运行,但是属性的调用方式可以改进,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
class Rectangle( object ): """ the width and length of Rectangle """ def __init__( self ): self .width = 0 self .length = 0 def setSize( self , size): self .width, self .length = size def getSize( self ): return self .width, self .length # 使用property方法将函数封装为属性,更优雅 size = property (getSize, setSize) if __name__ = = "__main__" : r = Rectangle() r.width = 3 r.length = 4 print r.size # (30, 40) r.size = 30 , 40 print r.width # 30 print r.length # 40 |
使用魔术方法实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class NewRectangle( object ): def __init__( self ): self .width = 0 self .length = 0 def __setattr__( self , name, value): if name = = 'size' : self .width, self , length = value else : self .__dict__[name] = value def __getattr__( self , name): if name = = 'size' : return self .width, self .length else : raise AttrubuteErrir if __name__ = = "__main__" : r = Rectangle() r.width = 3 r.length = 4 print r.size # (30, 40) r.size = 30 , 40 print r.width # 30 print r.length # 40 |
属性的获取顺序
最后我们来看看熟悉的获得顺序:通过实例获取其属性,如果在__dict__中有相应的属性,就直接返回其结果;如果没有,会到类属性中找。
看下面一个例子:
1
2
3
4
5
6
7
8
9
10
|
class A( object ): author = "qiwsir" def __getattr__( self , name): if name ! = "author" : return "from starter to master." if __name__ = = "__main__" : a = A() print a.author # qiwsir print a.lang # from starter to master. |
当 a = A() 后,并没有为实例建立任何属性,或者说实例的__dict__是空的。但是如果要查看 a.author,因为实例的属性中没有,所以就去类属性中找,发现果然有,于是返回其值 “qiwsir”。但是,在找 a.lang的时候,不仅实例属性中没有,类属性中也没有,于是就调用了__getattr__()方法。在上面的类中,有这个方法,如果没有__getattr__()方法呢?如果没有定义这个方法,就会引发 AttributeError,这在前面已经看到了。