整理 | Rachel

责编 | Jane

出品 | Python大本营(id:pythonnews)




【导语】程序员每日都在和 debug 相伴。新手程序员需要学习的 debug
手段复杂多样,设置断点、查看变量值……一些网站还专门针对debug撰写了新手教程。老司机们在大型的项目中要 debug
的问题不一样,模块众多、代码超长,面对大型项目的debug之路道阻且长。针对新手和老手程序员会遇到的不同debug问题,本文推荐了两个GitHub上的开源debug工具:PySnooper
和 Behold,帮助大家更加优雅、简洁地 debug 代码。




前言




在之前的推荐中,营长为大家介绍过一些有趣的实用工具,包括自动化UI测试工具
<https://mp.weixin.qq.com/s?__biz=MzI0ODcxODk5OA==&mid=2247502291&idx=1&sn=dd1752f80b6c42f58a44d863e6ac465e&scene=21#wechat_redirect>
、代码修复神器
<https://mp.weixin.qq.com/s?__biz=MzI0ODcxODk5OA==&mid=2247502272&idx=1&sn=031b4bf8a2342431566da2f739422538&scene=21#wechat_redirect>
、帮小白快速修复error
<https://mp.weixin.qq.com/s?__biz=MzI0ODcxODk5OA==&mid=2247502005&idx=1&sn=27d3733a2fd0464835104ce1e06eace7&scene=21#wechat_redirect>
、pdf翻译工具
<https://mp.weixin.qq.com/s?__biz=MzI0ODcxODk5OA==&mid=2247501860&idx=1&sn=aaeb6e1a03bb260948424110e3c86080&scene=21#wechat_redirect>
、变量命名神器
<https://mp.weixin.qq.com/s?__biz=MzI0ODcxODk5OA==&mid=2247500541&idx=1&sn=9fc60420b6a291137e5b40d59cc100ee&scene=21#wechat_redirect>
等等。今天,营长要为大家推荐两个基于 Python 的 debug 工具:PySnooper 和 Behold,帮助大家对不同规模的项目,有针对性的优雅
debug。




查看变量值,是 debug 过程中常要做的一件事。Python 开发者们除了使用 print 对变量逐个输出以外,是否还有其他方法可用呢?其实,使用
Print
语句查看变量有时候也很繁琐:首先需要找到变量所在的代码行,然后注释掉部分代码,再加一行输出命令;之后再根据原步骤进行复原。这波操作在代码量较大时就要耗费大量精力了,并且如果忘记复原,或者在复原代码时出现手误,甚至可能在
debug 过程中再新加 Bug,着实不值得!




此外,在一些大型项目上,我们有时只需要对项目的部分模块或代码行进行调试,但 Python 项目调试的时候需要人工对代码进行划分,以满足调试需求,这就使
debug 变得更困难。




为了让大家更专注写代码、debug 更轻松,营长特别选取了两个 Github 的 debug 神器:PySnooper 和
Behold,分别推荐给新手和大型代码项目的老司机。




接下来,先简单介绍并比较两个工具的特性,之后再具体讲解使用步骤、功能,如果想查看工具源代码和文档,可以到文末查看,别忘了给营长点”在看“!




PySnooper 与 Behold 对比:

对象不同,简洁相同




*
使用对象不同




两个项目有何异同?两个作者对项目的描述就能轻松发现两者的不同:PySnooper——a poor man's
debugger”,针对新手程序员;Behold——为大型Python项目专门搭建的 debug 工具。




*
安装与使用




两个工具都不约而同地把“简便易用”作为了首要目标。PySnooper 和 Behold 都是一行代码搞定:”pip
install“。使用上,两者对查看变量做了针对性地改进,都支持使用一行命令输出多个变量,不同于以往使用 print 语句的方式。




*
特性




比较而言,PySnooper
更适用于调试单个函数,对函数变量的更改过程、指向操作所在代码行上更突出,可以对变量值及值发生改变时所对应的代码行进行输出,并将输出存储为文件。而 Behold
更加注重对代码的整体调试,以及 debug 时对变量的筛选,例如支持对全局变量和局部变量的区分等。




具体而言,PySnooper 的特性包括:




*
输出关于某个函数中变量更改的详细过程记录,包括变量的值、使变量更改的相关代码行、更改时间

*
将上述记录输出为一个.log文件

*
查一个或多个非局部变量的值

