当前位置: 首页 > 科技赋能

Python 程序员的 30 个常见错误

时间:2024-05-22 15:01:58 科技赋能

在这篇文章中,我将总结一些新的和经验丰富的 Python 程序员常犯的错误,以帮助您避免在自己的工作中犯相同或类似的错误。

首先我要说的是,这些都是基于第一手经验。

我以教授 Python 为生。

在过去的七年里,我向数千名学生教授了数百门 Python 课程,并看着他们犯同样的错误。

换句话说,这些是我见过 Python 初学者犯过数千次的错误。

事实上,这些错误非常常见,我保证你在刚开始学习时也会犯这些错误。

“所以呢?”你问:“你在 Python 中也犯过这么多错误吗?”是的。

Python 可能是最简单、最灵活的语言之一,但它仍然是一种编程语言。

它仍然有语法、数据类型和巫师蒂姆居住的黑暗角落。

这个典故出自《蒙蒂派森与圣杯》中的魔术师蒂姆。

他们的主角指出了洞窟墙壁上记录的圣杯的位置。

作者这里指的是Python语言中容易出错的地方。

另外,Python 语言以作者 Guido van Rossum 最喜欢的《蒙蒂派森飞行马戏团(Monty Python’s Flying Circus)》 命名 - 译者注 好处是,得益于 Python 干净的设计,一旦你学会了 Python,你就可以自动避免很多陷阱。

Python 组件之间的交互最少,可以有效减少 bug。

它还具有非常简单的语法,这意味着您一开始犯错误的可能性较小。

当你真的犯了错误时,Python 的即时错误检测和报告可以帮助你快速恢复。

但Python编程并不是一项自动完成的任务,很多事情仍然需要提前准备。

那么废话不多说,我们直接进入正题。

在接下来的三节中,我们将这些错误分为三大类:语用错误、编码错误和编程错误。

如果你想阅读Python中更多常见的错误以及如何避免它们,那么O’Reilly系列书籍的《Python学习手册》(原书第5版)中有详细的解释。

01 语用错误 让我们从基础知识开始,看看编程新手在深入研究语法之前会遇到的事情。

如果您已经完成了一些编程,那么以下内容可能看起来非常简单;如果您曾经尝试教新手如何编程,他们可能不会那么简单。

1. 在交互式提示中输入Python代码。

在>>>交互式提示符中只能输入Python代码,不能输入系统命令。

人们经常在此提示符下输入 emacs、ls 或 edit 等命令。

这些不是 Python 代码。

Python代码中确实有调用系统命令(如os.system和os.popen)的方法,但并不像直接输入命令那么直接。

如果要在交互式提示中启动 Python 文件,请使用 import file 而不是系统命令 python file.py。

2. 文件中(仅)需要 Print 语句,因为交互式解释器会自动输出表达式的结果,因此您不需要交互式地键入完整的 print 语句。

这是一个很棒的功能,但请记住,在代码文件中,您通常只能使用 print 语句查看输出。

3. 注意Windows中的自动扩展。

如果您在Windows中使用记事本编辑代码文件,请注意保存时选择“所有文件”类型,并显式添加. py 后缀。

否则,记事本将在您的文件中添加 .txt 扩展名,从而导致无法以某些启动方式运行该程序。

更糟糕的是,像Word或WordPad这样的文字处理软件也默认添加了一些格式字符,而这些字符是Python语法无法识别的。

所以请记住,在Windows下始终选择“所有文件”并保存为纯文本,或者使用更“编程友好”的文本编辑工具,例如IDLE。

在IDLE中,记得保存时手动添加.py扩展名。

4、Windows下点击图标的问题。

在Windows下,您可以通过单击Python文件来启动Python程序,但这有时会出现问题。

首先,程序的输出窗口在程序结束的那一刻就消失了。

为了防止它消失,您可以在文件末尾添加 raw_input() 调用。

另外,请记住,如果出现错误,输出窗口将立即消失。

要查看您的错误消息,请使用另一种方法来调用您的程序:例如从系统命令行启动、在提示符下使用 import 语句或使用 IDLE 菜单中的选项等。

5. 导入仅在第一次有效。

您可以通过在交互式提示中导入文件来运行它,但这在会话中只能运行一次;后续导入仅返回加载的模块。

要强制 Python 重新加载文件的代码,请为此目的调用函数 reload(module)。

