国际惯例,来看一段入门级代码。
class Student(object):
address = '北京'
def __init__(self):
self.age = 18
stu = Student()
print(stu.age)
print(stu.address)
相信输出大家都猜到了,结果为:18 北京
那么,有没有想过,是怎么查找到对应的属性的,为什么执行一个不存在的属性程序就会挂掉呢?下面,简单来一步步分析下。
dir查看对象和类信息
通过dir来看一下对象和类都包含了哪些信息。
print(dir(stu))
print(dir(Student))
输出结果为:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'address', 'age']
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'address']
可以发现,两者都包括一个dict的东东,并且对象的实例变量和类变量都会存在于dir()方法中。下面看一下两者的dict又包含了什么东东呢?
dict查看键值对信息
代码如下:
print(stu.__dict__)
print(Student.__dict__)
输出结果为:
{'age': 18}
{'__module__': '__main__', 'address': '北京', '__init__': <function Student.__init__ at 0x102261620>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
有木有发现,不管是定义的类变量也好,实例变量也好,都在dict这个魔术方法中以key:value的形式进行保存。
分布结论
通过上面两步呢,可以得到一个结论,在调用对象的变量时,先在对象的dict这个魔术方法进行查找,查找到返回,如果找不到继续在类的dict中进行查找。
进一步分析
在如上程序中,如果我们执行stu.name
,那么程序就挂掉了,因为没在两个dict中查到对应的信息,下面,通过复写一个方法,就可以屏蔽掉报错信息了。
def __getattr__(self, item):
return 'not found'
getattr这个方法,就是在我们从两个dict查找失败时,会进行调用的方法,加了上述代码后,在执行stu.name
就会输出not found
信息了。
getattr和getattribute区别
不知道聪明的你有木有发现,除了上面说的getattr方法外,还有一个getattribute方法,那么两者有什么区别么,废话不说,直接上代码。
def __getattr__(self, item):
return 'not found'
def __getattribute__(self, item):
return 'getattribute called'
下面,再运行我们的程序,输出结果为:
getattribute called
getattribute called
这是为什么呢?
不难发现,复写这个方法后,都没从dict中进行数据查找,而是直接打印这个方法信息了。
大结论
在调用对象方法时,先调用getattribute方法,从这个方法中进行数据查找分发,先到对象的dict,如果查找失败,接着到类的dict,如果还失败,就会到getattr方法中,一般而言,可以在getattr方法中进行自己的一些操作,而很少去改动getattribute方法。