*
输出调试函数所引用的函数的变量更改记录

*
在缓存中输出记录,提高运行速度




Behold 的特性包括:




*
简单输出一个或多个变量的改变过程

*
依据变量的值对输出进行条件筛选

*
对变量的输出值给予自定义标签,提高输出结果的区分度

*
依据调试变量所在函数的所属模块筛选是否输出变量值

*
输出对象的部分或全部属性

*
依据全局变量和局部变量对输出进行筛选

*
将输出存储为Pandas.Dataframe格式的数据

*
在输出时使用自定义字典对变量输出的值进行重新定义




PySnooper: 新手程序员救星




1.安装:使用pip




* pip install pysnooper



2.设置需要调试的函数:使用@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)



输出如下:




*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* Starting var:.. number = 621:14:32.099769 call 3 @pysnooper.snoop()21:14:
32.099769 line 5 if number:21:14:32.099769 line 6 bits = []New var:....... bits
= []21:14:32.099769 line 7 while number:21:14:32.099769 line 8 number, remainder
= divmod(number, 2)New var:....... remainder = 0Modified var:.. number = 321:14
:32.099769 line 9 bits.insert(0, remainder)Modified var:.. bits = [0]21:14:
32.099769 line 7 while number:21:14:32.099769 line 8 number, remainder = divmod(
number, 2)Modified var:.. number = 1Modified var:.. remainder = 121:14:32.099769
line9 bits.insert(0, remainder)Modified var:.. bits = [1, 0]21:14:32.099769
line7 while number:21:14:32.099769 line 8 number, remainder = divmod(number, 2)
Modifiedvar:.. number = 021:14:32.099769 line 9 bits.insert(0, remainder)
Modifiedvar:.. bits = [1, 1, 0]21:14:32.099769 line 7 while number:21:14:
32.099769 line 10 return bits21:14:32.099769 return 10 return bits




3.将上述记录输出为文件,并保存在文件夹:文件命名为file.log,保存在“/my/log/”文件夹:




* @pysnooper.snoop('/my/log/file.log')



4.查看一个或多个非局部变量的值:查看foo.bar, self.whatever变量的改变过程,这两个变量不在number_to_bits函数中




* @pysnooper.snoop(variables=('foo.bar', 'self.whatever'))



5.输出调试函数所引用的函数的变量更改记录:




* @pysnooper.snoop(depth=2)



6.在缓存中输出记录,提高运行速度:

* @pysnooper.snoop(prefix='ZZZ ')



Beholder: 针对大型Python项目的调制工具




1.安装:使用pip




* pip install behold



2.简单输出一个或多个变量的改变过程:




*
*
*
*
*
*
*
* from behold import Behold
letters = ['a', 'b', 'c', 'd', 'A', 'B', 'C', 'D']
for index, letter in enumerate(letters): # 输出效果等价于如下代码 # print('index: {},
letter: {}'.format(index, letter)) Behold().show('index', 'letter')



3.依据变量的值对输出进行条件筛选:




*
*
*
*
*
*
*
*
* from behold import Behold
letters = ['a', 'b', 'c', 'd', 'A', 'B', 'C', 'D']
for index, letter in enumerate(letters): # 输出效果等价于如下代码 # if letter.upper() ==
letter and index % 2 == 0: # print('index: {}'.format(index))
Behold().when(letter.upper() == letterand index % 2 == 0).show('index')




4.对变量的输出值给予自定义标签,提高输出结果的区分度:这里依据变量的值分别打“even_uppercase”和“odd_losercase”标签,附在变量之后




*
*
*
*
*
*
*
*
*
*
*
* from behold import Behold
letters = ['a', 'b', 'c', 'd', 'A', 'B', 'C', 'D']
for index, letter in enumerate(letters): # 输出效果等价于如下代码 # if letter.upper() ==
letter and index % 2 == 0: # print('index: {}, letter:, {},
even_uppercase'.format(index, letter)) # if letter.upper() != letter and index
% 2 != 0: # print('index: {}, letter: {} odd_lowercase'.format(index, letter))
Behold(tag='even_uppercase').when(letter.upper() == letter and index % 2 == 0
).show('index', 'letter') Behold(tag='odd_lowercase').when(letter.lower() ==
letterand index % 2 != 0).show('index', 'letter')



5.依据调试变量所在函数的所属模块筛选是否输出变量值:





首先使用behold对函数设定调试规则:




