Python对象的属性访问过程详解

时间:2021-05-22

只想回答一个问题: 当编译器要读取obj.field时, 发生了什么?

看似简单的属性访问, 其过程还蛮曲折的. 总共有以下几个step:

1. 如果obj 本身(一个instance )有这个属性, 返回. 如果没有, 执行 step 2

2. 如果obj 的class 有这个属性, 返回. 如果没有, 执行step 3.

3. 如果在obj class 的父类有这个属性, 返回. 如果没有, 继续执行3, 直到访问完所有的父类. 如果还是没有, 执行step 4.

4. 执行obj.__getattr__方法.

通过以下代码可以验证:

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 fc = C()print c.aprint c.bprint c.class_fieldprint c.c

输出:

abclass fieldMethod C.__getattr__ has been called.c

PS: python里的attribute与property不同, 当使用了property里, property的解析优先级最高. 详见blog:从attribute到property.

补充知识:深入理解python对象及属性

类属性和实例属性

首先来看看类属性和类实例的属性在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 "<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__,实例不能作为弱引用的目标

属性的魔术方法

来看几个魔术方法

__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.xYou use getattr# 给对象的属性赋值时候,调用了__setattr__(self, name, value)方法,这个方法中有一句 self.__dict__[name] = value,通过这个语句,就将属性和数据保存到了对象的__dict__中>>> a.x = 7You 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.yyou are useing getattributeTraceback (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可以作为装饰器使用把方法标记为特性

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.lengthif __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,这在前面已经看到了。

以上这篇Python对象的属性访问过程详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。

声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

相关文章