《QT从基础到进阶·七十四》Qt+C++开发一个python编译器,能够编写,运行python程序改进版

1、概述
源码放在文章末尾

根据上一篇文章回顾下利用Qt+C++实现了一个简易的python编译器,类似pycharm或vsCode这样的编译器,该python编译器目前实现了如下功能:

(1)支持编写python程序

(2)编写代码时有代码补全提示

(3)程序运行到每行时该行高亮显示

(4)可以加载python脚本执行

(5)可以在程序运行的过程中随时中断

(6)有输出窗口实时显示程序执行的状态或执行程序的打印显示等

详细介绍可以看我上一篇文章。

在这篇文章中对上一版代码进行了一些优化和修改,具体修改功能如下:

(1)美化了界面操作,更像一个简易的python编译器

(2)新增了代码断点调试功能

(3)新增了菜单栏,功能分别为一键加载python脚本、运行python脚本、停止运行、单步调试、连续调试、清空所有断点,如下所示

下图为Python编译器的demo演示流程:
1、一键加载python脚本

关键代码如下所示(注意:加载python脚本时不能有中文路径,不然无法识别):

cpp 复制代码
void pythonRecipeWidget::on_loadScriptPushButton_clicked()
{
	QString initialDir;
	QString filePath = QFileDialog::getOpenFileName(this, tr("Select Script"), initialDir);
	if (filePath.isEmpty())
		return;

	ui.scriptPlainTextEdit->clear();
	std::ifstream file(filePath.toStdString());
	std::string script((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
	ui.scriptPlainTextEdit->setPlainText(script.c_str());
	file.close();
}

2、运行python脚本

给的例子中的python脚本会运行完for循环然后继续往下运行时会正常报错,因为没有导入该第三方工作库

3、停止运行

4、断点单步调试

5、连续调试

6、清除所有断点

主要代码分析:

运行 Python 脚本:使用 PyRun_SimpleString。

设置断点:通过 MyTrace 回调拦截 PyTrace_LINE。

断点调试(断点、暂停、继续、单步执行)。

中断机制(用户手动终止脚本)。

线程安全处理(QtConcurrent::run + PyGILState_Ensure)。

UI 控制启用/禁用(通过 QMetaObject::invokeMethod)。

cpp 复制代码
QtConcurrent::run([=]()
		{
			qDebug() << __FUNCTION__ << QThread::currentThreadId() << QThread::currentThread();

			QMetaObject::invokeMethod(m_run, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, false));
			QMetaObject::invokeMethod(m_stop, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, true));
			QMetaObject::invokeMethod(m_stepOver, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, true));
			QMetaObject::invokeMethod(m_continueExecute, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, true));
			
			PyGILState_STATE gstate = PyGILState_Ensure();
			scriptThreadState = PyThreadState_Get();

			PyEval_SetTrace(MyTrace, NULL);
			PyRun_SimpleString(GBK_To_UTF8(g_script).c_str());

			scriptThreadState = nullptr;
			PyGILState_Release(gstate);
			QMetaObject::invokeMethod(m_run, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, true));
			QMetaObject::invokeMethod(m_stop, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, false));
			QMetaObject::invokeMethod(m_stepOver, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, false));
			QMetaObject::invokeMethod(m_continueExecute, "setEnabled", Qt::QueuedConnection, Q_ARG(bool, false));
			is_paused = false;
			step_once = false;
		});

在后台线程中运行 Python 脚本,确保线程间 GIL 安全。

scriptThreadState 保存当前线程状态,供中断使用。

设置了 MyTrace 作为追踪函数,实现在某些行/事件进行控制,MyTrace实现对代码的断点、停止、继续运行等功能

