catObj寻找一个叫Eat的东西,这东西可能是任意对象,名字就叫Eat(暂且先忘了Cat类里定义了什么),这其实就涉及到Python属性访问顺序了:__getattribute__;数据描述符;对象属性;类属性等。
一、Python对象的方法调用时发生了什么
1.寻找Eat
catObj寻找一个叫Eat的东西,这东西可能是任意对象,名字就叫Eat(暂且先忘了Cat类里定义了什么),这其实就涉及到Python属性访问顺序了:
- __getattribute__
- 数据描述符
- 对象属性
- 类属性
- 非数据描述符
- 父类属性
- __getattr__()
为了更简单得去理解,本文只关注对象属性,类属性,父类属性这三点。
首先从对象属性去找:
catObj对象有没有名为Eat这个属性?从上面定义的Cat类看显然是没有,这个对象只有一个属性name,可打印__dict__查看对象的属性。
print(catObj.__dict__)
# 打印结果:
# {‘name’: ‘Godeng’}
catObj.food = “Fish”
# 打印结果:
# {‘name’: ‘Godeng’, ‘food’: ‘Fish’}
对象添加一个属性,它就会进对象的__dict__里,归根结底都是字典访问。
对象属性找不到,去类属性找,从catObj对象的__class__属性即可索引到类:
Cat类有没有名为Eat这个属性?从上面定义的Cat类看,显然是有的,且类也是对象,也可通过打印__dict__观察它的属性。
print(Cat.__dict__)
# 打印结果:
# {…’Eat’: <function Cat.Eat at 0x0000019EF98E0F28>,
# ‘__init__’: <function Cat.__init__ at 0x0000019EF98E0B70>…}
Eat和__init__赫然在列,而且Eat和__init__是一个质朴得不能再质朴的函数,函数,函数。
如果在类属性找不到的话,就会尝试去父类属性寻找,相似的过程。
那么到此,寻找到了一个名为Eat的函数。
2.包装成Eat方法&调用
def Eat(self):
print(“Cat is eating”)
从catObj对象一路寻找到的Eat函数是长这样的,但它并非最终的返回的方法。将函数与对象进行包装后的产物,才是方法。
class Method(object):
def __init__(self, obj, func):
self.__self__ = obj
self.__func__ = func
def __call__(self, *args, **kwargs):
self.__func__(self.__self__, *args, **kwargs)
catObj = Cat(“Godeng”)
eatMethod = Method(catObj, Cat.Eat)
eatMethod()
上面是模拟方法的结构和大致产生调用过程,方法对象自身会引用Cat类函数Eat,和eatObj对象,并方法对象被调用时(__call__调用时),自动把eatObj对象作为了该函数的self参数。
并非方法本身蕴含了什么不可告人的魔力,catObj.eat获取的其实就是类似上例的Method对象,从使用形式来看它提供的一个显著特点是,他就像一个不需要传self参数的函数。
方法&类函数引用上的区别:
类函数Cat.Eat是一个普通函数对象,它被Cat类引用。
catObj.Eat是一个方法对象,它引用了类函数Cat.Eat和catObj对象,它并不是像类函数那样有被Cat类或catObj对象引用的一个恒久的对象,每次catObj.Eat的调用都会走一遍上述流程,产生一个新的方法对象返回。
# 生成了一个新的方法对象,这句执行完毕后方法对象会被销毁
catObj.Eat()
# 生成了一个新的方法对象, 并由eatMethod引用,方法对象不会被销毁
eatMethod = catObj.Eat
# 删除eatMethod的引用,方法对象随之被销毁
eatMethod = None
延伸阅读:
二、如何从父类们找类函数
Python是门多继承语言,在上面catObj寻找Eat到它的类的时候,如果Cat类没有定义Eat,会尝试向父类寻找Eat。
class Monster(object):
def Eat(self):
print(“Monster Eat”)
class Pet(object):
def Eat(self):
print(“Pet Eat”)
class Cat(Pet, Monster):
def __init__(self, name):
self.name = name
假如Cat没定义Eat,它的两个父类Pet和Monster的都定义了Eat,那就是先在哪个父类找到Eat,那么就返回哪个父类的Eat,也即是取决于先从Pet类找,还是先从Monster类找。
这就涉及到Python一个老生常谈的概念,方法解析顺序(mro),它确定了这个顺序,它的具体规则会随着Python版本迭代而迭代,如Py2是深度优先和Py3是广度优先,具体在此就不展开了。
可通过打印类的mro来观察类的执行顺序。
print(Cat.mro())
print(Cat.Eat)
# 打印结果:
# [<class ‘__main__.Cat’>, <class ‘__main__.Pet’>, <class ‘__main__.Monster’>, <class ‘object’>]
# <function Pet.Eat at 0x000001C053510F28>