1. FlyPython首页
  2. Python高级话题

Tips for debugging with print()

使用 print ()进行调试的提示


A machine of much printing.

If you’re embarrassed at debugging with print(), please don’t be – it’s perfectly fine!
Many bugs are easily tackled with just a few checks in the right places.
As much as I love using a debugger, I often reach for a print() statement first.

如果您对使用 print ()进行调试感到尴尬,请不要这样——它完全没有问题!只需在正确的地方进行几次检查,就可以轻松地解决许多 bug。尽管我非常喜欢使用调试器,但我经常首先使用 print ()语句。

Here are five tips to get the most out of debugging with print().

以下是使用 print ()最大限度地发挥调试作用的五个技巧。

1. Debug variables with f-strings and =

1. 带有 f 字符串和 = 的调试变量

Often we use a print() to debug the value of a variable, like:

我们经常使用 print ()来调试变量的值,比如:

>>> print("widget_count =", widget_count)
widget_count = 9001

On Python 3.8+ we can use an f-string with the = specifier to achieve the same with less typing.
This specifier prints the variable’s name, “=”, and the repr() of its value:

在 Python 3.8 + 上,我们可以使用带 = 说明符的 f-string 来实现同样的功能,只需要少量的类型输入。这个说明符输出变量的名称“ =”,以及其值的 repr () :

>>> print(f"{widget_count=}")

Less typing for the win!


We aren’t limited to variable names with =.
We can use any expression:

我们并不局限于 = 的变量名,我们可以使用任何表达式:

>>> print(f"{(widget_count / factories)=}")
(widget_count / factories)=750.0833333333334

If you prefer spaces around your =, you can add them in the f-string and they will appear in the output:

如果你喜欢在你的 = 周围使用空格,你可以把它们添加到 f-string 中,它们会出现在输出中:

>>> print(f"{(widget_count / factories) = }")
(widget_count / factories) = 750.0833333333334



3. Use rich or pprint for pretty printing

3. 使用 rich 或 pprint 进行漂亮的打印

Rich is a terminal formatting library.
It bundles many tools for prettifying terminal output and can be installed with pip install rich.

是一个终端格式化库。它捆绑了许多工具,以美化终端输出,并可以安装 pip 安装富。

Rich’s print() function is useful for debugging objects.
It neatly indents large, nested data structures, and adds syntax highlighting:

的 print ()函数对于调试对象非常有用,它巧妙地缩进了大型嵌套数据结构,并添加了语法突显:

Screenshot using rich.print()

Cool beans.


Using from rich import print replaces the builtin print() function.
This is normally safe since the Rich version is designed as a drop-in replacement, but it does mean everything passed to print() is formatted.
To preserve our application’s non-debug output exactly, we can use an import alias like from rich import print as rprint, keeping rprint() for debugging.

使用 from rich import print 替换内置的 print ()函数。这通常是安全的,因为富版本被设计为一个下拉式替换,但它确实意味着传递给 print ()的所有内容都是格式化的。为了准确地保存应用程序的非调试输出,我们可以使用一个导入别名,比如来自 rich import print 的导入别名作为 rprint,保留 rprint ()用于调试。

For more info see the Rich quick start guide.


If you’re not at liberty to install Rich, you can use Python’s pprint() function instead.
The extra “p” stands for “pretty”.
pprint() also indents data structures, albeit without colour or style:

如果不能自由安装 Rich,可以使用 Python 的 pprint ()函数。多余的“ p”代表“漂亮”。Pprint ()也缩进数据结构,尽管没有颜色或样式:

>>> from pprint import pprint
>>> pprint(luke)
{'birth_year': '19BBY',
 'films': [3, 4, 5, 6, 7, 8],
 'id': 1,
 'name': 'Luke Skywalker'}



4. Use locals() to debug all local variables

4. 使用局部变量()调试所有局部变量

Local variables are all the variables defined within the current function.
We can grab all the local variables with the locals() builtin, which returns them in a dictionary.
This is convenient for debugging several variables at once, even more so when combined with Rich or pprint.

局部变量是当前函数中定义的所有变量。我们可以使用局部变量()内置函数获取所有的局部变量,并将它们返回到字典中。这对于同时调试多个变量非常方便,当与 Rich 或 pprint 结合时更是如此。

We can use locals() like so:


from rich import print as rprint

def broken():
    numerator = 1
    denominator = 0
    rprint("👉", locals())
    return numerator / denominator

When we run this code, we see the dictionary of values before the exception:


>>> broken()
👉 {'numerator': 1, 'denominator': 0}
Traceback (most recent call last):
ZeroDivisionError: division by zero

There’s also the globals() builtin which returns all the global variables, that is, those defined at the module scope, such as imported variables and classes.
globals() is less useful for debugging since global variables don’t usually change, but it is good to know about.