cpp 复制代码
int MyTrace(PyObject* obj, PyFrameObject* frame, int what, PyObject* arg)
{
	if (g_isExection)
		return 0;

	//如果把中断程序放在这里会导致比如在第二行中断时会在第二行执行完才中断

	if ((lineCount == PyFrame_GetLineNumber(frame)) && what == PyTrace_EXCEPTION)
	{
		g_isExection = true;
		int line = PyFrame_GetLineNumber(frame);
		return 0;
	}

	if (what == PyTrace_LINE)
	{
		char const* fileName = _PyUnicode_AsString(frame->f_code->co_filename);
		char const* name = _PyUnicode_AsString(frame->f_code->co_name);

		if (strcmp(fileName, "<string>") == 0 && strcmp(name, "__new__") != 0)
		{
			int line = PyFrame_GetLineNumber(frame);
			lineCount = line;
			ShowLine(line);

			qDebug() << "filename" << fileName << "name" << name << "line" << line << "frame" << frame << "f_back" << frame->f_back;

			breakPointAfter = line;
			//如果断点不在代码行上就移到下面最近的代码行
			for (auto breakPointLine : breakPoints)
			{
				if (breakPointBefore < breakPointLine && breakPointLine < breakPointAfter)
				{
					breakPointCallBack_(breakPointBefore, breakPointAfter);
					is_paused = true;
					step_once = false;
					breakPointBefore = line;
					break;

				}
			}

			//当前代码行等于断点行就暂停程序
			if (breakPointAfter != breakPointBefore)
			{
				for (auto breakPointLine : breakPoints)
				{
					if (line == breakPointLine)
					{
						is_paused = true;
						step_once = false;
						break;
					}
				}
			}
			breakPointBefore = line;

			//判断当前是否debugging
			if (is_paused && !step_once)
				bpDebuggingLineCallBack_(line, true);
			else
				bpDebuggingLineCallBack_(line, false);

			// 暂停执行,等待继续调试信号
			while (is_paused && !step_once) {
				std::this_thread::sleep_for(std::chrono::milliseconds(100));
			}

			// 单步执行一次后继续暂停
			if (step_once) {
				is_paused = true;
				step_once = false;
			}

			//如果把中断程序放在这里会导致比如在第二行中断时会在第二行执行前中断
			if (g_isAbort)
			{
				if (!m_isInterrupt)
				{
					qDebug() << "User abort.";
					//PyErr_SetString(PyExc_KeyboardInterrupt, "User abort.");

					if (scriptThreadState)
					{
						PyGILState_STATE gstate = PyGILState_Ensure();
						PyThreadState_SetAsyncExc((unsigned long)scriptThreadState->thread_id, PyExc_KeyboardInterrupt);
						PyGILState_Release(gstate);
					}
					m_isInterrupt = true;
				}
				bpDebuggingLineCallBack_(line, false);
				return 0;
			}
		}
	}

	return 0;
}

追踪函数 MyTrace

追踪函数通过判断 what == PyTrace_LINE 来对 Python 脚本执行的每一行做拦截,并根据断点及状态决定:

cpp 复制代码
for (auto breakPointLine : breakPoints)
{
	if (line == breakPointLine)
	{
		is_paused = true;
		step_once = false;
		break;
	}
}

命中断点:暂停程序。

ShowLine(line) 和 bpDebuggingLineCallBack_() 用于 UI 更新。

单步执行逻辑

cpp 复制代码
while (is_paused && !step_once) {
	std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

is_paused 和 step_once 控制主调试循环。

外部通过点击 "继续" 或 "单步" 按钮控制 is_paused 和 step_once 变量。

中断处理(abort)

cpp 复制代码
if (g_isAbort && !m_isInterrupt)
{
	PyThreadState_SetAsyncExc((unsigned long)scriptThreadState->thread_id, PyExc_KeyboardInterrupt);
	m_isInterrupt = true;
}

用户点击"停止"按钮时触发中断。

使用 PyThreadState_SetAsyncExc 强行注入 KeyboardInterrupt 异常。

断点跳转优化

cpp 复制代码
if (breakPointBefore < breakPointLine && breakPointLine < breakPointAfter)

源码下载

相关推荐
去往火星5 分钟前
Visual Studio 2022 QT5.14.2 新建项目无法打开QT的ui文件,出现闪退情况
ide·qt·visual studio
橙色小博9 分钟前
PyTorch中的各种损失函数的详细解析与通俗理解!
人工智能·pytorch·python·深度学习·神经网络·机器学习
DBWYX36 分钟前
c++项目 网络聊天服务器 实现;QPS测试
c++
小森776736 分钟前
(三)机器学习---线性回归及其Python实现
人工智能·python·算法·机器学习·回归·线性回归
-XWB-1 小时前
【LLM】使用MySQL MCP Server让大模型轻松操作本地数据库
人工智能·python·自然语言处理
XYY3692 小时前
前缀和 一维差分和二维差分 差分&差分矩阵
数据结构·c++·算法·前缀和·差分
PacosonSWJTU2 小时前
python基础-13-处理excel电子表格
开发语言·python·excel
longlong int2 小时前
【每日算法】Day 16-1:跳表(Skip List)——Redis有序集合的核心实现原理(C++手写实现)
数据库·c++·redis·算法·缓存
24白菜头2 小时前
C和C++(list)的链表初步
c语言·数据结构·c++·笔记·算法·链表
小军要奋进2 小时前
httpx模块的使用
笔记·爬虫·python·学习·httpx