请注意,重新加载时请使用括号,但导入时请勿使用括号。

6. 空行(仅)在交互式提示中起作用。

模块文件中的空行和注释将被忽略,但在交互式提示中键入代码时,空行表示复合语句的结尾。

换句话说,一个空行告诉交互式提示您已经完成了一个复合语句;在实际完成之前不要输入 Enter。

事实上,当您想要开始一条新语句时,您需要键入一个空行来结束当前语句 - 交互式提示一次只运行一个语句。

02 代码错误 一旦你开始认真地编写Python代码,一堆陷阱就会变得更加危险——这些是跨语言特性的基本代码错误,经常困扰不专心的程序员。

7. 不要忘记冒号。

这是新手程序员最常犯的错误:不要忘记在复合语句的起始语句(if、while、for等语句的第一行)末尾添加冒号:“。

你可能会忘记起初,这会成为一种潜意识的习惯,当天班上 75% 的学生就能记住这一点。

8. 初始化变量 在 Python 中,表达式中的名称只有在被分配一个值后才能使用。

这是有意为之的:它避免了一些输入错误,也避免了关于默认值应该是什么类型的问题(0、None、“??”、[]、?)。

9. 从第一列开始,确保将顶级、未嵌套的代码放在最左边的列中,这包括未嵌套在模块文件中的代码以及未嵌套在交互式提示中的代码。

Python 使用缩进来区分嵌套的代码块,因此代码左侧的空格表示嵌套的代码块。

除了缩进之外,空格通常会被忽略。

10. 缩进保持一致。

避免在同一代码块中混合使用制表符和空格进行缩进,除非您知道运行代码的系统如何处理制表符。

否则,编辑器中看起来像制表符的缩进对于 Python 来说可能看起来像空格。

为了安全起见,请使用全部制表符或全部空格来缩进每个代码块;使用更多或更少取决于您。

11.调用函数时使用括号。

无论函数是否需要参数,调用时都必须添加一对括号。

也就是说,使用 function(),而不是 function。

Python 函数只是具有特殊函数(调用)的对象,调用是使用括号触发的。

与所有对象一样,它们可以分配给变量并间接使用:x=function:x()。

在Python训练中,文件操作时经常会出现这样的错误。

新手经常使用 file.close 来关闭问题,而不是 file.close()。

因为在 Python 中引用函数而不调用它是合法的,所以不带括号的操作 (file.close) 会默默地成功,但不会关闭文件! 12、导入时不要使用表达式或路径在系统命令行中使用文件夹路径或文件扩展名,但不要在导入语句中使用。

即使用 import mod 而不是 import mod.py,或者 import dir/mod.py。

事实上,这可能是初学者犯的第二大错误。

因为模块会有 .py 以外的后缀(例如 .pyc),所以强制使用某个后缀不仅不符合语法,而且毫无意义。

系统特定目录路径的格式来自模块搜索路径设置,而不是导入语句。

您可以在文件名中使用点来指向包的子目录(例如 import dir1.dir2.mod),但最左边的目录必须通过模块搜索路径找到,并且 import 中没有其他路径格式。

错误的语句 import mod.py 被Python认为记录在一个包中。

它首先加载一个模块 mod,然后尝试在名为 mod 的目录中查找名为 py 的模块。

最后可能什么也找不到。

报告一系列令人困惑的错误消息。

13.不要用Python编写C代码。

以下是针对刚接触 Python 的 C 程序员的一些提醒: 在 if 和 while 中测试条件时不要输入括号(例如 if (X==1):) 。

如果您愿意,可以添加括号,但它们在这里完全是多余的。

不要以分号结束您的语句。

从技术上讲,这在 Python 中是合法的,但除非您想将许多语句放在同一行上(例如,x=1; y=2; z=3),否则它没有用。

不要在 while 循环的条件测试中嵌入赋值语句(例如 while ((x=next() != NULL)))。

在Python中,需要表达式的地方不能出现语句,并且赋值语句不是表达式。

03 编程错误最后说一下你使用较多Python函数(数据类型、函数、模块、类等)时可能遇到的问题。

由于篇幅有限,我在这里会尽量简洁,尤其是一些高级的。

概念。

欲了解更多详情,请阅读《Python学习手册》。

14. 打开文件的调用不使用模块搜索路径。

