整理 | Rachel

责编 | Jane

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

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

前言

在之前的推荐中,营长为大家介绍过一些有趣的实用工具,包括自动化UI测试工具、代码修复神器、帮小白快速修复error、pdf翻译工具、变量命名神器等等。今天,营长要为大家推荐两个基于 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 line         9             bits.insert(0, remainder)Modified var:.. bits = [1, 0]21:14:32.099769 line         7         while number:21:14:32.099769 line         8             number, remainder = divmod(number, 2)Modified var:.. number = 021:14:32.099769 line         9             bits.insert(0, remainder)Modified var:.. 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 Beholdletters  = ['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 Beholdletters  = ['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() == letter and index % 2 == 0).show('index')

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

from behold import Beholdletters  = ['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() == letter and 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, Itemitem = 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_stashdef 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 item in 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”,加入「长三角开发者联盟」。

推荐阅读:

  • 机器学习萌新必备的三种优化算法 | 选型指南

  • A* 算法之父、人工智能先驱Nils Nilsson逝世 | 缅怀

  • Python程序员Debug的利器,和Print说再见 | 技术头条

  • 入门AI第一步,从安装环境Ubuntu+Anaconda开始教!

  • 小程序的侵权“生死局”

  • @996 程序员,ICU 你真的去不起!

  • Elastic Jeff Yoshimura:开源正在开启新一轮的创新 | 人物志

  • 19岁当老板, 20岁ICO失败, 21岁将项目挂到了eBay, 为何初创公司如此艰难?

  • 她说:为啥程序员都特想要机械键盘?这答案我服!

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

Python程序员Debug利器,和Print说再见 | 技术头条相关推荐

  1. 流量为王,程序员如何打破 8 秒原则?| 技术头条

    作者 | 阿文 责编 | 郭芮 对于一个 Web 站点或是一个 APP 应用来说,当用户打开你的站点时,最在乎的并不是你提供的内容有多么的吸引人.内容质量有多么高,因为此时用户还没看见你的提供的内容, ...

  2. 薪资不如 Java、C,BAT 需求大,揭秘 Python 程序员跳槽现状!

    作者 | 雨蹊 本文经授权转载自100offer(ID:im100offer) 「人生苦短,快用Python」,这话曾是不少选择投入Python麾下的「码农」们的一句调侃和自我标榜. 与敏捷开发.大数 ...

  3. Python程序员烂大街了?真实的就业是?

    随着软件开发行业的蓬勃发展,程序员的需求量日益增长,其中以Python开发尤其显著,越来越多人选择去学习Python编程语言,找一份好工作. 然而,随着Python的火爆,质疑的声音也越来越大.前几日 ...

  4. Python 程序员必知必会的开发者工具

    Python 程序员必知必会的开发者工具 Python已经演化出了一个广泛的生态系统,该生态系统能够让Python程序员的生活变得更加简单,减少他们重复造轮的工作.同样的理念也适用于工具开发者的工作, ...

  5. Python程序员常犯的十个错误

    不管是在学习还是工作过程中,人都会犯错.虽然Python的语法简单.灵活,但也一样存在一些不小的坑,一不小心,不管是初学者还是资深Python程序员都有可能会栽跟头. 常见错误1:错误地将表达式作为函 ...

  6. 520 情人节 :属于Python 程序员的脱单攻略大合集(视频版)

    作者| Python 编程时光 责编| Carol 情人节年年有,但今年的 5.20 要比以往的更有意义. 2020.05.20 ,爱你爱你我爱你,如果再卡个时间(13:14),那就是 爱你爱你我爱你 ...

  7. Python 程序员最常犯的十个错误

    常见错误1:错误地将表达式作为函数的默认参数 在Python中,我们可以为函数的某个参数设置默认值,使该参数成为可选参数.虽然这是一个很好的语言特性,但是当默认值是可变类型时,也会导致一些令人困惑的情 ...

  8. python开发工具排名-Python程序员必备的四款开发工具

    原标题:Python程序员必备的四款开发工具 每一位Python程序员都会有套工具来应对开发工作上的挑战.多年来,Python程序员使用开发工具来完成他们的工作.有很多工具对他们是有用的,不过对于初入 ...

  9. python搞笑代码-【转】 Python 程序员的进化--搞笑版

    不久前,在互联网上出现了一篇有趣的文章,讲的是对于同一个问题,不同层次的程序员编出的Phthon代码显示出了不同的风格,代码都很简单,有趣.这篇文章的原始出处在这里,我把它整理了一下,并修改了几处错误 ...

最新文章

  1. java 动态多态性_java4 动态多态性
  2. 什么才是程序员摸鱼的最高境界?
  3. web中用纯CSS实现筛选菜单
  4. 学会使用svn:externals
  5. spring-data-redis 中使用RedisTemplate操作Redis
  6. C++中IDispatch接口
  7. Quartus16.0如何使用TCL脚本
  8. java 集合 延迟加载_java-如何测试延迟加载的JPA集合是否已初始化?
  9. 提高篇 第五部分 动态规划 第6章 斜率优化动态规划
  10. 40 SD配置-销售凭证设置-分配计划行类别
  11. HttpClient settimeout 设置
  12. 生成对抗网络GANs的用途
  13. 桥接模式---汽车导航
  14. Linux 挂载 IP SAN
  15. 用PS快速给图片添加逼真彩虹效果
  16. ITIL 4 Foundation-指导原则
  17. 除尘器选型需要考虑的因素
  18. Dynagon代码阅读感想
  19. sunxi:[0]全志SoC启动过程
  20. 如何清除ug服务器注册码,UG许可证删除不掉的解决方法

热门文章

  1. sql查询语句优化需要注意的几点
  2. RememberMe 功能的实现(base-auth使用说明)
  3. 【系列索引】结合项目实例 回顾传统设计模式 打造属于自己的模式类系列
  4. 浅谈“闭包”,什么才是“闭包”思想!—— javascript
  5. 专业研究HP procurve网络、阿姆瑞特和系统集成的论坛
  6. h3c l2tp ***配置
  7. linux安装8168网卡,Fedora 配置 RTL8168/8111 网卡 Linux下 RTL8168/8111 网卡配置
  8. 10.matlab中sort , min/max以及ceil 函数
  9. 数据结构Java版之红黑树(八)
  10. 栈和堆的区别(转 知乎)