我买了本豆瓣 9.6 分的 Python 书,发现里面每 5 页一个错误?!

2,039 阅读11分钟

前几天逛豆瓣读书,看到一本 Python 新书,评价还不错。书名是《从0到1 Python即学即用》,作者莫振杰,人民邮电出版社今年 3 月份出版,其分数达到了令人吃惊的 9.6 分(82 人评价)。

豆瓣链接:book.douban.com/subject/363…

虽然我不是 0 基础的 Python 开发者,但平时也会写一点面向初学者的技术博客,所以对这本书产生了兴趣。想看看这本书有哪些独到之处,希望自己能从优秀作者身上学到点东西。

于是我当天便下单了一本《从0到1》。书在次日晚上送到,在迫不及待地读完大半本后,我发现事情好像有点不太对劲。仅前 14 章,书中便已出现多达几十多个知识或常识性错误,有些错误甚至低级到让人匪夷所思。

我挑选了其中 30 个有代表性的错误摘录在此,邀请大家一起鉴定。

书中部分错误摘录

1. Python 只能拿英文字母做变量名吗?

image.png

Python 完全支持用 unicode 作为变量名(从 Python 3 开始),但作者却说只能变量名由英文字母组成。

>>> 世界 = 'World'
>>> print(世界)
World

离谱程度:⭐⭐⭐

2. 三引号真的等于“注释”吗?

image.png

在介绍注释时,作者给了一个很直接的观点:“多行注释使用三引号”。

这个说法不说是错的,至少不够严谨。据我说知,主流 Python 项目基本都是直接用 # 号来作为多行注释。而三引号作为创建字符串字面量的一种方式,是可当成注释用,但从未大规模流行过。

离谱程度:⭐⭐

3. Python 没有 switch 语句吗?

image.png

一本号称基于 Python 3.11 版本写的书,斩钉截铁地说 Python 没有 switch 语句,我不理解。真的就不提一嘴 match ... case ... 吗?

离谱程度:⭐⭐⭐

4. Python 中没有数组吗?

Python 中当然有数组(只是不常用),作者你为什么不试试执行下 import array 呢?

官方文档:array — Efficient arrays of numeric values — Python 3.11.3 documentation

离谱程度:⭐⭐

5. del 不能删除列表的最后一个元素吗?

image.png

在介绍列表的 pop() 方法和 del 语句的区别时,作者说用 del 是无法删除列表的最后一个元素的。我实在不懂他为什么要这么说。

咱先不说对比 pop 和 del 这件事本身就有点离谱,而且,执行 del animals[-1] 不就是删了最后一个元素吗?问题在哪呢?

离谱程度:⭐⭐⭐

6. 列表有 join 方法吗?

image.png

在介绍列表的内置方法时,作者把 join() 列在了里面。但是 .join() 从来都是字符串类型的内置方法,而不是列表的。也许作者使用的是来自另一个平行宇宙的 Python?

离谱程度:⭐⭐⭐⭐⭐

7. 将空列表赋值给变量就能清空列表吗?

image.png

在介绍如何清空列表时,作者提到将一个空列表赋值给原变量,就能达到“清空”列表的目的。

看到这个说法,我有些啼笑皆非。赋值操作只是修改了原变量的绑定关系(binding)而已,清空列表从何谈起?

离谱程度:⭐⭐⭐⭐⭐

8. 列表只能和正整数相乘?

不是这样的,列表可以和任何整数相乘:

>>> nums = [1, 2, 3]
>>> nums * 0
[]
>>> nums * -1
[]

离谱程度:⭐⭐⭐

9. 元组是由小括号定义的?

image.png

不,小括号不能定义元组,逗号才是定义元组的关键。

>>> t = 1, 2
>>> type(t)
<class 'tuple'>

离谱程度:⭐⭐⭐⭐⭐

10. 代码示例中,和 None 的对比不规范

image.png

在一份代码示例中,作者写出了 if result == None 这种代码。但是,稍有经验的 Python 开发者都知道,这类判断得写成 if result is None 吧?

小声问一句:作者清楚 is== 的区别吗?

离谱程度:⭐⭐⭐⭐⭐

11. 字符串替换默认只替换一次吗?

image.png

在介绍字符串的 .replace() 方法时,作者说该方法默认只会替换一次,其中 n 的默认值是 1。