当你在Python中调用open()访问外部文件时,Python不会使用模块搜索路径来定位目标文件。

它将使用您提供的绝对路径,或者假设该文件位于当前工作目录中。

模块搜索路径仅用于模块加载。

15、不同的类型有不同的方法。

列表方法不能用于字符串,反之亦然。

通常,方法调用是特定于数据类型的,但内部函数通常可用于多种类型。

例如,列表的reverse方法仅适用于列表,但len函数适用于任何有长度的对象。

16. 不能直接更改不可变数据类型。

请记住,您不能直接更改不可变对象(例如元组、字符串): T = (1, 2, 3)T[2] = 4 # 错误使用切片、连接等。

构造一个新对象并分配值?根据需要将原始变量添加到其中。

因为 Python 会自动回收未使用的内存,所以这并不像看上去那么浪费: T = T[:2] + (4,) # 没问题:T 变成 (1, 2, 4)17.易于使用 for 循环而不是 while 或 range 当您想要从左到右迭代排序对象的所有元素时,与 while 或 range 相比,可以使用简单的 for 循环(例如,for x in seq:)基于 的计数循环更容易编写并且通常运行速度更快。

除非绝对需要,否则请避免在 for 循环中使用范围:让 Python 为您处理标签问题。

在下面的示例中,所有三个循环结构都很好,但第一个通常更好;在 Python 中,简单就是王道。

S = "lumberjack" for c in S: print c # 最简单的 for i in range(len(S)): print S[i] # 太多 i = 0 # 太多 while i < len(S): print S [我];我+=。

不要尝试从改变对象的函数中获取结果。

直接变异操作,例如方法 list.append() 和 list.sort() 会变异一个对象,但不会返回它们更改的对象(它们将返回 None);正确的做法是直接调用它们而不分配结果。

经常看到初学者写出这样的代码:mylist = mylist.append(X)。

目的是获得append的结果,但实际上,这样做会将None分配给mylist,而不是更改后的列表。

一个更特殊的例子是使用排序后的键值来遍历字典中的每个元素。

请看下面的例子: D = {...}for k in D.keys().sort(): print D[k] 几乎成功——keys方法创建一个key列表,然后使用sort方法进行排序列表 - 但因为排序方法返回 None,所以循环失败,因为它实际上要迭代 None(这不是一个序列)。

要更正此代码,请将方法调用分成不同的语句,如下所示:Ks = D.keys()Ks.sort()for k in Ks: print D[k]19。

仅在数字类型中 类型转换仅存在于 Python 中。

诸如 之类的表达式。

会起作用 - 它会自动将整数转换为浮点类型,然后使用浮点运算。

但下面的代码会出错: S = "42" I = 1X = S + I # Type error 这也是故意的,因为不清楚:到底是什么将字符串转换为数字(进行相同的加法),或者将数字转换为字符串(连接)?在Python中,我们相信“显式优于隐式”(即EIBTI(显式优于隐式)),所以必须手动转换类型: X = int(S) + I # 做加法: 43X = S + str(I) # 字符串连接: ""20.循环数据结构可能会导致循环。

尽管这在实践中很少见,但如果一个对象 包含对其自身的引用的集合,则称为循环对象。

如果在对象中发现循环,Python 将输出一个 [...] 以避免陷入无限循环: >>> L = ['grail'] # 在 L 中引用 L 本身将会 >>> L. append(L) # 在对象中创建循环 >>> L['grail', [...]] 除了知道这三个点代表对象中的循环之外,这个例子也值得借鉴。

因为你的代码中可能会不经意间出现这样的循环结构,导致你的代码出错。

如有必要,维护已访问的对象的列表或字典,然后检查它以查看是否遇到循环。

21. 赋值语句不创建对象的副本,仅创建引用。

这是Python的一个核心概念,有时表现不正确会导致错误。

在下面的示例中,一个列表对象被分配给一个名为 L 的变量,然后在列表 M 中引用该变量。

如果在内部更改 L,则也会更改 M 引用的对象,因为它们都指向同一个对象。

>>> L = [1, 2, 3] # 共享列表对象 >>> M = ['X', L, 'Y'] # 嵌入对 L 的引用 >>> M['X', [ 1, 2, 3], 'Y']>>> L[1] = 0 # 也更改了 M>>> M['X', [1, 0, 3], 'Y'] 通常仅在较大的情况下这变得很重要程序,这些共享参考通常就是您所需要的。

