《流畅的Python》 读书笔记 第一章数据模型(2) 230926
1|01.2 如何使用特殊方法
特殊方法的存在是为了被 Python 解释器调用的,你自己并不需要调用它们
就是说通常你都应该用len(obj)
而不是obj.__len()__
,无论是系统预置的,还是你自己定义的类,交给Python,解释器会去调用你实现的__len()__
然而如果是 Python 内置的类型,比如列表(list)、字符串(str)、字节序列(bytearray)等,那么 CPython 会抄个近路,len 实际上会直接返回 PyVarObject 里的 ob_size 属性。
PyVarObject 是表示内存中长度可变的内置对象的 C 语言结构体。直接读取这个值比调用一个方法要快很多
找到Cpython源码Include\cpython\listobject.h
这句注释len(list) == ob_size
- ob_item ,指向动态数组的指针,动态数组保存元素对象的指针;
- allocated ,动态数组长度,即列表 容量 ;
- ob_size ,动态数组当前保存元素个数,即列表 长度
注: 下图引用了《Python 源码剖析》
特殊方法的调用是隐式的,比如 for i in x: 这个语句,背后其实用的是iter(x),而这个函数的背后则是
x.__iter__()
方法
在Python官方doc下对__iter__
的解释
iter
则是内置函数
for循环的本质其实是迭代器的循环
- 调用可迭代对象的
__iter__
方法,将goods转化为迭代器goods_iterator; - 调用迭代器goods_iterator的
__next__
方法,返回出goods的第一个元素; - 循环步骤2,直到迭代器内数据流全部输出,捕获异常()
通常你的代码无需直接使用特殊方法。除非有大量的元编程存在,直接调用特殊方法的频率应该远远低于你去实现它们的次数。唯一的例外可能是
__init__
方法,你的代码里可能经常会用到它,目的是在你自己的子类的__init__
方法中调用超类的构造器
通过内置的函数(例如 len、iter、str,等等)来使用特殊方法是最好的选择。这些内置函数不仅会调用特殊方法,通常还提供额外的好处,而且对于内置的类来说,它们的速度更快
除了快,还有啥好处?
不要自己想当然地随意添加特殊方法,比如
__foo__
之类的,因为虽然现在这个名字没有被 Python 内部使用,以后就不一定
1|11.2.1 模拟数值类型
现在来了个题,让你实现上图的类Vector,你会怎么做?
上图只演示了加法,实际这个类还可以实现乘法、求模(abs)
示例代码
__repr__
是字符串表示形式,也是repr
内置函数的背后,你print(v1*3)
得到Vector(9,12)
因此二来
__abs__
是abs(v1)背后调用的
__add__
是v1+v2背后调用的
__mul__
是v1*3背后调用的
1|21.2.2 字符串表示形式
作者顺着1.2.1继续展开讲每个魔术方法的一些细节
repr 就是通过 repr 这个特殊方法来得到一个对象的字符串表示形式的。如果没有实现 repr,当我们在控制台里打印一个向量的实例时,得到的字符串可能会是 <Vector object at 0x10e100070>
比如这样
你可以加个__repr__
细心的同学可能发现在Vector的案例中用到了%r
得到的将是
区别在于多了个引号
而关于%r,官方是这么解释的
更多的你可以参考https://docs.python.org/zh-cn/3.9/library/stdtypes.html#old-string-formatting
__repr__
和__str__
的区别在于,后者是在 str() 函数被使用,或是在用 print 函数打印一个对象的时候才被调用的,并且它返回的字符串对终端用户更友好。如果你只想实现这两个特殊方法中的一个,
__repr__
是更好的选择,因为如果一个对象没有__str__
函数,而 Python 又需要调用它的时候,解释器会用__repr__
作为替代
1|31.2.3 算术运算符
__add__
和 __mul__
分别对应+和*,是中缀运算符
注: 中缀运算符在操作数的中间
两个方法的返回值都是新创建的向量对象
中缀运算符的基本原则就是不改变操作对象,而是产出一个新的值
1|41.2.4 自定义的布尔值
学过if、while的都知道,他们后面的表达式不见得就是直观的True或者False,背后就是这个__bool__
为了判定一个值 x 为真还是为假,Python会调用 bool(x),这个函数只能返回 True 或者 False
bool(x) 的背后是调用
x.__bool__()
的结果
如果不存在
__bool__
方法,那么 bool(x) 会尝试调用 x.__len__()
。若返回 0,则 bool 会返回 False;否
则返回 True
示例代码
从上面的案例中可以看出来
Person
类__bool__
就是bool(p1)背后调用的
而在Human类中没有定义__bool__
,__len__
就发挥作用了
https://docs.python.org/zh-cn/3.9/library/stdtypes.html#truth-value-testing 逻辑值检测
一个对象在默认情况下均被视为真值,除非当该对象被调用时其所属类定义了 __bool__()
方法且返回 False
或是定义了 __len__()
方法且返回零
2|01.3 特殊方法一览
https://docs.python.org/zh-cn/3.9/reference/datamodel.html#special-method-names
再说一次: 我在知乎的另外一篇文章中也做了一些归类: https://zhuanlan.zhihu.com/p/656429071
3|01.4 为什么len不是普通方法
len 之所以不是一个普通方法,是为了让 Python 自带的数据结构可以走后门,abs 也是同理
如果 x 是一个内置类型的实例,那么 len(x) 的速度会非常快。背后的原因是 CPython 会直接从一个 C 结构体里读取对象的长度,完全不会调用任何方法
4|01.5 本章小结
通过实现特殊方法,自定义数据类型可以表现得跟内置类型一样,从而让我们写出更具表
达力的代码——或者说,更具 Python 风格的代码
Python 对象的一个基本要求就是它得有合理的字符串表示形式,我们可以通过
__repr
__ 和
__str__
来满足这个要求。前者方便我们调试和记录日志,后者则是给终端用户看的
5|01.6 延伸阅读
https://docs.python.org/zh-cn/3.9/reference/datamodel.html#
Python 语言参考手册里的“Data Model”一章(我提供的是中文)是最符合规范的知识来源
Alex Martelli 的《Python 技术手册(第 2 版)》对数据模型的讲解很精彩
David Beazley
《Python 参考手册(第 4 版)》
Python Cookbook(第 3版)中文版》
__EOF__

本文链接:https://www.cnblogs.com/wuxianfeng023/p/17730411.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了