只想回答一个问题: 当编译器要读取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.__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()
>>> 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'
>>> s.__dict__
{ 'lang' : 'python' }
>>> 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
...
>>> 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 >
>>> t = Spring()
>>> t.__dict__
{}
>>> t.tree( "xiangzhangshu" )
'xiangzhangshu'
>>> t.__dict__
{ 'x' : 'xiangzhangshu' }
>>> 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
|
>>> class Spring( object ):
... __slots__ = ( "tree" , "flower" )
...
>>> 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"
>>> 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 = A()
>>> a.x
You use getattr
>>> a.x = 7
You use setattr
>>> class B( object ):
... def __getattribute__( self , name):
... print "you are useing getattribute"
... return object .__getattribute__( self , name)
>>> 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)
@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 ):
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()
r.setSize( ( 30 , 40 ) )
print r.width
print r.length
|
这段代码可以正常运行,但是属性的调用方式可以改进,如下:
?
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 ):
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
size = property (getSize, setSize)
if __name__ = = "__main__" :
r = Rectangle()
r.width = 3
r.length = 4
print r.size
r.size = 30 , 40
print r.width
print r.length
|
使用魔术方法实现:
?
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
r.size = 30 , 40
print r.width
print r.length
|
属性的获取顺序
最后我们来看看熟悉的获得顺序:通过实例获取其属性,如果在__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
print a.lang
|
当 a = A() 后,并没有为实例建立任何属性,或者说实例的__dict__是空的。但是如果要查看 a.author,因为实例的属性中没有,所以就去类属性中找,发现果然有,于是返回其值 “qiwsir”。但是,在找 a.lang的时候,不仅实例属性中没有,类属性中也没有,于是就调用了__getattr__()方法。在上面的类中,有这个方法,如果没有__getattr__()方法呢?如果没有定义这个方法,就会引发 AttributeError,这在前面已经看到了。
|