强大的 Python 调试工具
项目描述
窥探
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 的输出如下所示:
让我们尝试一个更复杂的例子。我们正在编写一个 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
以便您可以轻松查看复杂数据结构的布局。如果prettyprinter
或pprintpp
已安装,它们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 中。
- 在底层转换源代码的魔法的存在下,例如
pytest
orbirdseye
(以及@spy
装饰器)。 pp
在第一次调用或之前修改源文件时snoop
。
在后台,pp
使用库executing
来定位函数调用的 AST 节点 - 如果您想编写一些自己的酷实用程序,请查看它。
pp
受到冰淇淋的启发,并提供相同的基本打印 API,但与和提供pp
无缝集成,这是独一无二的。snoop
pp.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')
有效。
(' 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
函数代替。snoop
PYTHONBREAKPOINT=snoop.snoop
禁用
如果您想保留snoop
代码库中的其他功能但禁用它们的效果,请通过enabled=False
. 例如,如果您使用的是 Django,请在生产环境snoop.install(enabled=DEBUG)
中settings.py
自动禁用它。禁用时,性能影响最小化,任何地方都没有输出。
您还可以在任何时候通过再次调用来动态地重新启用这些功能snoop.install(enabled=True)
,例如在特殊视图或信号处理程序中。
输出配置
install
有几个关键字参数用于控制snoop
and的输出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
对象。它具有frame
、event
、 和arg
中指定的sys.settrace()
属性,以及可能更改的其他属性。 -
pformat
:设置漂亮的格式化功能pp
使用。默认是使用第一个prettyprinter.pformat
,pprintpp.pformat
并且pprint.pformat
可以导入。
API 差异PySnooper
如果您熟悉PySnooper
并想使用snoop
,则应该注意一些事情,您必须采取不同的做法:
- 通过
prefix
和overwrite
到install()
,不是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
和自定义变量的显示。
如果您不确定它是否值得使用snoop
,PySnooper
请阅读此处的比较。
IPython/Jupyter 集成
snoop 带有一个 IPython 扩展,您可以在 shell 或笔记本中使用它。
首先,您需要加载扩展名,使用%load_ext snoop
笔记本单元格或添加'snoop'
到c.InteractiveShellApp.extensions
IPython 配置文件中的列表,例如~/.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.snoop
,config.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
项目详情
下载文件
下载适用于您平台的文件。如果您不确定要选择哪个,请了解有关安装包的更多信息。