使用Python16年后我有时还在犯的7个错误
这是我的部分生活故事,如果你不在乎,可以跳过这部分。
这个数字 16 有些误导,实际是这样的,我在 2003 年第一次接触 Python(我没有计算机科学背景,我以前学的是音乐和化学!),那年我实际是在写一本小说(都是一些华丽的废话,我惨遭失败。但是仅作为短篇小说作家,我还是要有一些出版作品的,包括诗歌)。任何一个曾经是作家的人都知道,有时候你需要将思维转向其他方面让潜意识来为你工作,所以像很多作家会打扫房间一样(不过这不是我的事,我有些羞于承认),我决定自学编程(我在德州仪器的一台带有音频磁带机的家用PC上像小孩子一样学习 BASIC,那时我年纪已经很大了,并且在大学时学习了一门 Pascal 课程)。我买了一本大部头 C++ 书,它对于消遣时间都感觉太大了。所以我又买了 Mark Lutz 的 Learning Python (我记得是在亚马逊上买的),而且这个更简单。我讨厌 Windows,并且在购买了带有大量软件的 OS 9 iMAC 之后对于 Mac 切换到 OS X 系统产生了莫名的怨怼,所以我在 Linux(Debian,IIRC)上使用 Python,基本上用它来作为 bash 脚本和 awk 的替代品,所以就不用再学那些了。2019年,我在一个生物化学实验室工作,无意中听到生物信息学部门的一位同事对另一位同事说:”这里糟透了,没有人知道怎么使用 Python“,当我尝试着举手示意后,获得了兼职的机会。现在我已经全职编程(我下班回家后还在写代码) 8 年了。
所以在 8 或 16 年后,无论你是否在意,我仍然发现我还在做一些愚蠢的事情,对此我只能感谢上帝在 VSCode 中内置了 pylint。但是我还是觉得这些值得分享。
注意,这不是 python 中的典型”陷阱“列表,如使用可变变量作为默认参数或者延迟绑定关闭,这些初学者应该深入并了解的实实在在的东西超出了本文的范围。如果你想了解 Python 中更多的陷阱,这里有四个资源:
- Matrin Chikilian 在 Toptal 上的 Python 程序员常犯的 10 错误
- Jibu James 在 Sayonetech 上的 Python 程序员犯的 7 个错误(他有一个对 __init__ 描述了很多的最佳实践,虽然我没有看到,但是他将其称为构造函数,这与我而言是一个小烦恼。但是这个错误对于那些拥有 Java 背景的人来说并不难见到。这只是一个词而已,谁会在乎呢?)
- Pete 在 Opensource 中发表的 我学 Python 常犯的 3 个错误。Spoiler 声明:这三个都是使用了 MUtables 作为默认参数,但是他给出了两种非常规的 def foo(bar=[]) 方式来解决这个问题,也许值得一读。
- Kenneth Reitz 的在线并出版的 O’Reilly 于《The Hitchhiker’s Guide to Python》一书中 "常见陷阱" 章节,我在下面会再次提到。
1. 混淆字符串的 .find() 方法和 re.search()
EDIT:事实证明我比想象的还要愚蠢。如果你想查找一个字符串的字面匹配结果,下面会更好的一点:
这包括了我在这一节中想描述的所有问题。
这是目前最危险的一个,因为它会导致错误的无声传递,并且没有人能了解你的意图。
假设你有一个需要解析一些文本的逻辑,条件是文本包含单词 ”dotard“(随机选的一个单词)。
通常有两种方式完成这件事:
或
问题是,如果你脑子犯迷糊了,混写了两面的两个:
这个条件计算结果总是为真(-1 是真值,1,2,3...当然也是),除非 'dotard' 是 mystring 的前六个字母,这样条件计算为 False(因为正确时返回索引, 0 是假值)。
我发现自己在编写测试时偶尔会这样写,这里你可以很好的发现这个错误,因为测试没有达到预期效果,你可以查询原因。但是我最近正执着与此。
2. 忘记输入 'enumerate'
很多时候我会这么写:
是想要替代:
我知道这很容易发现,但令我困扰的是,直到今天我有时还会这样做。我觉得是我懒惰的蜥蜴脑子发现 "enumerate" 单词太长了,输入很麻烦(这个单词输入起来很奇怪,要在很接近的按键上用一次左手手指,然后三次右手手指,然后五次左手手指,我就知道我会在各种地方找借口)。
3. 忘记输入 'range'
除了纯粹的疏忽之外,我没有任何好的借口。有时候我会写成:
而不是
4. 滥用(或实际曾使用)断言语句
'assert' 语句只应用作健全性检查,即检查在正常的程序操作中应该完全不可能的条件。一个愚蠢的例子:
为什么?因为你可以通过输入命令,即在终端输入 python -0 myscript.py 跳过断言来使你的脚本运行得更快。所以如果你使用 assert 控制程序流程(有时会异常结束)或者检查意料之外但不可能发生的条件,那么之后运行程序的人可能会无声的传递错误。
解决这个问题的方法是用抛出 AssertError 来代替:
这种实际上是断言的假设是很值得检查的。如果你正在检查变量的值,这里看起来更适合抛出 AssertError。
另一个警告:如果你正是使用 pytest,一些如 codacity 的 CI 工具会忽略你所有的断言语句。常规思维下没有人会运行 pytest -0,所以你可以忽略这些,pytest 围绕纯断言展开,所以你不必使用 unittest 中的 assertEquals 或是其他。
5. 忘记了多个异常的语法
这个可能只有我遇到过。在 Python 3.x 中正确的语法为:
我的思维还停留在长时间在 Python 2 中输入 except ValueError, e,当我看到我在 except 语句中输入逗号是,我仍不知道输入括号。
6. 在 REPL 或 Jyputer notebook 中使用 _ 作为一次性变量声明
如果变量实际上并没有被使用,那么使用一次性变量而不是混淆命名空间的方式,是正确且合适的。例如,如果你需要循环 n 次但不需要知道它每次的索引号,通常这么做:
如果你完全用不到循环索引 i,那么就没有必要混淆命名空间,并且 pylint 会在你这么用的时候发出警告。
使用 REPL 的时候有一个问题,_ 通常表示最近一次表达式计算的结果。例如:
尽管我从来没有遇到过这种问题(我很少使用 REPL,并且在试图远离 Jupyter notebooks,之前我曾在工作中需要它,不过它会使我对编码产生惰性。但是如果你想要使用它们,尽情去用吧,我不做评价),但它确实会导致混乱发生。
Kenneth Reitz 在 Python 简明指南中对此提出了一个很好的解决方案:使用双下划线 __ 代替单个下划线 _。(旁白:这是 Mr. Reitz 花费了大量时间组合的一本免费书,考虑交由 O'Reilly 打印出版。同样要对此报以批判的态度,里面太多次推荐 Reitz 的pipenv 项目,并且 pipenv 降低了很多依赖,事实上在线书籍中 "Vendorizing Dependencies" 一节于我而言有些讽刺。)但就事论事,就我所知它对闭包的后期绑定解释极佳,这与其它大多数闭包解释不同,(1)指出它在任何函数,而不仅仅是 lambdas,尽管在实践中这里容易发生问题,(2)指出如何使用 functools.partial 来定位问题而不是炫酷怪异的模式,(3)指出有时后期绑定 Python 的行为是有用的。我想指出一个非常具体的点(4),使用 Python 的 numpy 库中 piecewise 函数可以解决这个问题。
7. 在日志记录中使用普通字符串格式化语法
每个人都应该知道,你需要记录日志,尤其是在开发的时候。并且在记录日志的时候,不应该使用常规的 Python 字符串语法,它有自己的 基于 *arg 的解包语法:
也就是说有大量的第三方日志记录模块可以让你进行常规的字符串格式化。我自己喜欢 loguru,但是仅仅在第一次开发时使用,一旦项目成熟,它就会回到标准库日志记录模块,所以这是生产环境使用的一种。
就这样!我不是世界上最有经验的程序员(我认为自己处于中级、接近高级的位置,我的意思是我已经过了那种使用双下划线 __new__ 来多次校验子类的时候,这就是高级了,不是么?剧透:这是我的下一个教程主题)。至少觉得这是一个有趣且大部分内容是值得阅读的。从别人的错误中学习总是好的,即使是愚蠢的(额,愚蠢的错误,我不认为我是一个愚蠢的人)。令人沮丧的是,你一遍又一遍地做着同样的错误,且无力改变,但我认为它们可以称之为生活。
英文原文:https://www.prooffreader.com/2019/06/11/eight_stupid_things/
译者:敦伟