《流畅的Python》 读书笔记 230926(第一章后半部分)
1.2 如何使用特殊方法
特殊方法的存在是为了被 Python 解释器调用的,你自己并不需要调用它们
就是说通常你都应该用len(obj)而不是obj.__len()__,无论是系统预置的,还是你自己定义的类,交给Python,解释器会去调用你实现的__len()__
然而如果是 Python 内置的类型,比如列表(list)、字符串(str)、字节序列(bytearray)等,那么 CPython 会抄个近路,len 实际上会直接返回 PyVarObject 里的 ob_size 属性。
PyVarObject 是表示内存中长度可变的内置对象的 C 语言结构体。直接读取这个值比调用一个方法要快很多
找到Cpython源码Include\cpython\listobject.h
typedef struct {
PyObject_VAR_HEAD
/* Vector of pointers to list elements. list[0] is ob_item[0], etc. */
PyObject **ob_item;
/* ob_item contains space for 'allocated' elements. The number
* currently in use is ob_size.
* Invariants:
* 0 <= ob_size <= allocated
* len(list) == ob_size
* ob_item == NULL implies ob_size == allocated == 0
* list.sort() temporarily sets allocated to -1 to detect mutations.
*
* Items must normally not be NULL, except during construction when
* the list is not yet visible outside the function that builds it.
*/
Py_ssize_t allocated;
} PyListObject;
这句注释len(list) == ob_size
- ob_item ,指向动态数组的指针,动态数组保存元素对象的指针;
- allocated ,动态数组长度,即列表 容量 ;
- ob_size ,动态数组当前保存元素个数,即列表 长度
注: 下图引用了《Python 源码剖析》

特殊方法的调用是隐式的,比如 for i in x: 这个语句,背后其实用的是iter(x),而这个函数的背后则是
x.__iter__()方法
在Python官方doc下对__iter__的解释
object.__iter__(self)
此方法在需要为容器创建迭代器时被调用。此方法应该返回一个新的迭代器对象,它能够逐个迭代容器中的所有对象。对于映射,它应该逐个迭代容器中的键。
迭代器对象也需要实现此方法;它们需要返回对象自身
iter则是内置函数
for循环的本质其实是迭代器的循环
- 调用可迭代对象的
__iter__方法,将goods转化为迭代器goods_iterator; - 调用迭代器goods_iterator的
__next__方法,返回出goods的第一个元素; - 循环步骤2,直到迭代器内数据流全部输出,捕获异常()
# while 和 iterator
users = 'wuxianfeng','qianyuli'
users_iterator = iter(users) # 等同于 users.__iter__()
while True:
try:
print(next(users_iterator)) # 等同于users.__next__()
except StopIteration: # 捕捉异常终止循环
break
# for循环 则是这么写的,你会选择哪个方式,不言而喻
for user in users:
print(user)
通常你的代码无需直接使用特殊方法。除非有大量的元编程存在,直接调用特殊方法的频率应该远远低于你去实现它们的次数。唯一的例外可能是
__init__方法,你的代码里可能经常会用到它,目的是在你自己的子类的__init__方法中调用超类的构造器
通过内置的函数(例如 len、iter、str,等等)来使用特殊方法是最好的选择。这些内置函数不仅会调用特殊方法,通常还提供额外的好处,而且对于内置的类来说,它们的速度更快
除了快,还有啥好处?
不要自己想当然地随意添加特殊方法,比如
__foo__之类的,因为虽然现在这个名字没有被 Python 内部使用,以后就不一定
1.2.1 模拟数值类型

现在来了个题,让你实现上图的类Vector,你会怎么做?
上图只演示了加法,实际这个类还可以实现乘法、求模(abs)
v1 = Vector(3,4)
abs(v1) ==>5
v1*3 ==>Vector(9,12)
示例代码
from math import hypot
class Vector:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __repr__(self):
return 'Vector(%r, %r)' % (self.x, self.y)
def __abs__(self):
return hypot(self.x, self.y)
# def __bool__(self): # 官方写了,但后面没用到
# return bool(abs(self))
def __add__(self, other):
x = self.x + other.x
y = self.y + other.y
return Vector(x, y)
def __mul__(self, scalar):
return Vector(self.x * scalar, self.y * scalar)
if __name__ == '__main__':
v1 = Vector(3,4)
print(abs(v1)) # 5.0
print(v1*3) #Vector(9,12)
v2 = Vector(6,8)
print(v1+v2) #Vector(9,12)
__repr__是字符串表示形式,也是repr内置函数的背后,你print(v1*3)得到Vector(9,12)因此二来
__abs__是abs(v1)背后调用的
__add__是v1+v2背后调用的
__mul__是v1*3背后调用的
1.2.2 字符串表示形式
作者顺着1.2.1继续展开讲每个魔术方法的一些细节
repr 就是通过 repr 这个特殊方法来得到一个对象的字符串表示形式的。如果没有实现 repr,当我们在控制台里打印一个向量的实例时,得到的字符串可能会是 <Vector object at 0x10e100070>
比如这样
class Person:
def __init__(self,name):
self.name = name
p1 = Person('wuxianfeng')
print(p1) # <__main__.Person object at 0x0000020A9895F280>
你可以加个__repr__
class Person:
def __init__(self,name):
self.name = name
def __repr__(self):
return 'Person: %s' %self.name
p1 = Person('wuxianfeng')
print(p1) # Person: wuxianfeng
细心的同学可能发现在Vector的案例中用到了%r
def __repr__(self):
return 'Person: %r' %self.name
得到的将是
p1 = Person('wuxianfeng')
print(p1) #Person: 'wuxianfeng'
区别在于多了个引号
而关于%r,官方是这么解释的