*
*
*
*
*
*
*
*
*
*
*
* from behold import Behold
# 这是一个在代码库中常用的自定义函数def my_function(): x = 'hello' # 这是函数本身的逻辑
# 在“testing”环境时输出x的值 Behold().when_context(what='testing').show('x')
# 仅在“debug”环境时对函数进行调试输出 if Behold().when_context(what='debugging').is_true():
import pdb; pdb.set_trace()



在另一个代码模块中对设定调试规则的函数进行调试:




*
*
*
*
*
*
*
*
*
*
* from behold import in_context
# 设置context为“testing”@in_context(what='testing')def test_x(): my_function()
test_x()# 将输出'x: hello'
# 使用环境管理器设置环境为“debugging”以进行调试with in_context(what='debugging'): my_function()
# 转至pdb调试工具



6.输出对象的部分或全部属性:使用“with_args”指定调试对象的部分属性,使用“no_args”输出调试对象的全部属性




*
*
*
*
*
*
* from behold import Behold, Item
item = Item(a=1, b=2, c=3)#输出对象的部分属性Behold(tag='with_args').show(item, 'a', 'b'
)#输出对象的全部属性Behold(tag='no_args').show(item)



7.依据全局变量和局部变量对输出进行筛选:




*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* from __future__ import print_functionfrom behold import Behold, Item
# 定义全局变量g = 'global_content'
# 定义一个函数,设定局部变量def example_func(): employee = Item(name='Toby') boss =
Item(employee=employee, name='Michael')
print('# Can\'t see global variable') Behold().show('boss', 'employee', 'g')
print('\n# I can see the the boss\'s name, but not employee name') Behold(
'no_employee_name').show(boss)
print('\n# Here is how to show global variables') Behold().show(global_g=g,
boss=boss)
# 可以对变量的输出顺序进行调整 print('\n# You can force variable ordering by supplying
string arguments') Behold().show('global_g', 'boss', global_g=g, boss=boss)
print('\n# And a similar strategy for nested attributes')
Behold().show(employee_name=boss.employee.name)
example_func()



8.将输出存储为Pandas.Dataframe格式的数据:需要对变量值的标签进行定义,标签将存储为变量的键值




*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* from __future__ import print_functionfrom pprint import pprintfrom behold
import Behold, in_context, get_stash, clear_stash
def my_function(): out = [] for nn in range(5): x, y, z = nn, 2 * nn, 3 * nn
out.append((x, y, z))
# 对变量值的标签进行定义
# 尽在测试x的环境下存储y和z的值 Behold(tag='test_x').when_context(what='test_x').stash('y',
'z')
# 仅在测试y的环境下存储x和z的值 Behold(tag='test_y').when_context(what='test_y').stash('x',
'z')
# 仅在测试z的环境下存储x和y的值 Behold(tag='test_z').when_context(what='test_z').stash('x',
'y') return out
@in_context(what='test_x')def test_x(): assert(sum([t[0] for t in
my_function()]) ==10)
@in_context(what='test_y')def test_y(): assert(sum([t[1] for t in
my_function()]) ==20)
@in_context(what='test_z')def test_z(): assert(sum([t[2] for t in
my_function()]) ==30)
test_x()test_y()test_z()
print('\n# contents of test_x stash. Notice only y and z as expected')
pprint(get_stash('test_x'))
print('\n# contents of test_y stash. Notice only x and z as expected')
pprint(get_stash('test_y'))
print('\n# contents of test_z stash. Notice only x and y as expected')
print(get_stash('test_z'))







也可以对存储的结果进行清除。




* clear_stash()

当该命令的参数为空时,默认清除所有调试数据的缓存。如果想要指定清除某个或某些参数的调试缓存数据,则需在参数中进行指定。




9.在输出时使用自定义字典对变量输出的值进行重新定义:




下例中对变量的值进行了自定义。假设自定义字典中的键值为数据库索引,下例展示了将该索引转变为自定义标签的方法。




*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* from __future__ import print_functionfrom behold import Behold, Item

# 定义Behold的子类以支持自定义的属性提取class CustomBehold(Behold): @classmethod def
load_state(cls): cls.name_lookup = { 1: 'John', 2: 'Paul', 3: 'George', 4:
'Ringo' }
def extract(self, item, name): # 如果没有加载lookup state,则先进行加载 if not
hasattr(self.__class__,'name_lookup'): self.__class__.load_state()
# 抽取变量的值 val = getattr(item, name)
# 如果变量是一个Item类变量,则进行值转换 if isinstance(item, Item) and name == 'name': return
self.__class__.name_lookup.get(val,None)
# 否则使用Behold默认的转换函数 else: return super(CustomBehold, self).extract(item, name)

