当前位置:首页 > 科技  > 软件

再探泛型 API,感受 Python 对象的设计哲学

来源: 责编: 时间:2024-05-27 08:53:35 268观看
导读之前我们提到了泛型 API,这类 API 的特点是可以处理任意类型的对象,举个例子。// 返回对象的长度PyObject_Size// 返回对象的某个属性PyObject_GetAttr// 返回对象的哈希值PyObject_Hash// 将对象转成字符串返回PyObjec

之前我们提到了泛型 API,这类 API 的特点是可以处理任意类型的对象,举个例子。9Ru28资讯网——每日最新资讯28at.com

// 返回对象的长度PyObject_Size// 返回对象的某个属性PyObject_GetAttr// 返回对象的哈希值PyObject_Hash// 将对象转成字符串返回PyObject_Str

对应到 Python 代码中,就是下面这个样子。9Ru28资讯网——每日最新资讯28at.com

# PyObject_Sizeprint(len("古明地觉"))print(len([1, 2, 3]))"""43"""# PyObject_GetAttrprint(getattr("古明地觉", "lower"))print(getattr([1, 2, 3], "append"))print(getattr({}, "update"))"""<built-in method lower of str object at 0x7f081aa7e920><built-in method append of list object at 0x7f081adc1100><built-in method update of dict object at 0x7f081aa8fd80>"""# PyObject_Hashprint(hash("古明地觉"))print(hash(2.71))print(hash(123))"""81525063933782332031637148536541722626123"""# PyObject_Strprint(str("古明地觉"))print(str(object()))"""古明地觉<object object at 0x7fdfa0209d10>"""

这些 API 能处理任意类型的对象,这究竟是怎么办到的?要想搞清楚这一点,还是要从 PyObject 入手。9Ru28资讯网——每日最新资讯28at.com

我们知道对象在 C 看来就是一个结构体实例,并且结构体嵌套了 PyObject。9Ru28资讯网——每日最新资讯28at.com

# 创建一个列表,让变量 var 指向它var = [1, 2, 3]# 创建一个浮点数,让变量 var 指向它var = 2.71

列表对应的结构体是 PyListObject,浮点数对应的结构体是 PyFloatObject,变量 var 是指向对象的指针。那么问题来了,凭啥一个变量可以指向不同类型的对象呢?或者说变量和容器里面为什么可以保存不同对象的指针呢?9Ru28资讯网——每日最新资讯28at.com

原因在前面的文章中解释的很详细了,因为对象的指针会统一转成 PyObject * 之后再交给变量保存,以创建列表为例。9Ru28资讯网——每日最新资讯28at.com

图片图片9Ru28资讯网——每日最新资讯28at.com

当然创建浮点数也是同理,因此变量和容器里的元素本质上就是一个泛型指针 PyObject *。而对象的指针在交给变量保存的时候,也都会先转成 PyObject *,因为不管什么对象,它底层的结构体都嵌套了 PyObject。正是因为这个设计,变量才能指向任意的对象。9Ru28资讯网——每日最新资讯28at.com

图片图片9Ru28资讯网——每日最新资讯28at.com

所以 Python 变量相当于一个便利贴,可以贴在任意对象上。9Ru28资讯网——每日最新资讯28at.com

不过问题来了,由于对象的指针会统一转成 PyObject * 之后再交给变量保存,那么变量怎么知道自己指向的是哪种类型的对象呢?相信你肯定知道答案:通过 ob_type 字段。9Ru28资讯网——每日最新资讯28at.com

图片图片9Ru28资讯网——每日最新资讯28at.com

对象对应的结构体可以有很多个字段,比如 PyListObject,但变量能看到的只有前两个字段。至于之后的字段是什么,则取决于对象的类型,总之对变量来说是不可见的,因为它是 PyObject *。9Ru28资讯网——每日最新资讯28at.com

所以变量会先通过 ob_type 字段获取对象的类型,如果 ob_type 字段的值为 &PyList_Type,那么变量指向的就是 PyListObject。如果 ob_type 字段的值为 &PyFloat_Type,那么变量指向的就是 PyFloatObject,其它类型同理。9Ru28资讯网——每日最新资讯28at.com

当得到了对象的类型,那么再转成相应的指针即可,假设 ob_type 是 &PyList_Type,那么变量会再转成 PyListObject *,这样就可以操作列表的其它字段了。9Ru28资讯网——每日最新资讯28at.com

所以我们再总结一下:9Ru28资讯网——每日最新资讯28at.com

图片图片9Ru28资讯网——每日最新资讯28at.com

变量和容器里的元素只能保存相同的指针类型,而不同类型的对象,其底层的结构体是不同的。但这些结构体无一例外都嵌套了 PyObject,因此它们的指针会统一转成 PyObject * 之后再交给变量保存。9Ru28资讯网——每日最新资讯28at.com

然后变量在操作对象时,会先通过 ob_type 判断对象的类型,假如是 &PyList_Type,那么会再转成 PyListObject *,其它类型同理。9Ru28资讯网——每日最新资讯28at.com

图片图片9Ru28资讯网——每日最新资讯28at.com

相信你已经知道为什么泛型 API 可以处理任意类型的对象了,以 PyObject_GetAttr 为例,它内部会调用类型对象的 tp_getattro。9Ru28资讯网——每日最新资讯28at.com

