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

只想回答一个问题: 当编译器要读取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 f
c = C()
print c.a
print c.b
print c.class_field
print c.c

输出:

a
b
class field
Method 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.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可以作为装饰器使用把方法标记为特性

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

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

(0)

相关推荐

  • Python中私有属性的定义方式

    Python没有类似于Java的private关键字, 但也可以为类定义私有属性. 只需将属性命名变为以__开头, 例如 __field. 示例代码: class Vector2D(object): def __init__(self, x, y): self.__x = float(x) self.__y = float(y) self.xx = x self.yy = y def get_x(self): return self.__x # 内部访问 if __name__ == "__ma

  • pycharm实现在子类中添加一个父类没有的属性

    我就废话不多说了,还是直接看代码吧! class Car(): """一次模拟汽车的简单尝试""" def __init__(self, make, model, year): """初始化描述汽车的属性""" self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_de

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

    只想回答一个问题: 当编译器要读取obj.field时, 发生了什么? 看似简单的属性访问, 其过程还蛮曲折的. 总共有以下几个step: 1. 如果obj 本身(一个instance )有这个属性, 返回. 如果没有, 执行 step 2 2. 如果obj 的class 有这个属性, 返回. 如果没有, 执行step 3. 3. 如果在obj class 的父类有这个属性, 返回. 如果没有, 继续执行3, 直到访问完所有的父类. 如果还是没有, 执行step 4. 4. 执行obj.__ge

  • Python实现自动化邮件发送过程详解

    使用Python实现自动化邮件发送,可以让你摆脱繁琐的重复性业务,可以节省非常多的时间. 操作前配置(以较为复杂的QQ邮箱举例,其他邮箱操作类似) 单击设置-账号,滑倒下方协议处,开启IMAP/SMTP协议(IMAP,即Internet Message Access Protocol(互联网邮件访问协议),可以通过这种协议从邮件服务器上获取邮件的信息.下载邮件等.IMAP与POP类似,都是一种邮件获取协议.) (ps.开启需要验证) 记住端口号,后续写代码发送邮件时候需要 生成授权码,前期配置完

  • 基于js对象,操作属性、方法详解

    一,概述 在Java语言中,我们可以定义自己的类,并根据这些类创建对象来使用,在Javascript中,我们也可以定义自己的类,例如定义User类.Hashtable类等等. 目前在Javascript中,已经存在一些标准的类,例如Date.Array.RegExp.String.Math.Number等等,这为我们编程提供了许多方便.但对于复杂的客户端程序而言,这些还远远不够. 与Java不同,Java2提供给我们的标准类很多,基本上满足了我们的编程需求,但是Javascript提供的标准类很

  • Python对象类型及其运算方法(详解)

    基本要点: 程序中储存的所有数据都是对象(可变对象:值可以修改 不可变对象:值不可修改) 每个对象都有一个身份.一个类型.一个值 例: >>> a1 = 'abc' >>> type(a1) str 创建一个字符串对象,其身份是指向它在内存中所处的指针(在内存中的位置) a1就是引用这个具体位置的名称 使用type()函数查看其类型 其值就是'abc' 自定义类型使用class 对象的类型用于描述对象的内部表示及其支持的方法和操作 创建特定类型的对象,也将该对象称为该类

  • python函数定义和调用过程详解

    这篇文章主要介绍了python函数定义和调用过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 我们可以创建一个函数来列出费氏数列 >>> def fib(n): # write Fibonacci series up to n ... """Print a Fibonacci series up to n.""" ... a, b = 0, 1 ... while a &

  • python字典的元素访问实例详解

    说明 1.字典中没有下标的概念,使用key值访问字典中对应的value值.当访问的key值不存在时,代码会报错. 2.get('key'):直接将key值传入函数,当查询到相应的value值时,返回相应的值,当key值不存在时,返回None,代码不会出错. 3.get(key,数据):当查询相应的value值时,返回相应的值,当没有key值时,返回自定义的数据值. 实例 # 定义一个字典 dic = {'Name': '张三', 'Age': 20} # 使用 key 值访问元素 print(d

  • Java 用反射设置对象的属性值实例详解

    Java 用反射设置对象的属性值实例详解 /** * 用反射设置对象的属性值 * @param obj 需要設置值的對象 * @param fieldName 需要設置值的屬性 * @param value 需要设置的值 * @return 设置值后的对象 */ private Object invoke(Object obj, String fieldName, Object value) { String firstWord = fieldName.substring(0, 1).toUpp

  • python DataFrame转dict字典过程详解

    这篇文章主要介绍了python DataFrame转dict字典过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 背景:将商品id以及商品类别作为字典的键值映射,生成字典,原为DataFrame # 创建一个DataFrame # 列值类型均为int型 import pandas as pd item = pd.DataFrame({'item_id': [100120, 10024504, 1055460], 'item_categor

  • Python画笔的属性及用法详解

    画笔有颜色.画线的宽度等属性. 1.turtle.pensize() :设置画笔的宽度: 2.turtle.pencolor():没有参数传入返回当前画笔颜色:传入参数设置画笔颜色,可以是字符串如"green", "red",也可以是RGB 3元组 >>> pencolor('brown') >>> tup = (0.2, 0.8, 0.55) >>> pencolor(tup) >>> pen

  • python元组打包和解包过程详解

    1.在将多个以逗号分隔的值赋给一个变量时,多个值被打包成一个元组类型.当我们将一个元组赋给多个变量时,它将解包成多个值,然后分别将其赋给相应的变量. # 打包 a = 1, 10, 100 print(type(a), a) # <class 'tuple'> (1, 10, 100) # 解包 i, j, k = a print(i, j, k) # 1 10 100 2.解包时,如果解包出来的元素数目与变量数目不匹配,就会引发ValueError异常.错误信息为:too many valu

随机推荐