PySnooper -- 永远不要再使用 print 进行调试
PySnooper 是一个穷人的调试器。
您试图弄清楚为什么您的 Python 代码没有按照您认为的那样做。您希望使用带有断点和 watches 的成熟调试器,但是现在还不想设置这样的调试器。
您想知道哪些行正在运行,哪些没有运行,以及局部变量的值是什么。
大多数人会在关键部分使用打印行(print lines),其中一些显示变量的值。
PySnooper 允许您执行相同的操作,只是您只需向感兴趣的函数添加一个装饰器行,而不是小心地创建正确的打印行。您将得到函数的详细日志,包括运行了哪些行、何时运行以及何时更改了局部变量。
是什么让 PySnooper 从所有其他代码智能工具中脱颖而出?您可以在糟糕、庞大的企业代码库中使用它,而无需进行任何设置。只需打开装饰器,如下所示,并通过将其路径指定为第一个参数,将输出重定向到专用日志文件。
示例
我们正在编写一个函数,通过返回一个位列表将数字转换为二进制。让我们通过添加@pysnooper.snoop() 装饰器来窥探它:
import pysnooper @pysnooper.snoop() def number_to_bits(number): if number: bits = [] while number: number, remainder = divmod(number, 2) bits.insert(0, remainder) return bits else: return [0] number_to_bits(6)
stderr 的输出是
Source path:... /my_code/foo.py Starting var:.. number = 6 15:29:11.327032 call 4 def number_to_bits(number): 15:29:11.327032 line 5 if number: 15:29:11.327032 line 6 bits = [] New var:....... bits = [] 15:29:11.327032 line 7 while number: 15:29:11.327032 line 8 number, remainder = divmod(number, 2) New var:....... remainder = 0 Modified var:.. number = 3 15:29:11.327032 line 9 bits.insert(0, remainder) Modified var:.. bits = [0] 15:29:11.327032 line 7 while number: 15:29:11.327032 line 8 number, remainder = divmod(number, 2) Modified var:.. number = 1 Modified var:.. remainder = 1 15:29:11.327032 line 9 bits.insert(0, remainder) Modified var:.. bits = [1, 0] 15:29:11.327032 line 7 while number: 15:29:11.327032 line 8 number, remainder = divmod(number, 2) Modified var:.. number = 0 15:29:11.327032 line 9 bits.insert(0, remainder) Modified var:.. bits = [1, 1, 0] 15:29:11.327032 line 7 while number: 15:29:11.327032 line 10 return bits 15:29:11.327032 return 10 return bits Return value:.. [1, 1, 0]
或者,如果不想跟踪整个函数,可以将相关部分封装在一个 with 块中
import pysnooper import random def foo(): lst = [] for i in range(10): lst.append(random.randrange(1, 1000)) with pysnooper.snoop(): lower = min(lst) upper = max(lst) mid = (lower + upper) / 2 print(lower, mid, upper) foo()
输出类似于
New var:....... i = 9 New var:....... lst = [681, 267, 74, 832, 284, 678, ...] 09:37:35.881721 line 10 lower = min(lst) New var:....... lower = 74 09:37:35.882137 line 11 upper = max(lst) New var:....... upper = 832 09:37:35.882304 line 12 mid = (lower + upper) / 2 74 453.0 832 New var:....... mid = 453.0 09:37:35.882486 line 13 print(lower, mid, upper)
特性
如果您无法轻松访问 stderr,则可以将输出重定向到文件:
@pysnooper.snoop('/my/log/file.log')
你也可以传递一个流或一个可调用的,它们将被使用。
查看一些非局部变量的表达式的值:
@pysnooper.snoop(watch=('foo.bar', 'self.x["whatever"]'))
展开值以查看其所有属性或列表/词典项:
@pysnooper.snoop(watch_explode=('foo', 'self'))
这将输出如下行:
Modified var:.. foo[2] = 'whatever' New var:....... self.baz = 8
(有关更多控制,请参阅高级用法)
显示函数调用的函数的 snoop 行:
@pysnooper.snoop(depth=2)
使用前缀启动所有 snoop 行,以便轻松地为它们 grep:
@pysnooper.snoop(prefix='ZZZ ')
在多线程应用程序上,识别输出中窥探的线程:
@pysnooper.snoop(thread_info=True)
PySnooper 支持装饰生成器。
您还可以自定义对象的 repr:
def large(l): return isinstance(l, list) and len(l) > 5 def print_list_size(l): return 'list(size={})'.format(len(l)) def print_ndarray(a): return 'ndarray(shape={}, dtype={})'.format(a.shape, a.dtype) @pysnooper.snoop(custom_repr=((large, print_list_size), (numpy.ndarray, print_ndarray))) def sum_to_x(x): l = list(range(x)) a = numpy.zeros((10,10)) return sum(l) sum_to_x(10000)
您将获得列表的 l = list(size=10000),以及 ndarray 的 a = ndarray(shape=(10, 10), dtype=float64)。 custom_repr 按顺序匹配,如果一个条件匹配,则不会检查其他条件。
安装
您可以通过以下方式安装PySnooper:
- pip:
$ pip install pysnooper
- conda with conda-forge channel:
$ conda install -c conda-forge pysnooper
高级使用
watch_explode 将根据其类自动猜测如何扩展传递给它的表达式。 您可以使用以下类之一来更加具体:
import pysnooper @pysnooper.snoop(watch=( pysnooper.Attrs('x'), # attributes pysnooper.Keys('y'), # mapping (e.g. dict) items pysnooper.Indices('z'), # sequence (e.g. list/tuple) items ))
使用 exclude 参数排除特定键/属性/索引,例如 Attrs('x', exclude=('_foo', '_bar'))。
在“索引”后添加切片,仅查看该切片中的值,例如 Indices('z')[-3:]。
$ export PYSNOOPER_DISABLED=1 # 这使 PySnooper 不做任何窥探
许可
版权所有(c)2019 Ram Rachum 和合作者,根据 MIT 许可证发布。
我在 Python 和 Django 中提供开发服务,并且我提供 Python 研讨会来教授人们 Python 和相关主题。
媒体报道
Hacker News thread and /r/Python Reddit thread (22 April 2019)
(First edition: vz edited at 2019.08.24)