-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathappendix.tex
More file actions
446 lines (266 loc) · 18.1 KB
/
appendix.tex
File metadata and controls
446 lines (266 loc) · 18.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
\chapter{调试}
\index{调试}
程序中会出现不同的错误,加以区分有助于加快对错误的跟踪。
\begin{itemize}
\item 语法错误是Python在将源代码转换为字节码时产生的。它们通常说明程序的语法有错误。例如:在 {\tt def}语句后忽略冒号会产生类似{\tt SyntaxError: invalid syntax}的信息。
\item 运行时错误由解释器在程序运行出错时产生。大多数的运行时错误消息包含错误发生的位置和正在执行的函数。例如,一个无穷递归的函数最终导致运行时错误“maximum recursion depth exceeded”。
\item 语义错误指程序执行过程中没有产生出错信息,但程序没有做正确的工作。例如,一个表达式没有按照你期望的顺序进行求值,导致错误的结果。
\end{itemize}
\index{语法错误}
\index{运行时错误}
\index{语义错误}
\index{错误!编译时}
\index{错误!语法}
\index{错误!运行时}
\index{错误!语义}
\index{异常}
调试的第一步是找出你遇到了什么类型的错误。虽然下面的章节根据错误类型组织的,但一些方法使用于多种情况。
\section{语法错误}
\index{错误消息}
当你找到原因后语法错误一般很容易修正。不幸的是,错误消息常常不是很有帮助。最常见的消息是{\tt SyntaxError: invalid syntax}和{\tt SyntaxError: invalid token},它们都不包含很多信息量。
另一方面,错误消息告诉你问题发生在程序中的什么位置。事实上,当Python遇到问题时它将告诉你,但这不一定是错误的地方。有时错误在错误消息位置的前面,通常是前几行。
\index{增量开发}
\index{开发计划!增量}
如果你增量的开发程序,你会清楚错误在哪里。它就是你最新添加的代码。
如果你从书中复制一段代码,那么从仔细地检查你的代码和书中的代码开始。检查每一个字符。同时记住书本也会有错误,如果你遇到类似语法错误,也许它就是。
下面给出一些避免常见语法错误的方法:
\index{syntax}
\begin{enumerate}
\item 避免使用Python关键字作为变量名。
\index{关键字}
\item 确保你在每个复合语句头的尾部加上了冒号,包括{\tt for},{\tt while},{\tt if}和{\tt def}。
\index{头部}
\index{冒号}
\item 确保代码中的字符串都有匹配的引号。
\index{引号标记}
\item 如果你用三重引号标记多行字符串,确保你正确的结束该字符串。一个未终止的字符串会导致程序尾部{\tt invalid token}错误,或者程序接下来的部分都被认为字符串,直到下一个字符串。在第二种情况,Python可能不会产生一个错误消息!
\index{多行}
\index{字符创!多行}
\item Python将未封闭的运算符---\verb+(+,\verb+{+,或\verb+[+---的下一行作为当前语句的一部分。通常第二行将产生错误。
\item 在条件语句中使用传统的{\tt =}而不是{\tt ==}。
\index{条件}
\item 确保每行缩进正确。Python可以处理空格和tabs,但如果你混淆它们会产生错误。避免这个问题最好的方法是使用适合Python编辑、会自动缩进的文本编辑器。
\index{缩进}
\index{空白符}
\end{enumerate}
如果这些没有效,继续阅读下一章节...
\subsection{我做了修改但是没有任何区别}
如果解释器报错,但你找不到错误,有可能你和解释器看到不不是相同的代码。检查你的编程环境,确保你正在编辑的文件是Python将要运行的。
如果你不确定,在程序的开始位置添加明显的故意的语法错误,再次运行,如果解释器没有找到这个错误,说明你不在运行新的程序。
下面是一些可能的出错原因:
\begin{itemize}
\item 你编辑了文件,运行前忘记了保存。有的编程环境会为你自动保存,有的不会。
\item 你修改了文件的名字,但仍然运行老的名字。
\item 你的开发环境配置有误。
\item 如果你在编写模块并使用{\tt import},确保你写的模块名不同于Python标准模块名。
\index{模块!reload}
\index{reload函数}
\index{函数!reload}
\item 如果你使用{\tt import}读取一个模块,记得重启解释器或使用{\tt reload}来读取一个修改过的文件。如果你再次导入模块,解释器将不做任何事。
\end{itemize}
如果你陷入困境找不到出错的原因,一个方法是从一个类似“Hello,World!”的新程序开始,确保你从一个已知的可运行的程序开始。然后逐步添加原程序中的代码到新程序。
\section{运行时错误}
当你的程序没有语法错误,Python可以编译并运行。这时可能发生什么错误呢?
\subsection{我的程序什么也没做}
这个问题通常发生在你的文件包含函数和类,但没有任何调用执行的语句。也许你故意这样,因为你仅仅打算导入这个模块来提供函数和类。
如果这不是故意的,确保你调用一个函数来开始执行,或从一个交互的命令提示行下执行。参见下面的“执行流程”章节。
\subsection{我的程序挂起了}
\index{无限循环}
\index{无限递归}
\index{挂起}
如果一个程序停止,但上去什么也没做,我们称“挂起”。通常意味着程序陷入一个无限循环或者无线递归。
\begin{itemize}
\item 如果你怀疑某个循环引起这个问题,在循环开始时添加{\tt print}语句打印“进入循环”,在循环结束时打印“exiting the loop”退出循环。
运行程序,如果你的到第一条消息,但没有第二条消息,你得到一个无限循环。参见“无限循环”章节。
\item 大多数时候,无限循环会令程序运行一段时间,然后产生“RuntimeError: Maximum recursion depth exceeded”的错误。如果是这样,参考下面的“无限递归”章节。
如果你没有得到这样的错误信息,但你怀疑某个递归方法或函数有问题,你仍可以使用“无限递归”章节中提到的方法。
\item 如果这些方法都没有,尝试测试其他的循环、递归的函数和方法。
\item 如果还是没有效果,有可能你不清楚程序执行的流程。参考下面的“执行流程”章节。
\end{itemize}
\subsubsection{无限循环}
\index{无限循环}
\index{循环!无限}
\index{条件}
\index{循环!条件}
如果你有一个无限循环并且你认为这个循环导致了问题,在循环体结束的地方添加{\tt print}语句打印条件语句中的变量值和条件值。
例如:
\beforeverb
\begin{verbatim}
while x > 0 and y < 0 :
# do something to x
# do something to y
print "x: ", x
print "y: ", y
print "condition: ", (x > 0 and y < 0)
\end{verbatim}
\afterverb
%
现在当你运行程序,每个循环你都会看到3行输出。对于最后一次循环,条件应该为{\tt false}。如果循环不断执行,你可以看到{\tt x}和{\tt y}的值,也许能够发现为什么它们没有被正确的更新。
\subsubsection{无限递归}
\index{无限递归}
\index{递归!无限}
大多数时候,无限循环会令程序运行一段时间,然后产生“RuntimeError: Maximum recursion depth exceeded”的错误。
如果你怀疑一个函数或方法导致了无限递归,首先检查存在一个基本状态。换言之,应该有一个状态使函数或方法直接返回,而不是再次递归调用。如果不是,你需要重新思考算法,并设计一个基本状态。
如果存在一个基本状态,但程序似乎没有运行到这个状态,你可以在函数或方法开始的部分添加{\tt print}语句打印参数。现在当你运行程序,每次函数或方法被调用时,你将看到几行关于参数的输出。如果参数没有向基本状态靠拢,你也许会发现为什么这样。
\subsubsection{执行流程}
\index{执行流程}
如果你不确定程序的执行流程,在每个函数开始的地方加入{\tt print}语句,打印类似“entering function {\tt foo}”的语句,其中{\tt foo}是函数的名字。
现在当你运行程序,它将打印每个函数被调用的追踪。
\subsection{当我运行程序,我得到一个异常}
\index{异常}
\index{运行时错误}
有时程序运行时出错,Python将打印异常的名字、问题发生的位置和回溯。
\index{回溯}
回溯识别当前运行的函数,然后识别调用该函数的函数,以此类推。换言之,它追踪了你到达这个错误所经过的函数调用。同时它也包括这些调用在文件中的位置。
第一步是检查代码中对应的出错的位置,或许你能发现错误。下面是常见的运行时错误:
\begin{description}
\item[名字错误:] 你试图使用一个不在当前环境下的变量。记住局部变量是局部的,你不能在定义它们的函数的外部引用它们。
\index{名字错误}
\index{类型错误}
\index{异常!名字错误}
\index{异常!类型错误}
\item[类型错误:] 可能的几个原因是:
\begin{itemize}
\item 你试图不适当的使用值。例如:使用一个非整数的值作为字符串、列表或元组的下标。
\index{下标}
\item 格式字符串和用于转换的对象不匹配。这发生在或者数量不相同,或者调用了一个非法的转换。
\index{格式运算符}
\index{运算符!格式}
\item 你调用函数或方法时传递的参数个数有误。对于方法,检查方法定义,确保第一个参数是{\tt self},然后检查方法调用,确保你对一个对象调用方法,并正确的提供其他参数。
\end{itemize}
\item[键错误:] 你试图使用字典中不存在的键访问对应的元素。
\index{键错误}
\index{异常!键错误}
\index{字典}
\item[属性错误:] 你试图访问一个不存在的属性或方法。检查拼写!你可以使用{\tt dir}来列举存在的属性。
如果属性错误指出一个对象是{\tt 空类型},及它是{\tt 空}的。一个常见的原因是函数结束时忘记返回值。如果在返回尾部没有{\tt return}语句,函数将返回一个{\tt None}。另一个原因是使用类似列表中的{\tt sort}方法,它返回{\tt None}。
\index{属性错误}
\index{异常!属性错误}
\item[下标错误:] 用来访问列表、字符串或元组的下标超过长度减1。在错误发生的前一行使用{\tt print}语句显示下标值和序列的长度,检查两个值是否正确。
\index{下标错误}
\index{异常!下标错误}
\end{description}
\index{调试器(pdb)}
\index{Python调试器(pdb)}
\index{pdb(Python调试器)}
Python调试器({\tt pdb})允许你在错误前检查程序状态,有助于追踪异常。你可以在 \url{docs.python.org/lib/module-pdb.html}阅读有关{\tt pdb}的内容。
\subsection{我添加了太多的{\tt print}语句,输出令我应接不暇}
\index{print语句}
\index{语句!print}
使用{\tt print}语句调试的一个问题是你会被大量的输出信息掩埋。有两个处理方法:简化输出或简化程序。
简化输出可以删除或注释不用的{\tt print}语句,或将它们合并,或格式化输出使得它们容易理解。
简化程序你可以做一下几件事。首先缩小程序处理的问题的规模。例如,如果你在搜索一个列表,搜索一个{\em 小的}列表。如果程序从用户读取输入,输入最简单的参数。
\index{死区代码}
第二,整理程序,删除死区代码,使程序易读。例如,如果你认为问题深嵌在程序中,试图用简单的结构重写那部分。如果你怀疑一个大函数有错误,试图将它分割成小函数,然后分别测试。
\index{测试!最小测试案例}
\index{测试案例,最小}
通常寻找最小测试案例的过程让你找到错误。如果你发现一个程序对于一个情况适用,对另一个情况不使用,这将给你一些线索。
同样,重写一段代码有助于发现一些微小的错我。如果你做了一个你认为不会影响程序的修改,但事实上影响了,这个方法可以给你警戒。
\section{语义错误}
\index{语义错误}
\index{错误!语义}
在某种程度上,语义错误时最难调试的,因为解释器不能提供任何错误信息。你所知道的只有程序应该怎么做。
第一步是建立程序代码和你见到的行为之间的联系。你需要假设程序实际上做了什么。一个主要困难在于计算器运行的太快了。
你常希望你可以降低程序的速度,是人类可以跟上,通过使用调试器,你可以实现这步。但是在关键地方加入一些{\tt print}语句所用的时间通常短于建立调试器,加入删除断点,然后“逐步”运行程序直到错误发生。
\subsection{我的程序不工作}
你需要问自己这些问题:
\begin{itemize}
\item 有没有什么程序应该做却没有发生?找到执行该函数的代码段,确保程序被执行。
\item 有没有什么不应该发生的发生了?找到执行该函数的代码段,查看它是否执行了?
\item 有没有代码的执行效果与你期望的不同?确保你理解有问题的代码,尤其是包含调用其他Python模块中的函数或方法。阅读你调用的函数的文档,用一些简单的例子进行测试。
\end{itemize}
在编程时,你心中需要有一个关于程序如何工作的模型。如果你的程序没有按照你期望的工作,很可能问题不在于程序,而在于你心中的模型。
\index{模型,心中的}
\index{心中的模型}
修正你心中的模型的最好的方法是将程序分割为不同部分(通常是函数和方法),并分别测试。一旦你发现了模型和现实的差异,你就可以解决问题。
当然,在开发的过程中尼需要建立并测试组件。如果你遇到了问题,只有一小部分新的代码是不确定正确性的。
\subsection{我写了一个很长的表达式,它没有按照我期望的工作}
\index{表达式!大而复杂}
\index{大而复杂的表达式}
编写复杂的表达式是合理的,如果它们可读。但是它们调试起来很困难。通常我们将一个复杂的表达式分割成一系列的临时变量的赋值。
例如:
\beforeverb
\begin{verbatim}
self.hands[i].addCard(self.hands[self.findNeighbor(i)].popCard())
\end{verbatim}
\afterverb
%
可以重写为:
\beforeverb
\begin{verbatim}
neighbor = self.findNeighbor(i)
pickedCard = self.hands[neighbor].popCard()
self.hands[i].addCard(pickedCard)
\end{verbatim}
\afterverb
%
这个明晰的版本更适于阅读,因为变量名提供了额外的文档,同时调试也更容易,因为你可以检查中间变量的类型和它们的值。
\index{临时变量}
\index{变量!临时}
\index{运算符优先级}
\index{优先级}
大的表达式的另一个问题是计算的顺序不一定是你期望的。例如,如果你将表达式$\frac{x}{2 \pi}$翻译为Python,你也许会写成:
\beforeverb
\begin{verbatim}
y = x / 2 * math.pi
\end{verbatim}
\afterverb
%
这是不正确的,因为乘法和除法有相同的优先级,因此是从左往右计算的,这个表达式计算的是$x \pi / 2$。
调试表达式的一个好的方法是添加括号,使得计算顺序简洁明了:
\beforeverb
\begin{verbatim}
y = x / (2 * math.pi)
\end{verbatim}
\afterverb
%
当你不确定计算优先级是,使用括号。不仅程序将工作正常(按你的要求执行),同时也让那些没有记住优先级规则的人阅读起来更方便。
\subsection{我的函数或方法没有按照我期望的返回}
\index{return语句}
\index{语句!return}
如果你的{\tt return}语句包含一个复杂的表达式,你没有机会在返回前打印{\tt 返回}值。同样你可以使用临时变量。例如:对于
\beforeverb
\begin{verbatim}
return self.hands[i].removeMatches()
\end{verbatim}
\afterverb
%
你可以写成:
\beforeverb
\begin{verbatim}
count = self.hands[i].removeMatches()
return count
\end{verbatim}
\afterverb
%
现在你在返回前可以打印{\tt count}的值。
\subsection{我实在是卡住了,我需要帮助}
第一,尝试离开电脑几分钟。电脑辐射会对大脑产生影响,导致下列几种症状:
\begin{itemize}
\item 沮丧和愤怒
\index{沮丧}
\index{愤怒}
\index{调试!情绪反应}
\index{带情绪的调试}
\item 迷信的人认为“电脑讨厌我”,并神奇的相信“程序仅当我向后戴着帽子时才工作正常”。
\index{调试!迷信}
\index{迷信的调试}
\item 随机漫步编程(用各种可能的方法编程,并选择工作正常的那个)。
\index{随机漫步编程}
\index{开发计划!随机漫步编程}
\end{itemize}
如果你发现你有以上任意一种症状,站起来走一走。当你心绪平静时,思考一下程序。它是做什么的?什么肯能造成了这种行为?上次可以工作的程序是什么时候?下一步做什么?
有时找到一个错误很费时间。我常常在我离开电脑,让思维游荡的时候找到错误。一些找到错误最好的地方有火车上,浴室里,以及临睡前。
\subsection{不,我真的需要帮助}
即使最好的程序员也会卡住。有时你在一个程序上工作了太长的时间,因此你难以发现错误。而他人可能一眼就发现问题。
在你向其他人寻求帮助前,你需要做好准备。你的程序需要尽可能简洁,你需要最少的输入来重现错误。你需要在合适的位置加入{\tt print}语句,同时输出应可理解。你需要能够以简洁的语言描述问题。
当你想某人求助,你需要提供足够的信息:
\begin{itemize}
\item 是否有出错消息?它是什么?指向程序的哪部分?
\item 错误出现前你做的最后一步是什么?你写的最后几行是什么?什么新的测试导致了错误?
\item 你做了哪些尝试?你学到了什么?
\end{itemize}
当你找到了错误,花时间想一想你怎么能更快的定位它。下一次你遇到类似的问题,你就可以更快的找到问题。
记住,目标不仅仅是让程序工作,而是学会如何让程序工作。
\printindex
\clearemptydoublepage