# 定义一组Item变量用于测试items = [Item(name=nn) for nn in range(1, 5)]
print('\n# Show items using standard Behold class')for item in items:
Behold().show(item)

print('\n# Show items using CustomBehold class with specialized extractor')for
itemin items:    CustomBehold().show(item, 'name', 'instrument')



总结




在本文中,营长针对新手程序员和 Python
大型项目的代码调试为大家分别推荐了PySnooper和Behold两个调试工具,帮助大家简化代码调试过程、优化调试输出,以提高代码调试效率,希望对大家有所帮助。在未来,营长也会继续努力为大家发掘更多好用的工具,帮助大家更优雅地书写代码。






PySnooper的Github地址:


https://github.com/cool-RR/PySnooper/tree/2c8c74903d20e0e52e358ce95af437a18f5fb495




Behold的Github地址:

https://github.com/robdmc/behold




(本文为Python大本营原创文章,转载请微信联系1092722531)
长三角开发者联盟
代码就是力量,长三角的开发者联合起来!

加入「长三角开发者联盟」将获得以下权益

长三角地区明星企业内推岗位
CSDN独家技术与行业报告
CSDN线下活动优先参与权
CSDN线上分享活动优先参与权




扫码添加联盟小助手,回复关键词“长三角2”,加入「长三角开发者联盟」。



推荐阅读:


*
机器学习萌新必备的三种优化算法 | 选型指南
<https://blog.csdn.net/dQCFKyQDXYm3F8rB0/article/details/89507769>

*
A* 算法之父、人工智能先驱Nils Nilsson逝世 | 缅怀
<https://blog.csdn.net/dQCFKyQDXYm3F8rB0/article/details/89507770>

*
Python程序员Debug的利器,和Print说再见 | 技术头条
<https://mp.weixin.qq.com/s?__biz=MzU5MjEwMTE2OQ==&mid=2247485716&idx=1&sn=e1544b2f686a679fdd7335c60363b8db&scene=21#wechat_redirect>

*
入门AI第一步,从安装环境Ubuntu+Anaconda开始教!
<https://mp.weixin.qq.com/s?__biz=MzU5MjEwMTE2OQ==&mid=2247485716&idx=3&sn=47c67b41b98a2d781223347db355bbd5&scene=21#wechat_redirect>

*
小程序的侵权“生死局” <https://blog.csdn.net/csdnnews/article/details/89443205>

*
@996 程序员,ICU 你真的去不起! <https://blog.csdn.net/csdnnews/article/details/89507021>

*
Elastic Jeff Yoshimura:
<https://mp.weixin.qq.com/s?__biz=MzA3MjY1MTQwNQ==&mid=2649827390&idx=1&sn=b46af38886f0f18a8f9e612e1b4568ba&scene=21#wechat_redirect>
开源正在开启新一轮的创新 | 人物志
<https://mp.weixin.qq.com/s?__biz=MzA3MjY1MTQwNQ==&mid=2649827390&idx=1&sn=b46af38886f0f18a8f9e612e1b4568ba&scene=21#wechat_redirect>

*
19岁当老板, 20岁ICO失败, 21岁将项目挂到了eBay, 为何初创公司如此艰难?
<https://blog.csdn.net/Blockchain_lemon/article/details/89507730>

*
她说:
<https://mp.weixin.qq.com/s?__biz=MzA5MjcxNjc2Ng==&mid=2650559816&idx=1&sn=380cfd3d18fb987c0073bf1b8289155a&scene=21#wechat_redirect>
为啥程序员都特想要机械键盘?
<https://mp.weixin.qq.com/s?__biz=MzA5MjcxNjc2Ng==&mid=2650559816&idx=1&sn=380cfd3d18fb987c0073bf1b8289155a&scene=21#wechat_redirect>
这答案我服!
<https://mp.weixin.qq.com/s?__biz=MzA5MjcxNjc2Ng==&mid=2650559816&idx=1&sn=380cfd3d18fb987c0073bf1b8289155a&scene=21#wechat_redirect>






点击阅读原文,了解「CTA核心技术及应用峰会」。

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信