Python程序员Debug利器,和Print说再见

简介: Python程序员Debug利器,和Print说再见

 

整理 | 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 = 6

21: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 = 0

Modified var:.. number = 3

21: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 = 1

Modified var:.. remainder = 1

21: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 = 0

21: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 bits

21: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() == letter and 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() == 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, 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_function
from 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('
# I can see the the boss's name, but not employee name')
 Behold('no_employee_name').show(boss)
 print('
# Here is how to show global variables')
 Behold().show(global_g=g, boss=boss)
 # 可以对变量的输出顺序进行调整
 print('
# You can force variable ordering by supplying string arguments')
 Behold().show('global_g', 'boss', global_g=g, boss=boss)
 print('
# And a similar strategy for nested attributes')
 Behold().show(employee_name=boss.employee.name)
example_func()


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

from __future__ import print_function
from pprint import pprint
from 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('
# contents of test_x stash. Notice only y and z as expected')
pprint(get_stash('test_x'))
print('
# contents of test_y stash. Notice only x and z as expected')
pprint(get_stash('test_y'))
print('
# contents of test_z stash. Notice only x and y as expected')
print(get_stash('test_z'))

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

clear_stash()


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

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

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

from __future__ import print_function
from 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('
# Show items using standard Behold class')
for item in items:
 Behold().show(item)
print('
# Show items using CustomBehold class with specialized extractor')
for item in items:
 CustomBehold().show(item, 'name', 'instrument')
目录
相关文章
|
28天前
|
存储 Python
python数据类型、debug工具(一)
python数据类型、debug工具(一)
|
5月前
|
缓存 监控 程序员
Python中的装饰器是一种特殊类型的声明,它允许程序员在不修改原有函数或类代码的基础上,通过在函数定义前添加额外的逻辑来增强或修改其行为。
【6月更文挑战第30天】Python装饰器是无侵入性地增强函数行为的工具,它们是接收函数并返回新函数的可调用对象。通过`@decorator`语法,可以在不修改原函数代码的情况下,添加如日志、性能监控等功能。装饰器促进代码复用、模块化,并保持源代码整洁。例如,`timer_decorator`能测量函数运行时间,展示其灵活性。
46 0
|
5月前
|
Python
python之print函数
python之print函数
|
18天前
|
存储 C++ Python
[oeasy]python037_ print函数参数_sep分隔符_separator
本文介绍了Python中`print`函数的`sep`参数,即分隔符。通过回顾上文内容,解释了类型与`type`的概念,并强调了参数类型的重要性。文章详细探讨了`print`函数如何使用`sep`参数来分隔输出值,默认分隔符为空格(序号32)。还讨论了如何修改分隔符为其他字符,如冒号,并解释了为何反斜杠需要使用双反斜杠表示。最后,文章追溯了`sep`名称的由来,以及相关词汇的历史背景,如盎格鲁-萨克逊人的武器和语言。
17 0
|
2月前
|
算法 程序员 Python
程序员必看!Python复杂度分析全攻略,让你的算法设计既快又省内存!
在编程领域,Python以简洁的语法和强大的库支持成为众多程序员的首选语言。然而,性能优化仍是挑战。本文将带你深入了解Python算法的复杂度分析,从时间与空间复杂度入手,分享四大最佳实践:选择合适算法、优化实现、利用Python特性减少空间消耗及定期评估调整,助你写出高效且节省内存的代码,轻松应对各种编程挑战。
34 1
|
28天前
|
存储 程序员 Python
python数据类型、debug工具(二)
python数据类型、debug工具(二)
|
3月前
|
设计模式 JSON 程序员
豆瓣评分9.4!Python程序员必读的《流畅的Python》,放这里了!
Python 官方教程的开头是这样写的:“Python 是一门既容易上手又强大的编程语言。””这句话本身并无大碍,但需要注意的是,正因为它既好学又好用,所以很多Python程序员只用到了其强大功能的一小部分,只需要几个小时,经验丰富的程序员就能学会用 Python 写出实用的程序。 然而随着这最初高产的几个小时变成数周甚至数月,在那些先入为主的编程语言的影响下,开发者们会慢慢地写出带着“口音”的 Python 代码。即便 Python 是你的初恋,也难逃此命运。因为在学校里,亦或是那些入门书上,教授者往往会有意避免只跟语言本身相关的特性。
|
3月前
|
设计模式 JSON 程序员
豆瓣评分9.4!Python程序员必读的《流畅的Python》,放这里了!
Python 官方教程的开头是这样写的:“Python 是一门既容易上手又强大的编程语言。””这句话本身并无大碍,但需要注意的是,正因为它既好学又好用,所以很多Python程序员只用到了其强大功能的一小部分,只需要几个小时,经验丰富的程序员就能学会用 Python 写出实用的程序。 然而随着这最初高产的几个小时变成数周甚至数月,在那些先入为主的编程语言的影响下,开发者们会慢慢地写出带着“口音”的 Python 代码。即便 Python 是你的初恋,也难逃此命运。因为在学校里,亦或是那些入门书上,教授者往往会有意避免只跟语言本身相关的特性。
|
3月前
|
程序员 Python
[oeasy]python0028_女性程序员_Eniac_girls_bug_Grace
回顾上次内容,我们了解到 `.py` 文件中的代码是按顺序一行行被解释执行的,可以使用 `pdb3 hello.py` 来调试程序。此外,我们探讨了“bug”这一术语的由来,它最早是在 1947 年由 Grace Murray Hopper 发现的一只真正的飞蛾所引起的计算机故障,从此“debugging”成了查找并修复程序错误的过程。早期的程序员大多为女性,因为她们通常更加细心且有耐心,这些特质对于检查错综复杂的线路和编程工作至关重要。编程与编织有着相似之处,都需要细致和有条理的操作。最后,我们认识到 bug 的存在是程序员工作的基础,没有 bug 就不需要程序员去修正它们。
40 3