像Chrome元素检查工具一样检查PyQt/PySide程序元素
对于使用Qt Widgets编写的PyQt/PySide程序, 我们很难在运行时查看程序中的控件信息、定位控件代码, 无法做到像前端开发那样, 在浏览器中通过开发者工具轻松选择HTML元素, 定位代码及查看信息.
本项目旨在解决这个问题, 提供类似Chrome DevTools的PyQt/PySide程序元素检查工具, 以提高学习, 开发及调试效率.
使用pip install PyQtInspect
安装即可.
PyQtInspect
架构分为两部分:
调试端/服务端: GUI程序, 供开发者直观地查看元素信息、定位代码等;
被调试端/客户端: 运行在被调试的Python程序中,负责patch宿主程序的Python Qt框架, 响应调试端的请求以及将宿主程序的信息传递给调试端等.
当前支持两种启动模式:
分离模式: 先手动启动GUI服务端, 再启动被调试端以连接服务端. 被调试端关闭后, GUI服务端不会关闭.
直接模式 (推荐): 仅需启动被调试端, 被调试端会自行在本机启动一个GUI服务端(无须开发者事先手动启动服务端). 被调试端关闭后会一同关闭GUI服务端.
注意,直接模式下,每创建一个被调试端(客户端)的同时,都会创建一个服务端(服务端), 属于一对一的关系. 此外, 在直接模式下, 用户无法手动指定监听端口、关闭连接以及Attach进程等操作.
分离模式调试支持远程调试(服务端和客户端不在同一台机器上); 而在直接模式下, 由于自动启动的服务端和客户端在同一台机器上, 所以不支持远程调试.
此外, PyQtInspect还支持在PyCharm等IDE上运行, 以及通过Attach进程的方式附加到PyQt/PySide进程中进行调试.
目前推荐的启动方法, 一步即可同时启动PyQtInspect服务端和客户端, 需要使用者拥有被调试程序的Python源代码.
如果你是通过python xxx.py param1 param2
来运行你的PyQt5程序, 则仅需在python
和xxx.py
中间加入-m PyQtInspect --direct --file
参数,
即python -m PyQtInspect --direct --file xxx.py param1 param2
, 即可启动PyQtInspect调试.
如果被调试程序使用的是PySide2/PyQt6/Pyside6, 则需要额外添加--qt-support
参数, 以指定对应的Qt框架.
举个例子, 如被调试程序使用的是PySide2, 则启动命令为python -m PyQtInspect --direct --qt-support=pyside2 --file xxx.py param1 param2
.
直接模式下, 完整的启动命令为:
python -m PyQtInspect --direct [--multiprocess] [--show-pqi-stack] [--qt-support=[pyqt5|pyside2|pyqt6|pyside6]] --file py_file [file_args]
各参数的含义如下:
--direct
: 指定启动模式为直接模式--multiprocess
: 指定支持多进程调试, 默认不启用--show-pqi-stack
: 指定显示和PyQtInspect相关的调用栈, 默认不显示--qt-support
: 指定被调试程序使用的Qt框架, 默认为pyqt5
; 可选值为pyqt5
, pyside2
, pyqt6
, pyside6
.--file
: 指定被调试程序的Python源代码文件路径file_args
: 被调试程序启动的命令行参数以调试PyQt-Fluent-Widgets
为例, 其demo可使用python examples/gallery/demo.py
来运行程序,
此时可以使用python -m PyQtInspect --direct --file examples/gallery/demo.py
以直接模式启动PyQtInspect调试器.
注: 当使用PyCharm等使用pydevd调试器的IDE进行调试时, 务必保证IDE中的‘PyQt compatible’选项设置为项目使用的Qt框架, 否则可能会导致PyQtInspect无法正常工作乃至程序崩溃.
通过分离模式调试时, 务必先启动GUI服务端,再启动被调试的Python程序.
在终端上输入pqi-server
即可启动服务端GUI程序。启动后,指定监听端口(默认为19394
)并点击Serve
按钮启动服务端。
前提: 需要使用者拥有被调试程序的Python源代码.
类似直接模式, 如果你是通过python xxx.py param1 param2
来运行你的PyQt5程序, 则仅需在python
和xxx.py
中间加入-m PyQtInspect --file
参数,
即python -m PyQtInspect --file xxx.py param1 param2
, 即可启动PyQtInspect调试.
类似地, 如果被调试程序使用的是PySide2/PyQt6/Pyside6, 同样需要额外添加--qt-support
参数, 以指定对应的Qt框架.
举个例子, 如被调试程序使用的是PySide2, 此时的启动命令为python -m PyQtInspect --qt-support=pyside2 --file xxx.py param1 param2
.
分离模式下, 完整的启动命令为:
python -m PyQtInspect [--port N] [--client hostname] [--multiprocess] [--show-pqi-stack] [--qt-support=[pyqt5|pyside2|pyqt6|pyside6]] --file py_file [file_args]
各参数的含义如下:
--port
: 指定服务端监听端口, 默认为19394
--client
: 指定服务端监听地址, 默认为127.0.0.1
--multiprocess
: 指定支持多进程调试, 默认不启用--show-pqi-stack
: 指定显示和PyQtInspect相关的调用栈, 默认不显示--qt-support
: 指定被调试程序使用的Qt框架, 默认为pyqt5
; 可选值为pyqt5
, pyside2
, pyqt6
, pyside6
.--file
: 指定被调试程序的Python源代码文件路径file_args
: 被调试程序启动的命令行参数同样以调试PyQt-Fluent-Widgets
为例,
如果当前GUI调试端已在本机启动(监听地址为默认值127.0.0.1
)并监听了19394
端口(默认值),
我们可以使用python -m PyQtInspect --file examples/gallery/demo.py
启动被调试端.
(因为在该例子中, 服务端的地址和端口均为默认值, 所以不需要额外指定--client
和--port
参数)
注: 当使用PyCharm等使用pydevd调试器的IDE进行调试时, 务必保证IDE中的‘PyQt compatible’选项设置为项目使用的Qt框架, 否则可能会导致PyQtInspect无法正常工作乃至程序崩溃.
直接在PyCharm调试PyQtInspect Module即可, 不影响对程序的调试.
还是以PyQt-Fluent-Widgets
为例, 可以新增一个Debug配置, 参数如下:
然后直接Run/Debug即可.
如果没有被调试程序的源代码, 可以通过Attach进程的方式尝试启动PyQtInspect客户端.
点击More->Attach To Process
打开Attach窗口, 选择被调试程序的进程窗口, 点击Attach按钮即可.
注意: 对于大多数控件而言, 此时是拿不到它们创造时的调用栈信息, 除非是Attach后创建的.
点击Select按钮即可选择元素, 将鼠标hover到需要检查的控件上, 高亮控件的同时亦可预览控件的简要信息(类名, 对象名, 大小, 相对位置, 样式等等).
单击鼠标左键后可选中该控件. 此时可以对控件进行更详细的检查, 比如查看并定位其初始化时的调用栈、在内部执行代码、查看层次信息、控件树定位以及查看属性等操作.
控件简要信息区下方的第二个选项卡页展示了该控件的详细属性信息, 按控件类继承关系和属性的类型进行层次化展示.
控件简要信息区下方的第一个选项卡页展示了该控件初始化时的调用栈,双击可以拉起PyCharm定位到对应的文件和行.
如果拉起PyCharm失败, 可以在More->Settings中设置PyCharm的路径.
p.s. 对于通过Attach进程方式启动的PyQtInspect客户端, 如果Attach过程中控件已经创建好了, 此时是拿不到创建时的信息, 调用栈信息为空
控件选中后, 点击Run Code按钮, 可在所选控件的作用域内执行代码(其中控件实例为self
, 本质就是在控件对象内部的一个方法内执行代码).
工具最下方为层次关系导航条, 可以查看、高亮、定位所选控件的祖先控件和子控件, 方便使用者在控件的层次中来回切换. 因此,结合已有的鼠标选择功能,用户可做到更精细的选择。
(默认打开, 需要关闭请前往 More->Mock Right Button Down as Left When Selecting Elements 取消)
由于一些控件需要左键点击后才能显示, 为了方便选择, 可以通过右键点击的方式模拟左键点击.
p.s. 该功能仅当选择过程中开启.
(默认打开, 需要关闭请前往 More->Press F8 to Finish Selecting 取消)
对于一些很难通过鼠标点击选中的控件, 可以通过F8完成选中. 注意, F8仅用于结束选择, 在未开启选择的情况下按F8并不会开启选择.
点击菜单上的 View->Control Tree
, 可以查看当前所选控件所在进程的控件树结构.
单击(或者hove)树中的行可以高亮对应的控件.
class A(B, C)
的情况, 其中B
和C
继承于QObject
, 这样可能会导致C
的__init__
方法无法被执行, 从而引发异常.
PyQt作者曾提醒过不要多继承两个以上的PyQt类, 因为这样也会容易导致PyQt自身行为异常
PySide6无法选中一些控件
QEnterEvent
的type
会为170
(QEvent.DynamicPropertyChange
), 当程序访问propertyNames
时会引发异常.