栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

玩转Python|类与方法的隐藏秘密(3)

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

玩转Python|类与方法的隐藏秘密(3)

上期为大家准备的Python攻略,从原理上讲了Python类的本质,接下来的两篇Python专栏里,继续来讲讲关于类及其方法的一些冷知识和烫知识。

我们也会和前面两篇专栏一样,用各种神奇的例子,从原理和机制的角度为你还原一个不一样的Python。

没有读过前文的朋友,看下面链接:

玩转Python|类与方法的隐藏秘密(1)

玩转Python|类与方法的隐藏秘密(2)

下面是本篇正文



 

对象方法的本质

说到面向对象编程,大家应该对方法这一概念并不陌生。其实在上篇中已经提到,在Python中方法的本质就是一个字段,将一个可执行的对象赋值给当前对象,就可以形成一个方法,并且也尝试了手动制造一个对象。

但是,如果你对Python有更进一步的了解,或者有更加仔细的观察的话,会发现实际上方法还可以被如下的方式调用起来:

class T:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def plus(self, z):
        return self.x + self.y + z


t = T(2, 5)
t.plus(10)  # 17
T.plus(t, 10)  # 17, the same as t.plus(10)

没错,就是 T.plus(t, 10) 这样的用法,这在其他一些面向对象语言中似乎并没见到过,看起来有些费解。先别急,咱们再来做另外一个实验。

def plus(self, z):
    return self.x + self.y + z


class T:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    plus = plus


t = T(2, 5)
print(t)
print(plus)
print(T.plus)
print(t.plus)

# <__main__.T object at 0x7fa58afa7630>
# 
# 
# >

在这个程序中, plus 函数被单独定义,且在类 T 中被引入为字段。而观察一下上面的输出,会发现一个事实—— plus和T.plus完全就是同一个对象,但t.plus就并不是同一个。根据以往我们的分析,前者是显而易见的,但是 t.plus 却成了一个叫做 method 的东西,这又是怎么回事呢?我们继续来实验,接着上一个程序。

from types import MethodType

print(type(t.plus), MethodType)  #  
assert isinstance(t.plus, MethodType)

会发现传说中的method 原来是 types.MethodType 这个对象。

既然已经有了这个线索,那么我们继续翻阅一下这个 types.MethodType 的源代码,源代码有部分内容不可见,只找到了这些(此处Python版本为 3.9.6 )

class MethodType:
    __func__: _StaticFunctionType
    __self__: object
    __name__: str
    __qualname__: str
    def __init__(self, func: Callable[..., Any], obj: object) -> None: ...
    def __call__(self, *args: Any, **kwargs: Any) -> Any: ...

此处很抱歉没有找到官方文档,types库的文档在MethodType 的部分只有一行概述性文本而没有实质性内容,所以只好去翻源代码了。

types库文档链接:https://docs.python.org/3/library/types.html#types.MethodType

如果有有读者找到的正经的文档或说明欢迎后台同步我们,我们第一时间分享给大家。

不过这么一看,依然有很关键的发现——这个__init__方法有点东西,从名字和类型来看,func应该是一个函数,obj应该是一个任意对象。咱们再来想想,从逻辑要素的角度想想, t.plus 这个东西要想能运行起来,必要因素有那些,答案显而易见:

  • 运行逻辑,通俗来说就是实际运行的函数plus
     

  • 运行主体,通俗来说在方法前被用点隔开的那个对象t

到这一步为止答案已经呼之欲出了,不过本着严谨的科学精神接下来还是需要进行更进一步的验证,我们需要尝试拆解这个 t.plus ,看看里面到底都有些什么东西(接上面的程序)。

print(set(dir(t.plus)) - set(dir(plus)))  # {'__self__', '__func__'}
print(t.plus.__func__)  # 
print(t.plus.__self__)  # <__main__.T object at 0x7fa58afa7630>

首先第一行,将 dir 结果转为集合,看看那些字段是t.plus拥有而T.plus没有的。果不其然,刚好就俩字段: __self__ 和 __func__ 。


然后分别将这两个字段的值进行输出,发现—— t.plus.__func__就是之前定义的那个plus,而t.plus.__self__就是实例化出来的t。

到这一步,与我们的猜想基本吻合,只差一个终极验证。还记得上篇中那个手动制造出来的对象不,没错,让我们来用MethodType来更加科学也更加符合实际代码行为地再次搭建一回,程序如下:

from types import MethodType


class MyObject(object):
    pass


if __name__ == '__main__':
    t = MyObject()  # the same as __new__
    t.x = 2  # the same as __init__
    t.y = 5


    def plus(self, z):
        return self.x + self.y + z


    t.plus = MethodType(plus, t)  # a better implement

    print(t.x, t.y)  # 2 5
    print(t.plus(233))  # 240
    print(t.plus)
    # >

运行结果和之前一致,也和常规方式实现的对象完全一致,并且这个 t.plus 也正是之前实验中所看到的那种 method 。至此,Python中对象方法的本质已经十分清楚——对象方法一个基于原有函数,和当前对象,通过types.MethodType类进行组合后实现的可执行对象。

延伸思考1:基于上述的分析,为什么 T.plus(t, 10) 会有和 t.plus(10) 等价的运行效果?

延伸思考2:为什么对象方法开头第一个参数是 self ,而从第二个参数开始才是实际传入的?MethodType 对象在被执行的时候,其内部原理可能是什么样的?

类方法与静态方法

说完了对象方法,咱们再来看看另外两种常见方法——类方法和静态方法。首先是一个最简单的例子:

class T:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def plus(self, z):
        return self.x + self.y + z

    @classmethod
    def method_cls(cls, suffix):
        return str(cls.__name__) + suffix

    @staticmethod
    def method_stt(content):
        return ''.join(content[::-1])

其中 method_cls 是一个类方法, method_stt 是一个静态方法,这一点大家应该并不陌生。那废话不多说,先看看这个 method_cls 到底是什么(程序接上文)

print(T.method_cls)  # >

t = T(2, 3)
print(t.method_cls)  # >

很眼熟对吧,没错——无论是位于类T上的T.method_cls,还是位于对象t上的t.method_cls,都是在上一章节中所探讨过的types.MethodType类型对象,而且还是同一个对象。接下来再看看其内部的结构(程序接上文)

print(T.method_cls.__func__)  # 
print(T.method_cls.__self__)  # 
print(T)  # 
assert T.method_cls.__self__ is T

其中 __func__ 就是这个原版的 method_cls 函数,而 __self__ 则是类对象 T 。由此不难发现一个事实——类方法的本质是一个将当前类对象作为主体对象的方法对象。换言之,类方法在本质上和对象方法是同源的,唯一的区别在于这个 self 改叫了 cls ,并且其值换成了当前的类对象。看完了类方法,接下来是静态方法。首先和之前一样,看下 method_stt 的实际内容:

print(T.method_stt)  # 

t = T(2, 3)
print(t.method_stt)  # 

这个结果很出乎意料,但仔细想想也完全合乎逻辑——静态方法的本质就是一个附着在类和对象上的原生函数。换言之,无论是 T.method_stt 还是 t.method_stt ,实际获取到的都是原本的那个 method_stt 函数。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/958617.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号