更多的你可以参考https://docs.python.org/zh-cn/3.9/library/stdtypes.html#old-string-formatting
__repr__和__str__的区别在于,后者是在 str() 函数被使用,或是在用 print 函数打印一个对象的时候才被调用的,并且它返回的字符串对终端用户更友好。如果你只想实现这两个特殊方法中的一个,
__repr__是更好的选择,因为如果一个对象没有__str__函数,而 Python 又需要调用它的时候,解释器会用__repr__作为替代
class Person:
def __init__(self,name):
self.name = name
def __repr__(self):
return 'Person: %r' %self.name
def __str__(self):
return '人类: %s' %self.name
p1 = Person('wuxianfeng')
print(p1) # 调用 __str__
print(repr(p1)) # 自然是调用 __repr__
print(str(p1)) # 自然是调用 __str__
1.2.3 算术运算符
__add__ 和 __mul__ 分别对应+和*,是中缀运算符
注: 中缀运算符在操作数的中间
两个方法的返回值都是新创建的向量对象
中缀运算符的基本原则就是不改变操作对象,而是产出一个新的值
1.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
示例代码
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __bool__(self):
return True if self.age > 0 else False
def __len__(self):
return len(self.name)
class Human:
def __init__(self, name, age):
self.name = name
self.age = age
def __len__(self):
return len(self.name)
if __name__ == '__main__':
p1 = Person('wuxianfeng', 0)
p2 = Person('qianyuli', 1)
print(bool(p1))
print(bool(p2))
从上面的案例中可以看出来
Person类__bool__就是bool(p1)背后调用的
而在Human类中没有定义__bool__,__len__就发挥作用了
https://docs.python.org/zh-cn/3.9/library/stdtypes.html#truth-value-testing 逻辑值检测
一个对象在默认情况下均被视为真值,除非当该对象被调用时其所属类定义了 __bool__() 方法且返回 False 或是定义了 __len__() 方法且返回零
1.3 特殊方法一览
https://docs.python.org/zh-cn/3.9/reference/datamodel.html#special-method-names


