Skip to main content

强大的 Python 调试工具

项目描述

窥探

构建状态 支持 Python 2.7 和 3.5+ 版本,包括 PyPy

snoop 是一套功能强大的 Python 调试工具。它主要是为了成为 PySnooper 的一个更有特色和更精致的版本。它还包括自己的冰淇淋版本和其他一些漂亮的东西。

你试图弄清楚为什么你的 Python 代码没有做你认为它应该做的事情。您会喜欢使用带有断点和监视功能的成熟调试器,但您现在不必费心设置一个。

您想知道哪些行正在运行,哪些没有运行,以及局部变量的值是什么。

大多数人会print在战略位置使用线,其中一些显示变量的值。

snoop可以让你做同样的事情,除了不是仔细制作正确的print行,你只需将一个装饰器行添加到你感兴趣的函数中。你会得到你的函数的播放日志,包括哪些行运行以及更改局部变量的时间和确切时间。

安装就像pip install snoop.

您可以立即futurecoder上试用:在左侧的编辑器中输入您的代码,然后单击snoop按钮运行。不需要进口或装饰。

基本窥探用法

我们正在编写一个通过返回位列表将数字转换为二进制的函数。@snoop让我们通过添加装饰器来窥探它:

import snoop

@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)

请注意它是多么容易:只需import snoop@snoop. 如果您不喜欢神奇的导入,snoop.snoop并且from snoop import snoop仍然可以工作。或者,如果您根本不想在项目中导入,只需在install()某个地方调用一次。

stderr 的输出如下所示:

number_to_bits 输出

让我们尝试一个更复杂的例子。我们正在编写一个 memoizing 装饰器:它将函数参数和返回值存储在缓存中以避免重新计算:

import snoop

def cache(func):
    d = {}

    def wrapper(*args):
        try:
            return d[args]
        except KeyError:
            result = d[args] = func(*args)
            return result

    return wrapper

@snoop(depth=2)
@cache
def add(x, y):
    return x + y

add(1, 2)
add(1, 2)

在这里,我们指定depth=2意味着我们还应该向下一级进入内部函数调用。然后我们调用该函数两次以查看缓存的运行情况。这是输出:

缓存输出

乍一看,我们可以看到在第一次调用中缓存查找失败,KeyError因此调用了原始add函数,而在第二次调用中,立即返回了先前缓存的结果。

如果您不想跟踪整个函数,可以将相关部分包装在一个with块中:

import snoop
import random

def foo():
    lst = []
    for i in range(10):
        lst.append(random.randrange(1, 1000))

    with snoop:
        lower = min(lst)
        upper = max(lst)
        mid = (lower + upper) / 2

    return lower, mid, upper

foo()

输出类似:

富输出

常见论点

  • depth:如上所示,窥探您跟踪的函数/块所做的更深层次的调用。默认值为 1,表示没有内部调用,所以传递更大的值。
  • watch:通过将任意表达式指定为字符串来显示它们的值,例如:
@snoop(watch=('foo.bar', 'self.x["whatever"]'))
  • watch_explode:展开变量或表达式以查看它们的所有属性或列表/字典项:
@snoop(watch_explode=['foo', 'self'])

这将输出如下行:

........ foo[2] = 'whatever'
........ self.baz = 8

有关此参数的更高级用法,请参阅控制watch_explode

请参阅watch_extras自动显示有关任何值(局部变量、观察表达式或分解项)的附加信息。

pp- 很棒的打印调试

虽然snoop旨在使您免于编写print电话,但有时这仍然正是您需要的那种东西。pp旨在成为最好的版本。它可以单独使用,也可以与snoop.

pp(x)将输出x = <pretty printed value of x>,即它将显示其参数的源代码,以便您了解正在打印的内容,并格式化该值,pprint.pformat以便您可以轻松查看复杂数据结构的布局。如果prettyprinterpprintpp已安装,它们pformat将被使用而不是pprint.pformat.

pp将直接返回其参数,因此您可以轻松地将其插入代码中而无需重新排列。如果给定多个参数,它会将它们作为元组返回,因此您可以替换foo(x, y)foo(*pp(x, y))以保持代码行为不变。