这个错误实在离谱。哪怕稍微写过一点 Python,都应该知道 .replace() 默认替换无数次啊?n(其实参数名不叫 n 而叫 count) 的默认值是 -1,不是 1

replace(old, new, count=-1, /) method of builtins.str instance
    Return a copy with all occurrences of substring old replaced by new.

离谱程度:⭐⭐⭐⭐⭐

12. 介绍字符串 split() 方法时缺少 maxsplit 参数

image.png

紧接着,在 .replace() 之后,介绍 .split() 方法时,作者好像突然忘了 .split() 也是支持“最大切分次数(maxsplit)” 参数的。整段介绍只字未提该参数,实在奇怪。

离谱程度:⭐⭐⭐⭐

13. split() 的默认切分策略不是空格

当调用 split() 时不提供任何参数时,Python 不是把空格作为分隔符,而是把包括制表符、换行符等在内的所有空白字符当做分隔符。

离谱程度:⭐⭐⭐⭐⭐

14. strip() 支持多个字符作为参数,而不是一个

image.png

介绍 strip() 方法时,作者说该方法的作用是去除首尾的字符,这没错。但在介绍细节时,作者在“语法”和“代码示例”中只写了单个字符 char。

这是错误的,strip() 默认支持多个字符:

>>> s = 'Hello, World'
>>> s.strip('eHd')
'llo, Worl'

离谱程度:⭐⭐⭐⭐⭐

15. 字典是无序的吗?

image.png

我很确定,至少在作者所声称的这本书所基于的 Python 3.11 版本中,字典是有序(保留插入顺序)的,而不是无序的。

离谱程度:⭐⭐⭐⭐⭐

16. print 一个集合会对其进行排序吗?

image.png

在介绍集合类型时,作者提出了一个骇人听闻的说法:“当我们用 print() 输出一个集合时,会对其进行排序。”我想破了脑袋,也想不出他为啥要这么说,因为众所周知,Python 中的集合是无序的啊?

直到我仔细研读这段话的前面内容,才隐约猜出他为什么会有这个观点:

image.png

原来是他在打印 {1, 2, .., 5} 这个集合时,碰巧看到了这个所谓的“排序”现象,将这种由于哈希值导致的巧合当成了 Python 的固定行为。

我真的不知道该说什么好。

离谱程度:⭐⭐⭐⭐⭐

17. 谁会写出 num in result.keys() 这样的代码?

image.png

看到这段代码时,我沉默了。我已经很久没有看到有人写出这样的判断语句了。

众所周知,在 Python 中,判断某个键是否在字典中,更常见的写法是这样:

if num not in result:
    ...

离谱程度:⭐⭐⭐

18. 函数分为“有返回值”和“无返回值“两种?

image.png

在介绍函数时,作者说函数分为“有无返回值”两种。恕我才疏学浅,这种分类方式我是第一次听到,非常具有原创性。

不知道作者怎么看待生成器函数(协程)?你准备给它归入哪一类?

离谱程度:⭐⭐

19. 介绍传参报错时的逻辑谬误

image.png

在介绍函数传参时,作者写了一个会报错的示例:”有默认值的参数在前“。其中,他对于报错原因的解释令人非常费解。

解释器已经明说了,这是一个语法错误(SyntaxError)。这个语法错误在定义 ball 函数时就已经抛出,而不是下面的 ball('red') 调用语句。

但作者怎么还扯什么“第二参数没有传入值”之类的原因呢?他真的有认真阅读错误栈信息吗?

离谱程度:⭐⭐⭐⭐⭐

20. 介绍 sum() 函数时的奇怪逻辑

image.png

在介绍 max()、min() 和 sum() 函数时,作者非常“贴心”地特意提了一句:max() 和 min() 支持 max(1, 2, 3) 这种所谓的支持”一组数“的调用方式,而 sum() 不支持。

这本身是事实,但作者并没有详细解释原因,而只是抛出一句语焉不详的”你可以自行试一下“。

这到底有啥好试的?sum 不支持这种调用,是因为函数签名本来就不支持可变参数好嘛??

max() 函数签名:

max(...)
    max(iterable, *[, default=obj, key=func]) -> value
    max(arg1, arg2, *args, *[, key=func]) -> value

再看 sum() 函数签名:

sum(iterable, /, start=0)
    Return the sum of a 'start' value (default: 0) plus an iterable of numbers

