类属性和实例属性
首先来看看类属性和类实例的属性在python中如何存储,通过__dir__方法来查看对象的属性
>>
>
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__保存的对象的属性,看下面一个例子
>>
>
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’键,这就是这个类的属性,其值就是类属性的数据.
接来看,看看它的实例属性
>>
>
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__如何发生变化
# 定义类
>>
>
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__类属性,在元组中存储实例属性,不用字典,从而节省大量内存
# 在类中定义__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
"
"
,
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__,实例不能作为弱引用的目标
属性的魔术方法
来看几个魔术方法
- __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
"
"
,
line
1
,
in
<
module
>
File
"
"
,
line
4
,
in
__getattribute__
AttributeError
:
'B'
object
has no attribute
'y'
Property函数
porperty可以作为装饰器使用把方法标记为特性
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可以将函数封装为属性
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
这段代码可以正常运行,但是属性的调用方式可以改进,如下 :
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
使用魔术方法实现:
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__中有相应的属性,就直接返回其结果;如果没有,会到类属性中找。
看下面一个例子:
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,这在前面已经看到了。