// 等价于 getattr(v, name)PyObject *PyObject_GetAttr(PyObject *v, PyObject *name){       // 获取对象 v 的类型对象    PyTypeObject *tp = Py_TYPE(v);    PyObject* result = NULL;    // 如果类型对象实现了 tp_getattro,那么进行调用    // 等价于 Python 中的 type(v).__getattr__(v, name)    if (tp->tp_getattro != NULL) {        result = (*tp->tp_getattro)(v, name);    }    // 否则会退化为 tp_getattr,它要求属性名称必须是 C 字符串    // 不过 tp_getattr 已经废弃,应该使用 tp_getattro    else if (tp->tp_getattr != NULL) {        const char *name_str = PyUnicode_AsUTF8(name);        if (name_str == NULL) {            return NULL;        }        result = (*tp->tp_getattr)(v, (char *)name_str);    }    // 否则说明对象 v 没有指定属性    else {        PyErr_Format(PyExc_AttributeError,                    "'%.100s' object has no attribute '%U'",                    tp->tp_name, name);    }    return result;}

函数先通过 ob_type 找到对象的类型,然后通过类型对象的 tp_getattro 调用对应的属性查找函数。所以 PyObject_GetAttr 会根据对象的类型,调用不同的属性查找函数,因此这就是泛型 API 能处理任意对象的秘密。9Ru28资讯网——每日最新资讯28at.com

我们再以 Python 为例:9Ru28资讯网——每日最新资讯28at.com

class A:    def __getattr__(self, item):        return f"class:A,item:{item}"class B:    def __getattr__(self, item):        return f"class:B,item:{item}"a = A()b = B()print(getattr(a, "some_attr"))print(getattr(b, "some_attr"))"""class:A,item:some_attrclass:B,item:some_attr"""# 以上等价于print(type(a).__getattr__(a, "some_attr"))print(type(b).__getattr__(b, "some_attr"))"""class:A,item:some_attrclass:B,item:some_attr"""

在 Python 里的表现和源码是一致的,我们再举个 iter 的例子:9Ru28资讯网——每日最新资讯28at.com

data = [1, 2, 3]print(iter(data))print(type(data).__iter__(data))"""<list_iterator object at 0x7fb8200f29a0><list_iterator object at 0x7fb8200f29a0>"""

如果一个对象支持迭代器操作,那么它的类型对象一定实现了 __iter__,通过 type(data) 可以获取到类型对象,然后将 data 作为参数调用 __iter__ 即可。9Ru28资讯网——每日最新资讯28at.com

所以通过 ob_type 字段,这些泛型 API 实现了类似多态的效果,一个函数,多种实现。9Ru28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-90852-0.html再探泛型 API,感受 Python 对象的设计哲学

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: Vue3 新玩法!我能操控计算属性 Computed!

下一篇: Python 类型注解与检查:让代码“开口说话”的八个妙招

标签:
  • 热门焦点
  • 掘力计划第 20 期:Flutter 混合开发的混乱之治

    在掘力计划系列活动第20场,《Flutter 开发实战详解》作者,掘金优秀作者,Github GSY 系列目负责人恋猫的小郭分享了Flutter 混合开发的混乱之治。Flutter 基于自研的 Skia 引擎
  • 让我们一起聊聊文件的操作

    文件【1】文件是什么?文件是保存数据的地方,是数据源的一种,比如大家经常使用的word文档、txt文件、excel文件、jpg文件...都是文件。文件最主要的作用就是保存数据,它既可以保
  • 如何通过Python线程池实现异步编程?

    线程池的概念和基本原理线程池是一种并发处理机制,它可以在程序启动时创建一组线程,并将它们置于等待任务的状态。当任务到达时,线程池中的某个线程会被唤醒并执行任务,执行完任
  • 微软邀请 Microsoft 365 商业用户,测试视频编辑器 Clipchamp

    8 月 1 日消息,微软近日宣布即将面向 Microsoft 365 商业用户,开放 Clipchamp 应用,邀请用户通过该应用来编辑视频。微软于 2021 年收购 Clipchamp,随后开始逐步整合到 Microsof
  • 一文搞定Java NIO,以及各种奇葩流

    大家好,我是哪吒。很多朋友问我,如何才能学好IO流,对各种流的概念,云里雾里的,不求甚解。用到的时候,现百度,功能虽然实现了,但是为什么用这个?不知道。更别说效率问题了~下次再遇到,
  • 2天涨粉255万,又一赛道在抖音爆火

    来源:运营研究社作者 | 张知白编辑 | 杨佩汶设计 | 晏谈梦洁这个暑期,旅游赛道彻底火了:有的「地方」火了&mdash;&mdash;贵州村超旅游收入 1 个月超过 12 亿;有的「博主」火了&m
  • 年轻人的“职场羞耻感”,无处不在

    作者:冯晓亭 陶 淘 李 欣 张 琳 马舒叶来源:燃次元&ldquo;人在职场,应该选择什么样的着装?&rdquo;近日,在网络上,一个与着装相关的帖子引发关注,在该帖子里,一位在高级写字楼亚洲金
  • 网红炒股不为了赚钱,那就是耍流氓!

    来源:首席商业评论6月26日高调宣布入市,网络名嘴大v胡锡进居然进军了股市。在一次财经媒体峰会上,几个财经圈媒体大佬就&ldquo;胡锡进炒股是否知道认真报道&rdquo;展开讨论。有
  • 引领旗舰级影像能力向中端机普及 OPPO K11 系列发布 1799 元起

    7月25日,OPPO正式发布K系列新品—— OPPO K11 。此次 K11 在中端手机市场长期被忽视的影像板块发力,突破性地搭载索尼 IMX890 旗舰大底主摄,支持 OIS
Top