其中根本就没有支持可变参数啊。

离谱程度:⭐⭐

21. __init__() 方法的第一个参数不是必须叫 self

image.png

self 这个名字是一个事实标准,但不是不能打破的约束。

离谱程度:⭐⭐⭐

22. 通过实例来访问类属性真的不规范吗?

image.png

作者说,在“实际开发”(这个词在本书中多次出现)中,不建议通过实例来访问类属性,这不规范。

再次恕我才疏学浅,这个说法我又是第一次听到。通过实例访问类属性实在常见,我不知道作者为什么这么说。

离谱程度:⭐⭐⭐

23. 异常捕获代码有 bug

image.png

在演示 try ... finally 语句时,作者给出了上面的代码。

这段代码中有一个很常见的 bug:file = open(...) 应该在 try 之外,而不是在 try 内部。像现在这样写,当文件无法正常打开时,finally 内的代码块会报出 file 变量未定义的错误。

正确的写法:

file = open(...)
try:
	# Do something with file
finally:
	file.close()

离谱程度:⭐⭐⭐⭐

24. 应该优先使用 readlines 读取文件?

image.png

在文件读取部分,作者抛出了一个观点:实际开发(天啊,又一次所谓的”实际开发“)中,应该优先使用 readlines() 而不是 readline(),因为后者一次只能读取一行……

再次恕我没啥见识,这是我第一次听到类似说法。

作者为何要对 Python 中的事实标准做法 for line in f: 视而不见呢?要知道,它也是一次只读一行哦。

离谱程度:⭐⭐⭐⭐

25. 尽量少用正则表达式的 search 和 match?

image.png

又一个骇人听闻的”实际开发“中的建议。

离谱程度:⭐⭐⭐⭐

26. 翻转字符串必须转换成列表吗?

image.png

请听我说,不用搞一个列表这么麻烦,你只要写 ''.join(reverse(s)) 就行了。

离谱程度:⭐⭐

27. 关于深拷贝和浅拷贝

image.png

上面这段文字里几乎全是错误。

  • 使用 {**persion} 这种解包方式,完成的是浅拷贝,而不是作者所声称的深拷贝,不信的话,给字典塞一个列表进去试试就知道了
  • 在执行浅拷贝时,Python 根本不区分什么”基本类型“或”引用类型“(多么罕见的 Python 术语!),浅拷贝总是只拷贝引用(视值的可变性,效果不同),只有深拷贝才会递归地复制值

离谱程度:⭐⭐⭐⭐⭐

28. 代码语法错误

离谱程度:⭐⭐

29. 不规范的类型判断

Python 中,类型判断应该优先使用 isinstance(),而不是 type(...) == str

离谱程度:⭐⭐

30. 关于 filter() 的解释非常怪

image.png

我不知道作者写这么一大串,说 filter(None) 的结果不符合预期是啥意思。None 是 filter 的参数缺省值,不存在什么应该是 [False, None ...] 满足条件的说法,这不是什么所谓的”特例。“

离谱程度:⭐⭐⭐

后记

读完《从0到1》的前半部分,我合上书,看着封面上的宣传文案:“‘六边形’Python教程,满足初学者的学习幻想!”,感受到了一种黑色幽默。忽然很心痛那笔购书款——整整 86.3 元,足够我喝一周的奶茶了。

当然,咱也不是说一点收获没有。我复盘了一下,分析自己为什么会买这本奇书:

  • 评分高:豆瓣评分 9.6,排名靠前,大量有文字的评价很真实,不像刷的(我太天真了)
  • 对出版社有好感:人民邮电出版社图灵公司出版,之前读过不少他们出版的 Python 书,印象非常好,没想到这次翻车翻到姥姥家去了…

我买过不少糟糕的技术图书,但这次阅读《从0到1》的过程,大大刷新了我对于“一本书到底有多烂”的认知。真不知道,这种错误百出的书怎么能出版,咱就是说,但凡找个有几年经验的 Python 开发过一遍,也不至于这样啊?

突然又想到,既然书写成这样也能出版,那自己是不是也能出书啊?至少我能做到不跟读者说:“join 是列表的内置方法”“字符串替换默认替换 1 次”这种天方夜谭。

文章较长,权当给大家买书排个雷,希望各位学习 Python 的同学避开此书,祝大家周末愉快。