这是一个例子:

from snoop import pp
x = 1
y = 2
pp(pp(x + 1) + max(*pp(y + 2, y + 3)))

输出:

12:34:56.78 LOG:
12:34:56.78 .... x + 1 = 2
12:34:56.78 LOG:
12:34:56.78 .... y + 2 = 4
12:34:56.78 .... y + 3 = 5
12:34:56.78 LOG:
12:34:56.78 .... pp(x + 1) + max(*pp(y + 2, y + 3)) = 7

如果你已经有了,import snoop你也可以使用snoop.pp. 但理想情况下,您将使用install()to 完全避免导入。

在某些情况下pp找不到其参数的源代码,在这种情况下,它将显示一个占位符:

  • 当找不到源文件时,通常是因为它不存在,例如,如果您在 Python shell 中。来源取自linecache.
  • 在 Python 3.4 和 PyPy 中。
  • 在底层转换源代码的魔法的存在下,例如pytestor birdseye(以及@spy装饰器)。
  • pp在第一次调用或之前修改源文件时snoop

在后台,pp使用库executing来定位函数调用的 AST 节点 - 如果您想编写一些自己的酷实用程序,请查看它。

pp受到冰淇淋的启发,并提供相同的基本打印 API,但与和提供pp无缝集成,这是独一无二的。snooppp.deep

' pp' 代表 'pretty-print' ,绝对绝对没有其他含义。打字也非常简单快捷。

pp.deep用于跟踪子表达式

如果您有pp(<complicated expression>)并且想查看该表达式内部发生的情况而不仅仅是最终值,请将其替换为pp.deep(lambda: <complicated expression>). 这将以正确的顺序记录每个中间子表达式,没有额外的副作用,并返回最终值。重复前面的例子:

pp.deep(lambda: x + 1 + max(y + 2, y + 3))

输出:

12:34:56.78 LOG:
12:34:56.78 ............ x = 1
12:34:56.78 ........ x + 1 = 2
12:34:56.78 ................ y = 2
12:34:56.78 ............ y + 2 = 4
12:34:56.78 ................ y = 2
12:34:56.78 ............ y + 3 = 5
12:34:56.78 ........ max(y + 2, y + 3) = 5
12:34:56.78 .... x + 1 + max(y + 2, y + 3) = 7

(文字和内置函数的值被忽略了,因为它们很简单)

如果引发异常,它将显示哪个子表达式负责,如下所示:

12:34:56.78 ................ y = 2
12:34:56.78 ............ y + 3 = 5
12:34:56.78 ........ (y + 3) / 0 = !!! ZeroDivisionError!
12:34:56.78 !!! ZeroDivisionError: division by zero

如果你喜欢这个,你可能会喜欢@spy

@spy

装饰器@spy可让您@snoop与强大的调试器结合使用birdseye。编码:

from snoop import spy  # not required if you use install()

@spy
def foo():

大致相当于:

import snoop
from birdseye import eye

@snoop
@eye
def foo():

要减少 的依赖关系snoop,您需要birdseye单独安装:pip install birdseye.

唯一的大缺点@spy是它显着降低了性能,因此对于具有许多循环迭代的函数,请避免使用它。否则,您基本上可以始终使用它而不是@snoop. 然后,如果日志没有您需要的信息,您可以打开鸟瞰 UI 以查看更多详细信息,而无需编辑或重新运行您的代码。非常适合当您感到懒惰并且不确定哪种工具最好时。

spy将其参数传递给snoop,因此例如@spy(depth=2, watch='x.y')有效。

在此处的文档中阅读更多信息birdseye

(' spy' 之所以如此命名,是因为它是装饰器名称 ' snoop' 和 ' eye' 的组合)

install()

为了使定期调试您的项目更加方便,请尽早运行此代码:

import snoop

snoop.install()

然后snoop, pp, 和spy将在每个文件中可用,而无需导入它们。

您可以通过传递关键字参数来选择不同的名称<original name>=<new name>,例如:

snoop.install(snoop="ss")

会让你用@ss.

如果您不喜欢此功能并且希望仅正常导入,但又想install()用于其他配置,请通过builtins=False.

