通过与 Jira 对比,让您更全面了解 PingCode

  • 首页
  • 需求与产品管理
  • 项目管理
  • 测试与缺陷管理
  • 知识管理
  • 效能度量
        • 更多产品

          客户为中心的产品管理工具

          专业的软件研发项目管理工具

          简单易用的团队知识库管理

          可量化的研发效能度量工具

          测试用例维护与计划执行

          以团队为中心的协作沟通

          研发工作流自动化工具

          账号认证与安全管理工具

          Why PingCode
          为什么选择 PingCode ?

          6000+企业信赖之选,为研发团队降本增效

        • 行业解决方案
          先进制造(即将上线)
        • 解决方案1
        • 解决方案2
  • Jira替代方案

25人以下免费

目录

Python对象的方法调用时发生了什么

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>

相关文章