如果没有,您可以显式创建它们的副本以避免共享引用;对于列表,您可以使用空列表的切片创建顶级副本: >>> L = [1, 2, 3]>>> M = ['X', L[:], 'Y' ] # 嵌入 L 的副本 >>> L[1] = 0 # 只改变 L,但不影响 M>>> L [1, 0, 3]>>> M['X', [1, 2 , 3], 'Y'] 切片范围从默认0开始到被切片序列的最大长度。

如果两者都被省略,切片将提取序列中的所有元素并创建顶层的副本(一个新的非公共对象)。

对于字典,请使用字典的 dict.copy() 方法。

22. 局部作用域中变量名的静态标识 Python 默认将函数中分配的变量名视为局部作用域。

它们存在于函数的作用域内,并且仅在函数运行时存在。

从技术上讲,Python在编译def代码时静态地识别局部变量,而不是在运行时遇到赋值时。

不理解这一点可能会导致误解。

例如,看下面的示例,当您在引用后为变量赋值时会发生什么: >>> X = 99 >>> def func():... print X # 还不存在... X = 88 # Treat 编译此代码时,Python 遇到对 X 的赋值语句,并假设 X 将被视为此函数中任何位置的局部变量名称。

但后面实际运行这个函数时,执行打印语句时,赋值语句还没有发生,所以Python会报“未定义变量名”错误。

其实前面的例子想要做的事情很模糊:是想先输出全局的X,然后再创建一个本地的X,还是这是一个程序错误?如果你确实想导出这个全局X,你需要在全局语句中声明它或者通过封装模块的名称引用它。

23、默认参数和变量对象 执行def语句时,默认参数的值只解析并保存一次,而不是每次调用函数时都会解析并保存。

这通常是您想要的,但由于默认值需要在每次调用时保持相同的对象,因此在尝试更改可变默认值时应该小心。

例如,以下函数使用空列表作为默认值,然后在每次后续函数调用时更改其值: >>> def saver(x=[]): # 保存列表对象... x .append( 1) # 并且每次调用时... print x # 更改其值...>>> saver([2]) # 默认值 [2, 1]>>> saver() 不使用 # 使用默认值 [1]>>> saver() # 每次调用都会增加! [1, 1]>>> saver()[1, 1, 1] 有些人认为这是Python的一个特性——因为可变默认参数在函数调用之间保留它们的状态,所以它们可以提供类似于静态局部函数变量的功能在C.然而,当您第一次遇到它时,这似乎很奇怪,并且 Python 中有更简单的方法可以在调用之间保存状态(例如类)。

要消除这种行为,请使用切片或方法在函数开头创建默认参数的副本,或者在函数内移动默认值的表达式;只要这些值在每次调用函数时,都会得到一个新对象: >>> def saver(x=None):... if x is None: x = [ ] # 没有传入参数?... x.append(1) # 更改新列表 ... print x...>>> saver([2]) # 默认值 [2, 1]>>> saver() # 这次不会改变 [1]>>> saver()[ 1] 24. 其他常见的编程陷阱 以下是其他一些不能在这里详细讨论的陷阱: 顶层语句的顺序文件很重要:因为运行或加载文件会从上到下运行它。

语句,因此请务必将未嵌套的函数调用或类调用放在函数或类定义之后。

reload 不会影响使用 from 加载的名称:reload 最好与 import 语句一起使用。

如果使用from语句,记得重新加载后重新运行from,否则仍然会使用旧名称。

多重继承中的混合顺序是特殊的:这是因为超类的搜索是从左到右,位于类定义的头部。

如果多个超类中有重复的名称,则将使用最左边的类名称。

为准。

t88ry 语句中的空 except 子句可能会捕获比您预期更多的错误。

try 语句中的空 except 子句意味着所有错误都会被捕获,甚至真正的程序错误和 sys.exit() 调用也会被捕获。

中科院计算技术研究所培训中心依托中科院强大的技术背景,致力于为国家、企业、社会培养专业计算机人才。

经过近三十年的发展,现已成为国内顶级权威的IT精英培训机构,并受到众多业内人士的好评和好评。

我们将不懈努力,为中国输送更多高端IT精英技术人才。