还有 globals ()内置函数,它返回所有的全局变量,也就是那些在模块作用域中定义的变量,比如导入的变量和类。Globals ()对于调试的用处不大,因为全局变量通常不会发生变化,但是了解它是很有用的。

5. Use vars() to debug all of an object’s attributes

5. 使用 vars ()调试对象的所有属性

The vars() builtin returns a dictionary of an object’s attributes.
This is useful when we want to debug many attributes at once:

内置的 vars ()返回对象属性的字典。当我们想要同时调试多个属性时,这是非常有用的:

>>> rprint(vars(widget))
{'id': 1, 'name': 'Batara-Widget'}



(vars() without an argument is also equivalent to locals(), in about half the typing.)

(不带参数的 vars ()也等同于局部变量() ,大约只有一半的类型。)

vars() works by accessing the __dict__ attribute of the object.
Most Python objects have this as the dictionary of their (writeable) attributes.
We can also use __dict__ directly, although it’s a little more typing:

Vars ()通过访问对象的 _ dict _ _ 属性来工作。大多数 Python 对象都将它作为其(可写)属性的字典。我们也可以直接使用 dict,尽管它更多的是输入:

>>> rprint(widget.__dict__)
{'id': 1, 'name': 'Batara-Widget'}

vars() and __dict__ don’t work for every object.
If an object’s class uses __slots__, or it’s built in C, then it won’t have a __dict__ attribute.

Vars ()和 _ dict _ 不适用于每个对象。如果一个对象的类使用 _ slots _,或者它是内置在 c 中的,那么它就不会有 _ dict _ 属性。

6. Debug your filename and line number to make returning there easy

6. 调试你的文件名和行号,使返回更容易

Update (2021-10-09): Thanks to Lim H for the tip.

更新(2021-10-09) : 感谢 Lim h 的提示。

Many terminals allow us to open on filenames from output.
And text editors support opening files at a given line when a colon and the line number follows the filename.
For example, this output would allow us to open example.py, line 12:

许多终端允许我们从输出打开文件名。文本编辑器支持在文件名后面有冒号和行号的情况下在给定行打开文件。例如,这个输出允许我们打开 example.py,第12行:

In iTerm on macOS, we can command-click to open the file (“smart selection”).
For other terminals, check the documentation.

在 macOS 上的 iTerm 中,我们可以命令点击打开文件(“智能选择”)。

We can use this capability in our print() calls to ease reopening the code we’re trying to debug:

我们可以在 print ()调用中使用这个功能来轻松地重新打开我们试图调试的代码:

print(f"{__file__}:12 {widget_count=}")

This uses the Python magic variable __file__ to get the name of the current file.
We have to provide the line number ourselves.

这使用 Python magic 变量 _ _ file _ _ 来获取当前文件的名称。我们必须自己提供行号。

Running this we see:


$ python example.py
/Users/me/project/example.py:12 widget_count=9001

And in iTerm we can command-click the start of the line to jump right back to the broken code.

在 iTerm 中,我们可以命令——单击行的开始,然后直接跳回到破碎的代码。

I’m told that to open in VSCode specifically, you can output a link like vscode://file/<filename>.

我被告知,特别是在 VSCode 中打开时,您可以输出一个类似 VSCode://file/< filename > 的链接。

✨Bonus✨ 7. Try icecream 试试冰淇淋

Update (2021-10-09): Thanks to Malcolme Greene for reminding me of this package.

更新(2021-10-09) : 感谢 Malcolme Greene 提醒我这个包裹。

The icecream package provides a handy debugging shortcut function, ic().
This function combines some of the tools we’ve been looking at.
Called without arguments, ic() debugs details about where and when it was called:

Icecream 包提供了一个方便的调试快捷函数 ic ()。这个函数结合了我们一直在研究的一些工具。在没有参数的情况下调用 ic () ,调试它的时间和地点的详细信息:

from icecream import ic

def main():
    print("Starting main")

if __name__ == "__main__":
$ python example.py
Starting main
ic| example.py:6 in main() at 11:28:27.609

Called with arguments, ic() inspects and prints each expression (through source inspection) and its result:

通过参数调用,ic ()检查并打印每个表达式(通过源代码检查)及其结果:

from icecream import ic

def main():
    a = 1
    b = 2
    ic(a, b, a / b)

if __name__ == "__main__":
$ python example.py
ic| a: 1, b: 2, a / b: 0.5

This includes some syntax highlighting like Rich.

这包括一些像 Rich 这样的语法突显。

icecream also has some other handy abilities like installing as a builtin so you don’t need to import it.
Check out its documentation for more info.



May you ever print() your bugs away,