《流畅的Python》 读书笔记 第一章数据模型(2) 230926

流畅,python,读书笔记,第一章,数据模型 · 浏览次数 : 22

小编点评

# 人类类__bool__ ```python def __bool__(self): return True if self.age > 0 else False ``` # 人类类__len__ ```python def __len__(self): return len(self.name) ``` # 特殊方法一览 ```python 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) ``` # 归类 ```python class Human: def __init__(self, name, age): self.name = name self.age = age def __len__(self): return len(self.name) def __repr__(self): return '人类: %s' %self.name ```

正文

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 版)》对数据模型的讲解很精彩

https://stackoverflow.com/users/95810/alex-martelli 是他的主页

David Beazley

《Python 参考手册(第 4 版)》

Python Cookbook(第 3版)中文版》

与《流畅的Python》 读书笔记 第一章数据模型(2) 230926相似的内容:

《流畅的Python》 读书笔记 第一章数据模型(2) 230926

1.2 如何使用特殊方法 特殊方法的存在是为了被 Python 解释器调用的,你自己并不需要调用它们 就是说通常你都应该用len(obj)而不是obj.__len()__,无论是系统预置的,还是你自己定义的类,交给Python,解释器会去调用你实现的__len()__ 然而如果是 Python 内置

《流畅的Python》 读书笔记 第一章数据模型(1)230926

写在最前面的话 缘由 关于Python的资料市面上非常多,好的其实并不太多。 个人认为,基础的,下面的都还算可以 B站小甲鱼 黑马的视频 刘江的博客 廖雪峰的Python课程 进阶的更少,《流畅的Python》应该算一个。 加上,自己也很久没有耐心的看完一本书了 鉴于以上2点,2023-9-26开始

《流畅的Python》 读书笔记 第二章数据结构(2) 231011

2.5 对序列使用+和* 通常 + 号两侧的序列由相同类型的数据所构成,在拼接的过程中,两个被操作的序列都不会被修改,Python 会新建一个包含同样类型数据的序列来作为拼接的结果 +和*都遵循这个规律,不修改原有的操作对象,而是构建一个全新的序列 l1 = [1,2,3] l2 = [4,5,6]

《流畅的Python》 读书笔记 第二章数据结构(1) 231007

第2章 数据结构 ABC语言是Python的爸爸~ 很多点子在现在看来都很有 Python 风格:序列的泛型操作、内置的元组和映射类型、用缩进来架构的源码、无需变量声明的强类型 不管是哪种数据结构,字符串、列表、字节序列、数组、XML 元素,抑或是数据库查询结果,它们都共用一套丰富的操作:迭代、切片

Python装饰器实例讲解(三)

Python装饰器实例讲解(三) 本文多参考《流畅的python》,在此基础上增加了一些实例便于理解 姊妹篇 Python装饰器实例讲解(一),让你简单的会用 Python装饰器实例讲解(二),主要讲了一个万能公式(原理) 本文其实反而是最最基础的部分,当然也回答了好几个关键的问题,也有一些是重复的

Python常见面试题016. 请实现如下功能|谈谈你对闭包的理解

016. 请实现如下功能|谈谈你对闭包的理解 摘自<流畅的python> 第七章 函数装饰器和闭包 实现一个函数(可以不是函数)avg,计算不断增加的系列值的平均值,效果如下 def avg(...): pass avg(10) =>返回10 avg(20) =>返回10+20的平均值15 avg(

< Python全景系列-3 > Python控制流程盘点及高级用法、神秘技巧大揭秘!

全面深入地介绍 Python 的控制流程,包括条件语句、循环结构和异常处理等关键部分,尤其会将列表解析、生成器、装饰器等高级用法一网打尽。此外,我还将分享一些独特的见解和研究发现,希望能给你带来新的启发。文章的结尾,我们将有一个 "One More Thing" 环节,我会分享一个很特别但又很少人知道的有用的 Python 控制流程的技巧。

Python学习 —— 初步认知

写在前面 Python是一种流行的高级编程语言,具有简单易学、代码可读性高、应用广泛等优势。它是一种解释型语言,可以直接在终端或集成开发环境(IDE)中运行,而无需事先编译。 Python的安装 Python的安装过程非常简单。首先,你可以从Python的官方网站(https://www.pytho

Python web 框架对比:Flask vs Django

哈喽大家好,我是咸鱼 今天我们从几个方面来比较一些现在流行的两个 python web 框架——Flask 和 Django,突出它们的主要特性、优缺点和简单案例 到最后,大家将更好地了解哪个框架更适合自己的特定需求 参考链接:https://djangocentral.com/flask-vs-d

如何使用Python和Plotly绘制3D图形

本文分享自华为云社区《Plotly绘制3D图形》 ,作者:柠檬味拥抱。 在数据可视化领域,三维图形是一种强大的工具,可以展示数据之间的复杂关系和结构。Python语言拥有丰富的数据可视化库,其中Plotly是一款流行的工具,提供了绘制高质量三维图形的功能。本文将介绍如何使用Python和Plotly