再说一次: 我在知乎的另外一篇文章中也做了一些归类: https://zhuanlan.zhihu.com/p/656429071
1.4 为什么len不是普通方法
len 之所以不是一个普通方法,是为了让 Python 自带的数据结构可以走后门,abs 也是同理
如果 x 是一个内置类型的实例,那么 len(x) 的速度会非常快。背后的原因是 CPython 会直接从一个 C 结构体里读取对象的长度,完全不会调用任何方法
1.5 本章小结
通过实现特殊方法,自定义数据类型可以表现得跟内置类型一样,从而让我们写出更具表
达力的代码——或者说,更具 Python 风格的代码
Python 对象的一个基本要求就是它得有合理的字符串表示形式,我们可以通过
__repr__ 和
__str__来满足这个要求。前者方便我们调试和记录日志,后者则是给终端用户看的
1.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版)中文版》
《流畅的Python》 读书笔记 230926(第一章后半部分)的更多相关文章
- 流畅的python学习笔记:第一章
这一章中作者简要的介绍了python数据模型,主要是python的一些特殊方法.比如__len__, __getitem__. 并用一个纸牌的程序来讲解了这些方法 首先介绍下Tuple和nametup ...
- 【vue.js权威指南】读书笔记(第一章)
最近在读新书<vue.js权威指南>,一边读,一边把笔记整理下来,方便自己以后温故知新,也希望能把自己的读书心得分享给大家. [第1章:遇见vue.js] vue.js是什么? vue.j ...
- C#本质论读书笔记:第一章 C#概述|第二章 数据类型
第一章 1.字符串是不可变的:所有string类型的数据,都不可变,也可以说是不可修改的,不能修改变量最初引用的数据,只能对其重新赋值,让其指向内存中的一个新位置. 第二章 2.1 预定义类型或基本类 ...
- 流畅的python学习笔记:第二章
第二章开始介绍了列表这种数据结构,这个在python是经常用到的结构 列表的推导,将一个字符串编程一个列表,有下面的2种方法.其中第二种方法更简洁.可读性也比第一种要好 str='abc' strin ...
- 《深入理解bootstrap》读书笔记:第一章 入门准备
一.bootstrap框架简介 Bootstrap是最流行的前端开发框架. 什么是框架:开发过程的半成品. bootstrap具有以下重要特性: (1)完整的CSS样式插件 (2)丰富的预定义样式表 ...
- 《LINUX内核设计与实现》读书笔记之第一章和第二章
一.第一章 1. Unix内核的特点简洁:仅提供系统调用并有一个非常明确的设计目的抽象:几乎所有东西都被当做文件可移植性:使用C语言编写,使得其在各种硬件体系架构面前都具备令人惊异的移植能力进程:创建 ...
- Android群英传》读书笔记 (1) 第一章 Android体系与系统架构 + 第二章 Android开发工具新接触
第一章 Android体系与系统架构 1.Dalvik 和 ARTDalvik好比是一辆可折叠的自行车,平时是折叠的,只有骑的时候,才需要组装起来用.ART好比是一辆组装好了的自行车,装好就可以骑了. ...
- Linux内核分析 读书笔记 (第一章、第二章)
第一章 Linux内核简介 1.1 Unix的历史 Unix很简洁,仅仅提供几百个系统调用并且有一个非常明确的设计目的. 在Unix中,所有东西都被当做文件,这种抽象使对数据和对设备的操作是通过一套相 ...
- JavaScript权威指南读书笔记【第一章】
第一章 JavaScript概述 前端三大技能: HTML: 描述网页内容 CSS: 描述网页样式 JavaScript: 描述网页行为 特点:动态.弱类型.适合面向对象和函数式编程的风格 语法源自J ...
- 《流畅的python》读书笔记,第一章:python数据模型
这本书上来就讲了魔法方法,也叫双下方法.特殊方法,通过两个例子对让读者了解了双下方法的用法,更重要的是,让我一窥Python的语言风格和给使用者的自由度. 第一个例子:一摞Python风格的纸牌: i ...
随机推荐
- A First course in FEM —— matlab代码实现求解传热问题(稳态)
这篇文章会将FEM全流程走一遍,包括网格.矩阵组装.求解.后处理.内容是大三时的大作业,今天拿出来回顾下. 1. 问题简介 涡轮机叶片需要冷却以提高涡轮的性能和涡轮叶片的寿命.我们现在考虑一个如上图所 ...
- 2023-06-24:给你一根长度为 n 的绳子, 请把绳子剪成整数长度的 m 段, m、n都是整数,n > 1并且m > 1, 每段绳子的长度记为 k[0],k[1]...k[m - 1]。 请问
2023-06-24:给你一根长度为 n 的绳子, 请把绳子剪成整数长度的 m 段, m.n都是整数,n > 1并且m > 1, 每段绳子的长度记为 k[0],k[1]...k[m - 1 ...
- 国标GB28181协议客户端开发(二)程序架构和注册
国标GB28181协议客户端开发(二)程序架构和注册 本系列文章旨在探讨国标GB28181协议设备端的开发过程.本文将聚焦于架构设计和设备注册,并详细介绍了设备端的程序架构设计.exosip库介绍和接 ...
- ModifyAjaxResponse,修改ajax请求返回值,前后端调试之利器
一.概要 先看图 京豆多的离谱,你的第一想法肯定是:按F12修改了网页元素 没那么简单,你看支持刷新的 肯定还是假的,通过 Fiddler 或 Wireshark 等抓包工具修改了响应包:或者干脆改了 ...
- APP中Web容器的核心实现
现在的业务型APP中,采用纯原生开发策略的已经很少了,大部分都使用的混合开发.如原生,H5,ReactNative,Flutter,Weex它们之间任意的组合就构成了混合开发. 其中原生+H5是出 ...
- Python + unittest + ddt + HTMLTestRunner + log + excel + mysql + 企业微信通知, 接口自动化框架V2.0,支持多业务处理,仅需维护 excel 用例,无需要编写代码
Python + unittest + ddt + HTMLTestRunner + log + excel + mysql + 企业微信通知 + Jenkins 实现的接口自动化框架. 项目介绍 接 ...
- 软件设计 day1
Software Design Methodology 软件设计方法学 中国石油大学(华东)2022-2023-3 国际周课程 Advanced software design 张晓东老师邀请在日本广 ...
- C++图像处理函数及程序(一)
C++开源项目: Boost.GIL:通用图像库 CImg :用于图像处理的小型开源C++工具包 CxImage :用于加载,保存,显示和转换的图像处理和转换库,可以处理的图片格式包括 BMP, JP ...
- MySQL8 概述、下载、安装、使用(Windows2019和centos7.9)
MySQL8 概述.下载.安装.使用(Windows2019和centos7.9) 1.MySQL概述 1.1 数据库相关概念在这一部分,先了解三个概念:数据库.数据库管理系统.SQL. 名称 全称 ...
- Redis的设计与实现(2)-链表
链表在 Redis 中的应用非常广泛, 比如列表键的底层实现之一就是链表: 当一个列表键包含了数量比较多的元素, 又或者列表中包含的元素都是比较长的字符串时, Redis 就会使用链表作为列表键的底层 ...