2020-07-08 更新: 增加对于flask调试的说明

其他调试工具

近来翻到一篇将Python调试方法的文章1, 其中介绍了日志, pdb, 扩展中还介绍了 几个GUI工具.

这些工具在查找错误, 分析性能时很有用.

IPython

我也分享一款简单的小工具IPython: 这是一款交互式的Python的Shell, 拥有了强大的提示功能, 不仅能用于调试, 也能用于开发过程中 快速识别数据结构, 模拟真实的运行上下文.

这个大而全的工具其实装起来挺烦的, pip install ipython, 你也为他只执行了一条 语句, 但是附带的程序着实不少. 所以, 尽量和virtualenv进行配合, 将所有的库都 放置在本地.

使用IPython其实很简单,

1
2
import IPython
IPython.embed()

这样就能在程序执行时获取一个功能强大的交互器, 以下是一个简单的例子

1
2
3
4
5
6
7
8
9
10
11
# hello.py

def main():
print('Hello world')
a = 10
print(a, a * 5)
import IPython; IPython.embed()


if __name__ == '__main__':
main()

使用见下图:

图中可以直接获取a的值, 也能像正常程序一样使用Ctrl-D退出IPython. 通过一条语句就可以瞥见程序执行时的真正情况, 比起那些IDE的断点功能, 我想IPython的这种功能应该更受欢迎才对.

IPython可用的场景.

这是我平时使用IPython进行开发和调试时的场景. 如果大家也遇到了相似的场景, 也可以 利用工具来节约时间.

  1. 开发阶段, 当你不知道某个函数的返回值, 或是对文档中的数据结构有些疑问. 那么可以使用IPython在固定位置调用, 从而查看函数返回的数据结构

  2. 使用try-except, 或是某些if-else出现了问题, 可以通过插入IPython的语句来 查看当时的输入输出情况.

  3. 调试时错误时, IPython可以充当断点, 但是它比断点的功能更强. 甚至, 你可以试着 运行一下之后的语句, 或是毫不相关的任意合法的语句. 想想吧, 这是多么舒服的操作. 你完全不可能在任何编译型语言中做到.

有些注意点.

对于IPython的使用, 其实还是有些地方需要注意.

  1. 用过之后删除为好. 对于像Tornado这样的单线程框架, 如果一不小心, 预留的IPython语句被触发, 那么整个程序就停止了. 真是GG.

  2. 退出程序中的IPython.embed()并不意味着程序的退出, 程序会接着执行之后的语句, 感觉这更像是用GDB的调试功能.

  3. 使用IPython时, 最好不要在for循环中使用, 这也是可以理解的, for循环可以有 很多次执行, 退出一个Shell之后还有许多等待进入. 最后只能用kill关闭程序. 这样的体验我肯定大家也不想有. 如果真的想调试一下for循环, 为什么不试着写个break呢?

  4. 最好别在Windows平台上使用. 实习时, 公司的电脑是Windows7的. 每次IPython的 启动慢的让我怀疑人生. 如果你想消磨自己宝贵的时间, 那么Windows系统真是个不错 的选择.

uwsgi中如何使用

如果你的程序是使用uwsgi启动的, IPython.embed()操作会立刻退出, 这里, 我提供 两种方案

uwsgi配置参数

第一个方法是允许uwsgi与stdin的交互, 具体就是在uwsgi的配置文件中增加这么一句话:

1
honour-stdin: 1

或是启动时使用这个参数: uwsgi --honour-stdin -y app.yaml

使用tornado调试uwsgi应用

具体的操作可以参考: 使用Tornado调试各种Python框架的程序, 这样既不用入侵原始代码, 也能有漂亮的日志输出.

针对新版的tornado与asyncio结合使用时的问题

在tornado6.0以上的环境中, 如果直接使用IPython会报RuntimeError: This event loop is already running的错误.

一个可行的解决方案是在程序开头使用nest_asyncio进行patch.

1
2
3
4
5
6
7
8
import os

# patch要放在所有语句之前, 防止其他库引用了未被patch的库
# 为了安全, 只在DEBUG模式下打开, 使用时需要: DEBUG=true python main.py
if os.environ.get("DEBUG") == "true":
print("In nest asyncio mode")
import nest_asyncio
nest_asyncio.apply()

具体请访问: 关于IPython在asyncio模式下使用的探讨

flask应用的简单调试

6.0以后的tornado想要使用IPython调试, 有点太过于不自由了, 我只是想在本地调试, 却还要被asyncio所困扰. 因为平时我使用flask进行开发, 使用tornado又容易遇到问题. 因此, 我重新整理了一下自己的需求. 我想要的只是tornado的漂亮日志+改动文件后自动重载+方便的IPython embed操作,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# tornado 的日志的处理, 以mock的形式介入
from tornado.log import enable_pretty_logging, app_log
enable_pretty_logging()

log_mock = mock.patch('logging.getLogger', return_value=app_log)
log_mock.start()
start_server()
log_mock.end()


# 针对jinja模板, 打开调试模式
application.jinja_env.auto_reload = True
application.config['TEMPLATES_AUTO_RELOAD'] = True

# 以多进程方式启动
application.run(debug=True, port=options.port, threaded=False, processes=3)

# 单个进程
application.run(debug=True, port=options.port)