作为替代方案,在 Python 3.7+中,如果您设置环境变量,则可以使用新breakpoint函数代替。snoopPYTHONBREAKPOINT=snoop.snoop

禁用

如果您想保留snoop代码库中的其他功能但禁用它们的效果,请通过enabled=False. 例如,如果您使用的是 Django,请在生产环境snoop.install(enabled=DEBUG)settings.py自动禁用它。禁用时,性能影响最小化,任何地方都没有输出。

您还可以在任何时候通过再次调用来动态地重新启用这些功能snoop.install(enabled=True),例如在特殊视图或信号处理程序中。

输出配置

install有几个关键字参数用于控制snoopand的输出pp

  • out: 确定输出目的地。默认情况下这是标准错误。你也可以通过:

    • Path要写入该位置的文件的字符串或对象。默认情况下,这总是会附加到文件中。通过overwrite=True初始清除文件。
    • 任何带有write方法的东西,例如sys.stdout文件对象。
    • 任何带有单个字符串参数的可调用对象,例如logger.info.
  • color:确定输出是否包含转义字符以在控制台中显示彩色文本。如果你在输出中看到奇怪的字符,你的控制台不支持颜色,所以通过color=False.

    • 代码是使用Pygments突出显示的语法,并且此参数作为样式传递。您可以通过传递命名样式(参见此图库)或样式类的字符串来选择不同的配色方案。默认样式是 monokai。
    • 默认情况下,此参数设置为out.isatty(),这对于 stdout 和 stderr 通常为 true,但如果它们被重定向或管道化,则为 false。True如果要强制着色,则通过或样式。
    • 要在 PyCharm 运行窗口中查看颜色,请编辑运行配置并勾选“在输出控制台中模拟终端”。
  • prefix: 传递一个字符串以使用该字符串开始所有 snoop 行,以便您可以轻松地对其进行 grep。

  • columns:这指定了每个输出行开头的列。您可以传递一个字符串,其中包含用空格或逗号分隔的内置列的名称。这些是可用的列:

    • time: 当前时间。这是默认情况下唯一的列。
    • thread: 当前线程的名称。
    • thread_ident:当前线程的标识符,以防线程名称不唯一。
    • file:当前函数的文件名(不是完整路径)。
    • full_file:文件的完整路径(在调用函数时也会显示)。
    • function: 当前函数的名称。
    • function_qualname: 当前函数的限定名。
  • watch_extras和:在高级用法replace_watch_extras下阅读这些内容

    如果您想要自定义列,请打开一个问题告诉我您对什么感兴趣!同时,您可以传递一个列表,其中元素是字符串或可调用对象。可调用对象应该接受一个参数,这将是一个Event对象。它具有frameevent、 和arg中指定的sys.settrace()属性,以及可能更改的其他属性。

  • pformat:设置漂亮的格式化功能pp使用。默认是使用第一个prettyprinter.pformatpprintpp.pformat并且pprint.pformat可以导入。

API 差异PySnooper

如果您熟悉PySnooper并想使用snoop,则应该注意一些事情,您必须采取不同的做法:

  • 通过prefixoverwriteinstall(),不是snoop()
  • 的第一个参数pysnooper.snoop,称为output,应该install与关键字一起传递给out
  • 而不是snoop(thread_info=True),写install(columns='time thread thread_ident')
  • 代替环境变量PYSNOOPER_DISABLED,使用install(enabled=False).
  • 而不是使用custom_repr,请参阅watch_extras自定义变量的显示

如果您不确定它是否值得使用snoopPySnooper阅读此处的比较

IPython/Jupyter 集成

snoop 带有一个 IPython 扩展,您可以在 shell 或笔记本中使用它。

首先,您需要加载扩展名,使用%load_ext snoop笔记本单元格或添加'snoop'c.InteractiveShellApp.extensionsIPython 配置文件中的列表,例如~/.ipython/profile_default/ipython_config.py.

%%snoop然后使用笔记本单元顶部的单元魔法来跟踪该单元:

%%窥探示例

高级用法

watch_extras

