- 调试的时候需要区分不同类别的错误,这样才能更好地找到错误:
- 语法错误是python将源代码翻译成字节代码的时候产生的,说明程序的结构是有问题的;
- 如省略
def
语句中的冒号产生如下错误:SyntaxError: invalid syntax
- 如省略
- 运行时错误是当程序运行的时候出错,解释器所产生的错误:
- 大多数运行时错误包含错误在哪里产生和正在执行哪个函数这样的信息;
- 如一个无限递归函数造成:
maximum recursion depth exceeded
- 语义错误是指一个程序并没有抛出异常信息,但是并没有做出正确的事情:
- 如一个表达式并没有按照预期的顺序执行产生错误结果;
(一)语法错误
对于语法错误来讲,抛出的错误信息通常帮助并不是很大,但是能够确定出错的位置,但是真正错误的位置可能在错误消息出现之前;
- 避免语法错误的方法:
- 变量名不与关键字重名;
- 每个复合句的末尾都有冒号:
for
,while
,if
,def
; - 引号的匹配;
- 三重引号的多行字符串,要确保正确地结束了字符串;
- 避免没有关闭的操作符:
(
,{
,[
; - 检查
==
是不是写成了=
; - 确保缩进符合要求;
- 避免使用非ASCII码;
当以上的信息都不能起作用时候:
需要检查:当前正在编译的代码是python试图运行的代码:可以尝试在程序的开头故意设置一些小错误;
如果故意设置的小错误并没有提示新的错误:
- 原因:
- 运行之前并没有将编辑进行保存:有些编程环境不能自动保存;
- 更改了文件名,但是运行的时候仍然使用旧名称;
- 开发环境配置不正确;
- 使用
import
时,确保没有使用python的标准模块名作为模块名; - 当使用
import
载入一个模块,必须重启解释器或者使用reload
才能重新载入一个修改了的文件; - 导入一个模块两次,第二次是无效的;
(二)运行时错误
当语法正确的时候,python能够编译,这时候可能出现运行时错误;
1. 程序什么都没做
当文件由函数和类组成,但是并没有实际调用函数执行,这个错误经常的出现;
2. 程序挂死
**程序挂死:**程序停止,但是程序看起来什么都没做;通常来说是程序陷入了无限循环或者无限递归;
-
如果你怀疑问题出在某个循环,在该循环之前添加一个打印语句,输出“进入循环”,在循环之后添加一个打印“退出循环”的语句;
-
大多数情况下,无限递归会造成程序运行一会儿之后输出
RuntimeError: Maximum recursion depth exceeded
错误。 -
无限递归
递归需要有一个基础情形:存在某种条件能够终止递归,而不是无限递归下去;
- 执行流
当不确定程序执行的过程,在每个函数的开始处添加打印语句,用于跟踪函数调用的轨迹;
3. 运行程序的时候产生异常
- 当运行的时候产生错误,python会 打印出一些信息,包括异常的名称、产生异常的行号和一个回溯(traceback);
- 回溯会指出正在运行的函数、以及调用它的上层函数;即它追踪进行到目前函数调用所调用过的函数,包括每次函数的调用所在的行号;
- 常见运行时错误:
- 命名错误(NameError):使用当前环境中不存在的变量名;
- 类型错误(TypeError):
- 值的使用方法不对:下标不是整数
- 格式化字符串中的项与传入用于转换的项之间不匹配;
- 传递给函数的参数数量不对;如果是方法,需要查看方法的第一个参数是不是self;
- 键错误(KeyError):使用字典中没有的键访问字典;
- 属性错误(AttributeError):
- 尝试访问不存在的属性或者方法;
- 如果一个属性错误表名一个对象是
NoneType
,则意味着它是None
,问题不在于属性名,而是在于对象本身; - 对象为
None
的一个原因是函数没有返回值,函数如果没有返回值,则会返回None
- 使用列表方法,通常返回的结果为
None
,如sort
方法,返回值为None
- 索引错误(IndexError):访问列表、字符串或者元组的索引超出文件索引范围;
4. 使用太多的打印语句,以至于刷屏
- 解决方案:
- 简化输出:你可以移除或注释掉不再需要的打印语句,或者合并它们,或者格式化输出便于理;
- 简化程序:
- 缩减当前求解问题的规模;
- 清理程序:移除死代码,并且重新组织程序使得易于理解;
(三)语义错误
1. 程序不能工作
- 应该运行的代码是不是都运行了;不应该运行的代码是不是都没有运行;代码是不是产生了预期的效果;
- 当程序不能正确运行的时候,问题常常不是在于程序本身,而是在于我们自己关于程序是怎样运行的思维模型出现问题;
- 纠正思维模型的办法是将程序分成组件,即通常的函数和方法,之后分别单独测试每个组件;
2. 复杂表达式不能正确运行
复杂表达式通常很难进行调试,将这个复杂的表达式打散成一系列临时变量的赋值语句是好办法:
self.hands[i].addCard(self.hands[self.findNeighbor(i)].popCard())
打散后:
neighbor = self.findNeighbor(i)
pickedCard = self.hands[neighbor].popCard()
self.hands[i].addCard(pickedCard)
- 超长表达式的另外的一个问题是:计算顺序和预期的不一致;只要不太确定计算顺序就用括号;
3. 函数没有返回预期的结果
可以使用临时变量暂时存储函数的返回值的结果,便于调试;
最终目标不是让程序工作,是学习如何让程序正确工作