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)
 該所有者的項目
                                                                (
                                                                該所有者的項目
                                                                (