install有另一个参数称为watch_extras. 您可以将函数列表传递给它以自动显示有关任何值的额外信息:局部变量、监视表达式和分解项。例如,假设您想查看每个变量的类型。你可以定义一个这样的函数:

def type_watch(source, value):
    return 'type({})'.format(source), type(value)

然后你会写install(watch_extras=[type_watch]). 结果输出如下:

12:34:56.78    9 |     x = 1
12:34:56.78 .......... type(x) = <class 'int'>
12:34:56.78   10 |     y = [x]
12:34:56.78 .......... y = [1]
12:34:56.78 .......... type(y) = <class 'list'>

您编写的函数应该接受两个参数source-value通常这些参数是变量的名称及其实际值。他们应该返回一对表示返回信息的“来源”(仅用于显示,它不必是有效的 Python)和实际信息。如果您不想为此特定值显示任何内容,请返回None。任何引发的异常都会被捕获并静音。

默认情况下已经启用了两个这样的功能:一个显示值的len().shape属性(由 numpy、pandas、tensorflow 等使用),另一个显示.dtype属性。

watch_extras被添加到这两个默认函数中,因此您不必再次指定它们。如果您不想包含它们,请改用replace_watch_extras指定确切的列表。原始函数可以在这里找到:

from snoop.configuration import len_shape_watch, dtype_watch

控制watch_explode

watch_explode将根据其类自动猜测如何扩展传递给它的表达式。您可以通过使用以下类之一来更具体:

@snoop(watch=(
    snoop.Attrs('x'),    # Attributes (specifically from __dict__ or __slots__)
    snoop.Keys('y'),     # Mapping (e.g. dict) items, based on .keys()
    snoop.Indices('z'),  # Sequence (e.g. list/tuple) items, based on len()
))

使用参数排除特定键/属性/索引exclude,例如Attrs('x', exclude=('_foo', '_bar')).

之后添加一个切片Indices以仅查看该切片内的值,例如Indices('z')[-3:].

自定义变量的显示

(另见watch_extras

使用cheap_repr库呈现值以提高性能并避免充斥控制台。它为大多数常见类(包括来自第三方库的类)专门定义了 repr 函数。如果缺少课程,请在此处打开一个问题。您还可以为课程注册自己的代表。这是一个例子:

from cheap_repr import register_repr, cheap_repr

@register_repr(MyClass)
def repr_my_class(x, helper):
    return '{}(items={})'.format(
        x.__class__.__name__,
        cheap_repr(x.items, helper.level - 1),
    )

在这里阅读更多。

您还可以增加单个类的详细程度(请参阅文档),例如:

from cheap_repr import find_repr_function

find_repr_function(list).maxparts = 100

多个单独的配置

如果您需要比全局install函数更多的控制,例如,如果您想在一个进程中写入多个不同的文件,您可以创建一个Config对象,例如:config = snoop.Config(out=filename). 然后config.snoopconfig.pp并且config.spy将使用该配置而不是全局配置。

install()参数与输出配置相关的参数相同enabled

贡献

反馈和讨论

我很想听听用户的意见!显然,如果您有问题,请打开一个问题,但也要查看带有“讨论”标签的问题。还有很多工作要做,我真的很想听听人们的意见,这样我才能把事情做好。

您也可以通过电子邮件向我发送您喜欢或讨厌的内容snoop。只知道它正在被使用是有帮助的。

发展

拉请求总是受欢迎的!

请编写测试并使用Tox运行它们。

Tox 会自动安装所有依赖项。您只需要安装 Tox 本身:

$ pip install tox

如果要针对所有目标 Python 版本运行测试,请使用pyenv安装它们。否则,您只能运行已在您的机器上安装的那些:

# run only some interpreters
$ tox -e py27,py36

或者只是在开发人员模式下安装项目并带有测试依赖项:

$ pip install -e path/to/snoop[tests]

并运行测试:

$ pytest

下载文件

下载适用于您平台的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。

源分布

snoop-0.4.2.tar.gz (139.8 kB 查看哈希)

已上传 source

内置分布

snoop-0.4.2-py2.py3-none-any.whl (27.8 kB 查看哈希

已上传 py2 py3