在本章中,我们将介绍以下配方:
- 学习 Python 解释器基础知识
- 通过日志进行调试
- 使用断点进行调试
- 提高调试技能
编写代码并不容易。事实上,这很难。即使是世界上最好的程序员也无法预见任何可能的替代方案和代码流
这意味着执行我们的代码总是会产生惊喜和意外的行为。有些是非常明显的,有些是非常微妙的,但识别和删除代码中这些缺陷的能力对于构建可靠的软件至关重要。
软件中的这些缺陷被称为Bug,因此消除它们被称为调试。
仅仅通过阅读来检查代码是不好的。总是有惊喜,复杂的代码很难理解。这就是为什么通过停止执行和查看事物的当前状态来进行调试的能力很重要。
Everyone, EVERYONE introduces bugs in the code, normally to be surprised by them later. Some people have described debugging as being the detective in a crime movie where you are also the murderer.
任何调试过程大致遵循以下路径:
- 你知道有个问题吗
- 你知道正确的行为应该是什么
- 您将发现当前代码产生错误的原因
- 您可以更改代码以生成正确的结果
95%的时间里,除了步骤 3 之外的所有事情都是琐碎的,这是调试过程的主要部分
要了解 bug 的核心原因,需要使用科学方法:
- 测量并观察代码正在执行的操作
- 提出一个关于为什么会这样的假设
- 验证或反驳这是正确的,也许通过一个实验
- 使用生成的信息来迭代流程
调试是一种能力,因此,它会随着时间的推移而改进。实践在培养直觉方面起着重要作用,直觉指出哪些路径有可能识别错误,但有一些一般性的想法可以帮助您:
- **分而治之:**隔离代码的小部分,以便理解代码。尽量简化问题。
有一种叫做狼篱笆算法的格式,由 Eduard Gauss 描述:
"There's one wolf in Alaska; how do you find it? First build a fence down the middle of the state, wait for the wolf to howl, determine which side of the fence it is on. Repeat process on that side only, until you get to the point where you can see the wolf."
-
**从错误中向后移动:**如果在某个特定点有明显错误,则该错误可能位于周围。从错误中逐渐向后移动,沿着轨迹,直到找到错误源。
-
**只要你证明你的假设,你就可以假设任何你想要的东西:**代码非常复杂,很难一下子就记住。您需要验证一些小假设,当这些假设结合在一起时,将为检测和解决问题提供坚实的基础。做一些小的实验,这将允许你从脑海中删除部分代码,这些代码实际上可以工作,并专注于未经测试的代码。
或者用福尔摩斯的话说:
"Once you eliminate the impossible, whatever remains, no matter how improbable, must be the truth."
但记住要证明这一点。避免未经检验的假设。
All of this sounds a bit scary, but actually most of the bugs are pretty evident. Maybe a typo, or a piece of code not ready for a particular value. Try to keep things simple. Simple code is easier to analyze and debug.
在本章中,我们将看到一些用于调试的工具和技术,并将它们专门应用于 Python 脚本。这些脚本将有一些 bug,我们将作为配方的一部分进行修复。
在本教程中,我们将介绍 Python 的一些内置功能,这些功能用于检查代码、调查发生了什么,以及检测什么时候行为不正常。
We can also verify when things are working as expected. Remember that being able to discard part of the code as the source of a bug is incredibly important.
调试时,我们通常需要分析来自外部模块或服务的未知元素和对象。鉴于 Python 的动态特性,代码在执行过程中的任何时候都是高度可发现的。
默认情况下,此配方中的所有内容都包含在 Python 的解释器中。
- 进口
pprint:
>>> from pprint import pprint- 创建一个名为
dictionary的新词典:
>>> dictionary = {'example': 1}- 在此环境中显示
globals:
>>> globals()
{...'pprint': <function pprint at 0x100995048>,
...'dictionary': {'example': 1}}- 使用
pprint以可读格式打印globals字典:
>>> pprint(globals())
{'__annotations__': {},
...
'dictionary': {'example': 1},
'pprint': <function pprint at 0x100995048>}- 显示
dictionary的所有属性:
>>> dir(dictionary)
['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']- 显示
dictionary对象的帮助:
>>> help(dictionary)
Help on dict object:
class dict(object)
| dict() -> new empty dictionary
| dict(mapping) -> new dictionary initialized from a mapping object's
| (key, value) pairs
...在步骤 1 中导入pprint(pretty print)之后,我们创建了一个新的字典,作为步骤 2 中的示例。
步骤 3 显示了全局名称空间如何包含定义的字典和模块。globals()显示所有导入模块和其他全局变量。
There's an equivalent locals() for local namespaces.
pprint有助于在步骤 4 中以更可读的格式显示globals,增加更多空间,并按行分隔元素。
步骤 5 展示了如何使用dir()获取 Python 对象的所有属性。注:这包括所有双下划线值,如__len__。
使用内置的help()功能将显示对象的相关信息。
dir()特别适用于检查未知对象、模块或类。如果需要过滤掉默认属性并澄清输出,可以通过以下方式过滤输出:
>>> [att for att in dir(dictionary) if not att.startswith('__')]
['clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']同样,如果您正在搜索一个特定的方法(例如以set开头的方法),您可以用同样的方法进行过滤。
help()将显示函数或类的docstring。docstring是在定义之后定义的字符串,用于记录函数或类:
>>> def something():
... '''
... This is help for something
... '''
... pass
...
>>> help(something)
Help on function something in module __main__:
something()
This is help for something请注意,在下一个示例中,这是对字符串的帮助,它是在函数定义之后定义的。
docstring is normally enclosed in triple quotes to allow writing string with multiple lines. Python will treat everything inside triple-quotes as a big string, even if there are newlines. You can use either ' or " characters, as long as you use three of them. You can find more information about docstrings at https://www.python.org/dev/peps/pep-0257/.
内置功能的文档可在中找到 https://docs.python.org/3/library/functions.html#built-在功能中,可在中找到pprint的完整文档 https://docs.python.org/3/library/pprint.html# 。
- 提高调试技能配方
- 通过记录进行调试
毕竟,调试是检测程序内部发生的事情以及可能发生的意外或不正确的影响。一种简单但非常有效的方法是在代码的关键部分输出变量和其他信息,以遵循程序流程。
这种方法最简单的形式称为打印调试,或者在调试时在某些点插入打印语句来打印变量或点的值。
但将这项技术再进一步,结合第 2 章中介绍的日志记录技术,使任务自动化变得简单允许我们创建程序执行的半永久性跟踪,这在检测运行程序中的问题时非常有用。
从 GitHub 下载debug_logging.py文件:https://github.com/PacktPublishing/Python-Automation-Cookbook/blob/master/Chapter10/debug_logging.py 。
它包含气泡排序算法(的一个实现 https://www.studytonight.com/data-structures/bubble-sort ),这是对元素列表进行排序的最简单方法。它在列表上迭代数次,每次迭代时,检查并交换两个相邻的值,因此较大的值在较小的值之后。这使得较大的值像列表中的气泡一样上升。
Bubble sort is a simple but naive way of implementing a sort, and there are better alternatives. Unless you have an extremely good reason not to, rely on the standard .sort method in lists.
运行时,它会检查以下列表以验证其正确性:
assert [1, 2, 3, 4, 7, 10] == bubble_sort([3, 7, 10, 2, 4, 1])我们在这个实现中有一个 bug,所以我们可以将它作为配方的一部分来修复!
- 运行
debug_logging.py脚本,检查是否失败:
$ python debug_logging.py
INFO:Sorting the list: [3, 7, 10, 2, 4, 1]
INFO:Sorted list: [2, 3, 4, 7, 10, 1]
Traceback (most recent call last):
File "debug_logging.py", line 17, in <module>
assert [1, 2, 3, 4, 7, 10] == bubble_sort([3, 7, 10, 2, 4, 1])
AssertionError- 启用 dubug 日志记录,更改
debug_logging.py脚本的第二行:
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.INFO)将前一行更改为下一行:
logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.DEBUG)注意不同的level。
- 再次运行脚本,其中包含更多信息:
$ python debug_logging.py
INFO:Sorting the list: [3, 7, 10, 2, 4, 1]
DEBUG:alist: [3, 7, 10, 2, 4, 1]
DEBUG:alist: [3, 7, 10, 2, 4, 1]
DEBUG:alist: [3, 7, 2, 10, 4, 1]
DEBUG:alist: [3, 7, 2, 4, 10, 1]
DEBUG:alist: [3, 7, 2, 4, 10, 1]
DEBUG:alist: [3, 2, 7, 4, 10, 1]
DEBUG:alist: [3, 2, 4, 7, 10, 1]
DEBUG:alist: [2, 3, 4, 7, 10, 1]
DEBUG:alist: [2, 3, 4, 7, 10, 1]
DEBUG:alist: [2, 3, 4, 7, 10, 1]
INFO:Sorted list : [2, 3, 4, 7, 10, 1]
Traceback (most recent call last):
File "debug_logging.py", line 17, in <module>
assert [1, 2, 3, 4, 7, 10] == bubble_sort([3, 7, 10, 2, 4, 1])
AssertionError- 分析输出后,我们发现列表的最后一个元素没有排序。我们分析代码并在第 7 行中发现一个 off-by-one 错误。你看到了吗?让我们通过更改以下行来修复它:
for passnum in reversed(range(len(alist) - 1)):将前一行更改为下一行:
for passnum in reversed(range(len(alist))):(注意删除了-1操作。)
- 再次运行它,您将看到它按预期工作。此处不显示调试日志:
$ python debug_logging.py
INFO:Sorting the list: [3, 7, 10, 2, 4, 1]
...
INFO:Sorted list : [1, 2, 3, 4, 7, 10]步骤 1 显示脚本并显示代码有错误,因为它没有正确地对列表排序。
脚本已经有一些日志显示开始和结束结果,还有一些调试日志显示每个中间步骤。在步骤 2 中,我们激活了DEBUG日志的显示,与步骤 1 一样,只显示INFO日志。
Note that the logs are displayed by default in the standard error output. This is displayed by default in the Terminal. If you need to direct the logs somewhere else, such as a file, see how to configure a different handler. See the logging configuration in Python for more details: https://docs.python.org/3/howto/logging.html.
步骤 3 再次运行脚本,这次显示额外的信息,表明列表中的最后一个元素没有排序。
这个错误是一个逐个错误,一种非常常见的错误,因为它应该迭代到整个列表的大小。这在步骤 4 中已修复。
Check the code to understand why there's an error. The whole list should be compared, but we made the mistake of reducing the size by one.
步骤 5 显示修复脚本正确运行。
在这个方法中,我们事先就策略性地定位了调试日志,但在实际调试实践中可能并非如此。作为 bug 调查的一部分,您可能需要添加更多或更改位置。
这种技术的最大优点是,我们能够看到程序的流程,能够从一个时刻到另一个时刻检查代码执行情况,并理解流程。但缺点是,我们可以以一堵没有提供问题具体信息的文字墙结束。你需要在太多和太少的信息之间找到平衡。
出于同样的原因,尝试限制非常长的变量,除非它们是必要的。
请记住在修复错误后降低日志记录级别。可能需要删除一些您发现不相关的日志。
The quick and dirty version of this technique is to add print statements instead of debug logs. While some people are resistant to this, it is actually a valuable technique to use for debug purposes. But remember to clean them up when you're done.
所有内省元素都可用,因此您可以创建日志,以显示dir(object)对象的所有属性:
logging.debug(f'object {dir(object)}')任何可以显示为字符串的内容都可以显示在日志中,包括任何文本操作。
- 学习 Python 解释器基础配方
- 提高调试技能配方
Python 有一个名为pdb的现成调试器。鉴于 Python 代码是被解释的,这意味着可以通过设置断点在任意点停止代码的执行,断点将跳入命令行,在命令行中可以使用任何代码分析情况并执行任意数量的指令。
让我们看看怎么做。
下载debug_algorithm.py脚本,可从 GitHub 获得:https://github.com/PacktPublishing/Python-Automation-Cookbook/blob/master/Chapter10/debug_algorithm.py 。
在下一节中,我们将详细分析代码的执行。该代码检查数字是否遵循某些属性:
def valid(candidate):
if candidate <= 1:
return False
lower = candidate - 1
while lower > 1:
if candidate / lower == candidate // lower:
return False
lower -= 1
return True
assert not valid(1)
assert valid(3)
assert not valid(15)
assert not valid(18)
assert not valid(50)
assert valid(53)您可能知道代码在做什么,但请耐心听我说,这样我们就可以交互地分析代码了。
- 运行代码以查看所有断言是否有效:
$ python debug_algorithm.py- 在
while循环之后,第 7 行之前添加breakpoint(),结果如下:
while lower > 1:
breakpoint()
if candidate / lower == candidate // lower:- 再次执行代码,看到代码在断点处停止,进入交互
Pdb模式:
$ python debug_algorithm.py
> .../debug_algorithm.py(8)valid()
-> if candidate / lower == candidate // lower:
(Pdb)- 检查候选值和两个操作。此行检查
candidate除以lower是否为整数(浮点与整数除法相同):
(Pdb) candidate
3
(Pdb) candidate / lower
1.5
(Pdb) candidate // lower
1- 使用
n继续下一条指令。看到它结束 while 循环并返回True:
(Pdb) n
> ...debug_algorithm.py(10)valid()
-> lower -= 1
(Pdb) n
> ...debug_algorithm.py(6)valid()
-> while lower > 1:
(Pdb) n
> ...debug_algorithm.py(12)valid()
-> return True
(Pdb) n
--Return--
> ...debug_algorithm.py(12)valid()->True
-> return True- 继续执行,直到找到另一个带有
c的断点。请注意,这是对valid()的下一个调用,其中有 15 个作为输入:
(Pdb) c
> ...debug_algorithm.py(8)valid()
-> if candidate / lower == candidate // lower:
(Pdb) candidate
15
(Pdb) lower
14- 继续运行并检查数字,直到
valid函数所做的事情有意义为止。你能找出代码的作用吗?(如果不能,不要担心,检查下一节。)完成后,用q退出。这将停止执行:
(Pdb) q
...
bdb.BdbQuit正如您可能已经知道的,代码是检查一个数字是否是素数。它尝试将该数字除以所有小于该数字的整数。如果在任何一点是可除的,它返回一个False结果,因为它不是素数。
This is actually a very inefficient way of checking for a prime number, as it will take a very long time to deal with big numbers. It is fast enough for our teaching purposes, though. If you're interested in finding primes, you can take a look at math packages such as SymPy (https://docs.sympy.org/latest/modules/ntheory.html?highlight=prime#sympy.ntheory.primetest.isprime).
在检查了步骤 1 中的一般执行之后,在步骤 2 中,我们在代码中引入了一个breakpoint。
在步骤 3 执行代码时,代码停止在breakpoint位置,进入交互模式。
在交互模式下,我们可以检查任何变量的值以及执行任何类型的操作。如步骤 4 所示,有时,通过复制代码行的各个部分,可以更好地分析代码行。
可以在命令行中检查代码并执行常规操作。下一行代码可以通过多次调用n(ext)来执行,如步骤 5 所述,以查看代码流。
步骤 6 显示了如何按顺序恢复执行c(ontinue)命令,以便在下一个断点停止。所有这些操作都可以进行迭代,以查看流和值,并了解代码在任何时候都在做什么。
可以使用q(uit)停止执行,如步骤 7 所示。
要查看所有可用的操作,您可以随时调用h(elp)。
您可以使用l(ist)命令随时检查周围的代码。例如,在步骤 4 中:
(Pdb) l
3 return False
4
5 lower = candidate - 1
6 while lower > 1:
7 breakpoint()
8 -> if candidate / lower == candidate // lower:
9 return False
10 lower -= 1
11
12 return True其他两个主要的调试器命令是s(tep),它将执行下一步,包括输入一个新调用;以及r(eturn),它将从当前函数返回。
You can set up (and disable) more breakpoints using the pdb command b(reak). You need to specify the file and line for the breakpoint, but it's actually more straightforward and less error-prone to just change the code and run it again.
您可以覆盖变量,也可以读取变量。或者创建新的变量。或者打额外的电话。或者任何你能想象到的东西。Python 解释器的全部功能将为您服务!使用它来检查某件事是如何工作的,或者验证是否发生了某件事。
Avoid creating variables with names that are reserved for the debugger, such as calling a list l. It will make things confusing and interfere when trying to debug, sometimes in non-obvious ways.
breakpoint()函数在 Python3.7 中是新的,但如果您使用的是该版本,则强烈建议使用该函数。在以前的版本中,需要将其替换为以下内容:
import pdb; pdb.set_trace()它们的工作方式完全相同。请注意同一行中的两条语句,这在 Python 中通常是不推荐的,但这是将断点保留在一行中的好方法。
Remember to remove any breakpoints once debugging is done! Especially when committing to a version-control system such as Git.
您可以在官方政治公众人物中阅读更多关于新的breakpoint电话的信息,描述其用法:https://www.python.org/dev/peps/pep-0553/ 。
完整的pdb文档可在此处找到:https://docs.python.org/3.7/library/pdb.html#module-pdb。它包括所有调试命令。
- 学习 Python 解释器基础配方
- 提高调试技能配方
在本食谱中,我们将分析一个小脚本,该脚本复制对外部服务的调用,对其进行分析并修复一些 bug。我们将展示不同的技术来改进调试。
该脚本将把一些个人姓名 ping 到一个互联网服务器(httpbin.org,一个测试站点)上以取回这些姓名,模拟从外部服务器检索这些姓名。然后,它会将他们分为名字和姓氏,并准备按姓氏排序。最后,它将对它们进行排序。
该脚本包含几个我们将检测和修复的 bug。
对于这个配方,我们将使用requests和parse模块,并将它们包括在我们的虚拟环境中:
$ echo "requests==2.18.3" >> requirements.txt
$ echo "parse==1.8.2" >> requirements.txt
$ pip install -r requirements.txtdebug_skills.py脚本可从 GitHub 获得:https://github.com/PacktPublishing/Python-Automation-Cookbook/blob/master/Chapter10/debug_skills.py 。请注意,它包含我们将作为此配方的一部分修复的错误。
- 运行脚本,它将生成一个错误:
$ python debug_skills.py
Traceback (most recent call last):
File "debug_skills.py", line 26, in <module>
raise Exception(f'Error accessing server: {result}')
Exception: Error accessing server: <Response [405]>- 分析状态代码。我们得到 405,这意味着我们发送的方法是不允许的。我们检查了代码,发现对于第 24 行中的调用,我们使用了
GET,而正确的调用是POST(如 URL 中所述)。将代码替换为以下内容:
# ERROR Step 2\. Using .get when it should be .post
# (old) result = requests.get('http://httpbin.org/post', json=data)
result = requests.post('http://httpbin.org/post', json=data)为了清晰起见,我们用(old)注释了旧的错误代码。
- 再次运行代码,这将产生不同的错误:
$ python debug_skills.py
Traceback (most recent call last):
File "debug_skills_solved.py", line 34, in <module>
first_name, last_name = full_name.split()
ValueError: too many values to unpack (expected 2)- 在错误前面的第 33 行插入断点。再次运行并进入调试模式:
$ python debug_skills_solved.py
..debug_skills.py(35)<module>()
-> first_name, last_name = full_name.split()
(Pdb) n
> ...debug_skills.py(36)<module>()
-> ready_name = f'{last_name}, {first_name}'
(Pdb) c
> ...debug_skills.py(34)<module>()
-> breakpoint()运行n不会产生错误,这意味着它不是第一个值。在 c上运行了几次之后,我们意识到这不是正确的方法,因为我们不知道产生错误的输入是什么。
- 相反,我们用一个
try...except块来包装该行,并在该点生成一个断点:
try:
first_name, last_name = full_name.split()
except:
breakpoint()- 我们再次运行代码。这一次,代码在数据产生错误时停止:
$ python debug_skills.py
> ...debug_skills.py(38)<module>()
-> ready_name = f'{last_name}, {first_name}'
(Pdb) full_name
'John Paul Smith'- 原因现在很清楚了,第 35 行只允许我们拆分两个单词,但如果添加了中间名,则会引发错误。经过一些测试后,我们进入这一行进行修复:
# ERROR Step 6 split only two words. Some names has middle names
# (old) first_name, last_name = full_name.split()
first_name, last_name = full_name.rsplit(maxsplit=1)- 我们再次运行脚本。一定要删除
breakpoint和try..except块。这一次,它会生成一个名称列表!他们按姓氏的字母顺序排列。但是,有几个名称看起来不正确:
$ python debug_skills_solved.py
['Berg, Keagan', 'Cordova, Mai', 'Craig, Michael', 'Garc\\u00eda, Roc\\u00edo', 'Mccabe, Fathima', "O'Carroll, S\\u00e9amus", 'Pate, Poppy-Mae', 'Rennie, Vivienne', 'Smith, John Paul', 'Smyth, John', 'Sullivan, Roman']谁叫O'Carroll, S\\u00e9amus?
- 为了分析这个特殊的情况,但跳过其余的情况,我们必须在第 33 行中创建一个
if条件,只中断该名称。注意in以避免完全正确:
full_name = parse.search('"custname": "{name}"', raw_result)['name']
if "O'Carroll" in full_name:
breakpoint()- 再次运行脚本。断点在适当的时刻停止:
$ python debug_skills.py
> debug_skills.py(38)<module>()
-> first_name, last_name = full_name.rsplit(maxsplit=1)
(Pdb) full_name
"S\\u00e9amus O'Carroll"- 在代码中向上移动并检查不同的变量:
(Pdb) full_name
"S\\u00e9amus O'Carroll"
(Pdb) raw_result
'{"custname": "S\\u00e9amus O\'Carroll"}'
(Pdb) result.json()
{'args': {}, 'data': '{"custname": "S\\u00e9amus O\'Carroll"}', 'files': {}, 'form': {}, 'headers': {'Accept': '*/*', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'close', 'Content-Length': '37', 'Content-Type': 'application/json', 'Host': 'httpbin.org', 'User-Agent': 'python-requests/2.18.3'}, 'json': {'custname': "Séamus O'Carroll"}, 'origin': '89.100.17.159', 'url': 'http://httpbin.org/post'}- 在
result.json()字典中,实际上有一个不同的字段似乎正确地呈现了名称,它被称为'json'。让我们详细看一下,;我们可以看到这是一本字典:
(Pdb) result.json()['json']
{'custname': "Séamus O'Carroll"}
(Pdb) type(result.json()['json'])
<class 'dict'>- 更改代码,而不是解析
'data'中的原始值,直接使用结果中的'json'字段。这简化了代码,这太棒了!
# ERROR Step 11\. Obtain the value from a raw value. Use
# the decoded JSON instead
# raw_result = result.json()['data']
# Extract the name from the result
# full_name = parse.search('"custname": "{name}"', raw_result)['name']
raw_result = result.json()['json']
full_name = raw_result['custname']- 再次运行代码。记住移除
breakpoint:
$ python debug_skills.py
['Berg, Keagan', 'Cordova, Mai', 'Craig, Michael', 'García, Rocío', 'Mccabe, Fathima', "O'Carroll, Séamus", 'Pate, Poppy-Mae', 'Rennie, Vivienne', 'Smith, John Paul', 'Smyth, John', 'Sullivan, Roman']这一次,一切都是正确的!您已成功调试该程序!
配方的结构分为三个不同的问题。让我们分小块进行分析:
- 第一次错误错误呼叫外部服务:
在步骤 1 中显示第一个错误之后,我们仔细阅读了结果错误,表示服务器正在返回 405 状态代码。这对应于一个不允许的方法,表明我们的调用方法不正确。
检查以下线路:
result = requests.get('http://httpbin.org/post', json=data)它指示我们正在使用GET调用为POST定义的一个 URL,因此我们在步骤 2 中进行更改。
Notice that there has been no extra debugging steps as such in this error, but a careful reading of the error message and the code. Remember to pay attention to error messages and logs. Often, this is enough to discover the issue.
我们在步骤 3 中运行代码以查找下一个问题。
- 第二个错误中间名错误处理:
在步骤 3 中,我们得到了一个错误,该错误的值太多,无法解包。此时,我们创建一个breakpoint来分析步骤 4 中的数据,但发现并非所有数据都会产生此错误。步骤 4 中所做的分析表明,在没有产生错误时停止执行可能会非常混乱,必须继续执行,直到产生错误为止。我们知道在这一点上会产生误差,但仅限于某些类型的数据。
由于我们知道错误在某个点产生,我们在步骤 5 的try..except块中捕获它。当产生异常时,我们触发breakpoint。
这使得第 6 步脚本的执行在full_name为'John Paul Smith'时停止。这会产生一个错误,因为split需要两个元素,而不是三个。
这在步骤 7 中得到了修复,允许除最后一个单词以外的所有内容都成为名字的一部分,将任何中间名分组到第一个元素中。这符合我们这个程序的目的,按姓氏排序。
Names are actually quite complex to handle. Check out this article if you want to be delighted with the great amount of wrong assumptions one can make regarding names: https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/.
下一行与rsplit相同:
first_name, last_name = full_name.rsplit(maxsplit=1)它将文本按单词分割,从右边开始,最多进行一次分割,保证只返回两个元素。
当代码更改时,步骤 8 再次运行代码以发现下一个错误。
- 外部服务使用错误返回值的第三个错误:
在步骤 8 中运行代码将显示列表,并且不会产生任何错误。但是,通过检查结果,我们可以看到一些名称被错误处理。
我们在步骤 9 中选择一个示例并创建一个条件断点。我们仅在数据满足if条件时激活breakpoint。
The if condition in this case stops at any time the "O'Carroll" string appears, not having to make it stricter with an equal statement. Be pragmatic about this code, as you'll need to remove it after the bug is fixed anyway.
在步骤 10 中再次运行代码。从那里,一旦确认数据如预期的那样,我们就反向工作以找到问题的根源。步骤 11 分析之前的值和代码,试图找出导致错误值的原因。
然后,我们发现我们在服务器的result返回值中使用了错误的字段。json字段中的值更适合此任务,我们已经对其进行了分析。步骤 12 检查该值并查看应如何使用该值。
在步骤 13 中,我们更改代码以进行调整。请注意,parse模块不再需要,并且使用json字段时代码实际上更干净。
This result is actually more common than it looks, especially when dealing with external interfaces. We may use it in a way that works, but maybe it's not the best. Take a little bit of time to read the documentation and keep an eye on improvements and learn how to better use the tools.
修复后,在步骤 14 中再次运行代码。最后,代码完成了预期的工作,按姓氏按字母顺序排列姓名。请注意,包含奇怪字符的另一个名称也是固定的。
固定脚本可从 GitHub 获得:https://github.com/PacktPublishing/Python-Automation-Cookbook/blob/master/Chapter10/debug_skills_fixed.py 。您可以下载它并查看其差异。
还有其他创建条件断点的方法。实际上,调试器支持创建停止的断点,但仅当满足某些条件时。如果可能,我们会发现直接使用代码更容易,因为它在运行之间是持久的,并且更容易记住和操作。您可以在 Pythonpdb文档中查看如何创建它:https://docs.python.org/3/library/pdb.html#pdbcommand-打破。
第一个错误中显示的捕获异常的断点演示了如何在代码中生成条件。只是以后要小心取下它们!
还有其他调试器具有更多的功能。例如:
ipdb(https://github.com/gotcha/ipdb :添加制表符补全和语法突出显示pudb(https://documen.tician.de/pudb/ :显示一个老式的、半图形化的、基于文本的界面,采用 90 年代早期自动显示环境变量的工具的风格web-pdb(https://pypi.org/project/web-pdb/ :打开一个 web 服务器以访问调试器的图形界面
检查他们的文档,了解如何安装和运行它们
There are more debuggers available, a search on the internet will give you more options, including Python IDEs. In any case, be aware of adding dependencies. It is always good to be able to use the default debugger.
Python 3.7 中的新断点命令允许我们使用PYTHONBREAKPOINT环境变量在调试器之间轻松地进行更改。例如:
$ PYTHONBREAKPOINT=ipdb.set_trace python my_script.py这将在代码中的任何断点上启动ipdb。您可以在breakpoint()文档中了解更多信息,请参见:https://www.python.org/dev/peps/pep-0553/#environment-变量。
An important effect on this is to disable all breakpoints by setting PYTHONBREAKPOINT=0, which is a great tool to ensure that code in production is never interrupted by a breakpoint() left by mistake.
Pythonpdb文档可以在这里找到:https://docs.python.org/3/library/pdb.html 可在找到parse模块的全部文档 https://github.com/r1chardj0n3s/parse 和的整个requests文件 http://docs.python-requests.org/en/master/ .
- 学习 Python 解释器基础配方
- 带断点的调试配方