1.相关版本
anaconda(python 3.10.16)
Anaconda 是一个开源的 Python 和 R 数据科学平台,广泛用于科学计算、数据分析、机器学习等领域。它提供了一个便捷的包管理和环境管理系统,可以帮助用户更轻松地安装、更新和管理 Python 库及其他依赖。
qt6(cmake+msvc)
Qt6 是一个跨平台的 C++ 图形用户界面(GUI)库,广泛用于开发桌面和移动应用程序。Qt 提供了丰富的控件、图形、网络、数据库等模块,并且支持跨平台开发,允许开发者在 Windows、Linux、macOS 和其他平台上编写一次代码,然后运行在多种操作系统上。
pybind11(Version: 2.13.6)
pybind11 是一个轻量级的库,它允许将 C++ 代码和 Python 代码进行高效的绑定。它的主要目的是通过简洁的 API 和强大的功能,帮助开发者将 C++ 库暴露给 Python 用户。通过 pybind11,开发者可以非常方便地将 C++ 的高性能代码与 Python 的灵活性和易用性结合起来。
2.简单介绍下相关创建或下载。
1.创建anaconda环境:conda create -n myenv python=3.10
2.qt creator创建项目时选择qcmake
两个编译器可以都选择
运行项目时选择msvc
。
3. pybind11下载网址pybind/pybind11: Seamless operability between C++11 and Python (github.com)
下载解压,在qt项目下创建external文件,
将pybind11放入
保证pybind11文件夹下即是内容
3.代码示列
1.python代码
# script.py
def hello_from_python():
print("Hello from Python!")
def add(a, b):
return a + b
将其放入项目根目录中。
2.qt代码中的CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(ttt VERSION 0.1 LANGUAGES CXX)
# 如果不需要避免 Qt 定义 'slots' 和 'signals',可以移除以下行
add_definitions(-DQT_NO_KEYWORDS)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
# 指定 Python 解释器路径
set(Python3_EXECUTABLE "D:/Anaconda/envs/sc/python.exe")
# 指定 Python 的头文件目录
set(Python3_INCLUDE_DIR "D:/Anaconda/envs/sc/include")
# 指定 Python 的库文件路径
set(Python3_LIBRARY "D:/Anaconda/envs/sc/libs/python310.lib")
# 添加 pybind11 子目录
add_subdirectory(external/pybind11)
# 查找 Python3 的解释器、开发库和头文件
find_package(Python3 3.10.16 EXACT REQUIRED COMPONENTS Interpreter Development)
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(ttt
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET ttt APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(ttt SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(ttt
${PROJECT_SOURCES}
)
endif()
endif()
# 链接 Qt Widgets 和 Python3 的库
target_link_libraries(ttt PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Python3::Python pybind11::embed)
# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.ttt)
endif()
set_target_properties(ttt PROPERTIES
${BUNDLE_ID_OPTION}
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
include(GNUInstallDirs)
install(TARGETS ttt
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(ttt)
endif()
从你 CMake 配置代码来看,整体的设置是为一个 Qt 6 项目与 Python 环境(通过 CMake 和 MSVC 编译)结合使用。下面是对这段代码的简要说明,以及一些可能需要注意的地方。
代码解析
1. CMake 最低版本和项目名称
cmake_minimum_required(VERSION 3.5)
project(ttt VERSION 0.1 LANGUAGES CXX)
cmake_minimum_required(VERSION 3.5)
:要求使用的 CMake 版本至少为 3.5,这通常用于确保能够使用特定的 CMake 功能。project(ttt VERSION 0.1 LANGUAGES CXX)
:指定项目名称为ttt
,版本为0.1
,且使用 C++ 作为开发语言。
2. Qt 设置
add_definitions(-DQT_NO_KEYWORDS)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
add_definitions(-DQT_NO_KEYWORDS)
:避免使用 Qt 特定的关键字(如signals
和slots
)以免与 C++ 关键字冲突。set(CMAKE_AUTOUIC ON)
,set(CMAKE_AUTOMOC ON)
,set(CMAKE_AUTORCC ON)
:自动启用 Qt 的 UI、MOC 和 RCC 处理。find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
:查找 Qt 的安装,并确保找到正确版本的 Qt(优先 Qt6,如果没有则回退到 Qt5)。find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
:为 Qt 添加具体的模块支持(此处为Widgets
模块,用于创建桌面应用的 GUI)。
3. Python 设置
set(Python3_EXECUTABLE "D:/Anaconda/envs/sc/python.exe")
set(Python3_INCLUDE_DIR "D:/Anaconda/envs/sc/include")
set(Python3_LIBRARY "D:/Anaconda/envs/sc/libs/python310.lib")
# 查找 Python3 的解释器、开发库和头文件
find_package(Python3 3.10.16 EXACT REQUIRED COMPONENTS Interpreter Development)
set(Python3_EXECUTABLE ...)
、set(Python3_INCLUDE_DIR ...)
和set(Python3_LIBRARY ...)
:这三行代码手动设置了 Python 解释器、头文件和库的路径,确保 CMake 可以找到对应的 Python 环境(D:/Anaconda/envs/sc
是 Anaconda 环境路径)。find_package(Python3 3.10.16 EXACT REQUIRED COMPONENTS Interpreter Development)
:查找 Python 3.10.16 的解释器和开发工具(用于编译扩展)。
4. Pybind11 设置
add_subdirectory(external/pybind11)
add_subdirectory(external/pybind11)
:将pybind11
的源码添加为子模块。你需要确保external/pybind11
目录下已经包含了pybind11
的源代码。
5. 项目源文件
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mainwindow.h
mainwindow.ui
)
set(PROJECT_SOURCES ...)
:指定项目的源文件,包括 C++ 源文件(main.cpp
,mainwindow.cpp
),头文件(mainwindow.h
),以及 UI 文件(mainwindow.ui
)。
6. 生成可执行文件
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(ttt
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
else()
if(ANDROID)
add_library(ttt SHARED
${PROJECT_SOURCES}
)
else()
add_executable(ttt
${PROJECT_SOURCES}
)
endif()
endif()
qt_add_executable(ttt ...)
:如果 Qt 版本为 Qt 6 或更高,使用qt_add_executable
创建可执行文件。add_executable(ttt ...)
:如果是 Qt 5,则使用add_executable
创建可执行文件。
7. Pybind11 和 Qt 链接
# 链接 Qt Widgets 和 Python3 的库
target_link_libraries(ttt PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Python3::Python pybind11::embed)
target_link_libraries
:链接 Qt 、python和 Pybind11 库,以便可以在 C++ 项目中使用 Python 和 Qt。
一些建议和注意事项:
-
Pybind11 子模块:确保
external/pybind11
文件夹下已经存在pybind11
的源代码。如果没有,确保你已经通过 Git 将pybind11
的代码作为子模块引入项目,或者手动下载并放置在该目录下。使用 Git 子模块的命令:
git submodule add https://github.com/pybind/pybind11 external/pybind11 git submodule update --init --recursive
-
Python 和 Qt 版本兼容性:确认你的 Python 环境(3.10.16)与 Qt6 和 Pybind11 版本兼容。通常,Pybind11 支持 Python 3.6 到 3.10 版本,因此 3.10.16 应该没有问题。
-
MSVC 设置:确保在 Windows 上使用 MSVC 编译时,CMake 能找到正确的 MSVC 编译器。如果使用 Visual Studio,确保已安装支持 C++ 和 Qt6 的编译工具链。
-
编译和运行:在构建时,你可能需要一些额外的配置,确保 CMake 能找到 Python 和 Qt 相关的库和头文件。如果遇到任何编译错误,可以检查 CMake 输出,确认路径设置是否正确。(以上解析由gpt4o生成)
2 qt中的main.cpp代码
#include <QCoreApplication>
#include <QDebug>
#include <Python.h>
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
#ifdef _WIN32
// 设置 PYTHONHOME 和 PYTHONPATH
_putenv_s("PYTHONHOME", "D:/Anaconda/envs/sc");
_putenv_s("PYTHONPATH", "D:/Anaconda/envs/sc/Lib;D:/Anaconda/envs/sc/DLLs;D:/Anaconda/envs/sc/Lib/site-packages");
// 获取当前的 PATH 环境变量
char* existingPath = nullptr;
size_t len = 0;
_dupenv_s(&existingPath, &len, "PATH");
std::string newPath = "D:/Anaconda/envs/sc/DLLs;";
if (existingPath) {
newPath += existingPath;
free(existingPath);
}
// 更新 PATH 环境变量
_putenv_s("PATH", newPath.c_str());
#endif
// 初始化 Python
qDebug() << "Initializing Python...";
Py_Initialize();
if (!Py_IsInitialized()) {
qDebug() << "Python initialization failed!";
return 1;
}
qDebug() << "Python initialized successfully.";
// 设置 Python 脚本路径
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('.')"); // 当前目录
// 测试导入一个 Python 脚本
qDebug() << "Importing Python module...";
PyObject *moduleName = PyUnicode_FromString("script"); // 假设有一个 script.py 文件
PyObject *module = PyImport_Import(moduleName);
Py_DECREF(moduleName);
if (!module) {
qDebug() << "Failed to import Python module!";
PyErr_Print();
Py_Finalize();
return 1;
}
qDebug() << "Python module imported successfully.";
// 调用 Python 中的函数
qDebug() << "Calling Python function...";
PyObject *func = PyObject_GetAttrString(module, "hello_from_python");
if (func && PyCallable_Check(func)) {
PyObject *result = PyObject_CallObject(func, nullptr);
Py_XDECREF(result);
} else {
qDebug() << "Failed to call Python function!";
PyErr_Print();
}
Py_XDECREF(func);
Py_DECREF(module);
// 关闭 Python
Py_Finalize();
qDebug() << "Python finalized.";
return app.exec();
}
代码解析
1. 头文件
#include <QCoreApplication>
#include <QDebug>
#include <Python.h>
#include <QCoreApplication>
:这是 Qt 的一个核心模块,用于管理应用程序的控制流(例如,初始化、事件循环等)。它适用于没有图形界面的 Qt 应用程序。#include <QDebug>
:这是 Qt 的调试工具类,用于输出调试信息,类似于标准 C++ 中的std::cout
,但它提供了更强大的功能,如自动格式化。#include <Python.h>
:这是 Python C API 的主要头文件,它提供了与 Python 解释器交互的所有功能。你需要在 C++ 中嵌入 Python 时使用这个头文件。
2. 环境变量设置
#ifdef _WIN32
// 设置 PYTHONHOME 和 PYTHONPATH
_putenv_s("PYTHONHOME", "D:/Anaconda/envs/sc");
_putenv_s("PYTHONPATH", "D:/Anaconda/envs/sc/Lib;D:/Anaconda/envs/sc/DLLs;D:/Anaconda/envs/sc/Lib/site-packages");
// 获取当前的 PATH 环境变量
char* existingPath = nullptr;
size_t len = 0;
_dupenv_s(&existingPath, &len, "PATH");
std::string newPath = "D:/Anaconda/envs/sc/DLLs;";
if (existingPath) {
newPath += existingPath;
free(existingPath);
}
// 更新 PATH 环境变量
_putenv_s("PATH", newPath.c_str());
#endif
-
_putenv_s
:这是 Windows 平台上用于设置环境变量的函数。_putenv_s("KEY", "VALUE")
会设置一个新的环境变量,或者更新现有的环境变量。这里用来设置PYTHONHOME
和PYTHONPATH
,确保 Python 环境能够找到必要的库文件。 - 这两句代码设置的环境变量 不是永久生效的。它们仅在程序的运行期间有效。
PYTHONHOME
:指定 Python 解释器的主目录,通常是 Anaconda 环境的路径。PYTHONPATH
:指定 Python 库的位置,Python 需要这个路径来找到它的标准库和安装的模块。
-
_dupenv_s
:这个函数用于获取现有的环境变量的值(例如获取当前PATH
环境变量的内容)。它把当前PATH
环境变量复制到existingPath
变量中,供后续更新使用。 -
_putenv_s("PATH", newPath.c_str())
:这里更新了PATH
环境变量,确保 Python 的 DLL 文件目录被包含在其中,从而可以在 C++ 程序中调用 Python 相关的 DLL。
3. 初始化 Python
qDebug() << "Initializing Python...";
Py_Initialize();
if (!Py_IsInitialized()) {
qDebug() << "Python initialization failed!";
return 1;
}
qDebug() << "Python initialized successfully.";
-
Py_Initialize()
:这是 Python C API 中的一个函数,用来初始化 Python 解释器。它需要在任何 Python C API 调用之前调用。它会设置 Python 运行时所需的基本环境(例如内存管理、垃圾回收等)。 -
Py_IsInitialized()
:这个函数用于检查 Python 解释器是否已经成功初始化。如果返回0
,表示初始化失败;如果返回非零值,表示初始化成功。
4. 设置 Python 脚本路径
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('.')"); // 当前目录
PyRun_SimpleString()
:这个函数用于执行 Python 代码字符串。这里它执行了两行 Python 代码:import sys
:导入 Python 标准库中的sys
模块。sys.path.append('.')
:将当前目录(.
)添加到 Python 模块的搜索路径中。这样 Python 会从当前目录加载模块,确保脚本文件能被找到。如果你的script.py
模块的位置改变了,那么你需要更新sys.path.append()
中的路径,以确保 Python 能够找到该模块。- 举例:假设你有一个 Python 脚本
my_script.py
,并且它在当前目录下有一个mymodule.py
模块。如果你想在my_script.py
中导入mymodule.py
,你需要确保当前目录被包含在sys.path
中。否则,Python 就无法找到mymodule.py
注意!!由于将之前将scrapy.py放入了项目根文件,这里的‘.’需要修改,或者可以直接将代码放入和项目名.exe相同的目录,也就是生成的debug文件夹下。
5. 导入 Python 模块
qDebug() << "Importing Python module...";
PyObject *moduleName = PyUnicode_FromString("script"); // 假设有一个 script.py 文件
PyObject *module = PyImport_Import(moduleName);
Py_DECREF(moduleName);
if (!module) {
qDebug() << "Failed to import Python module!";
PyErr_Print();
Py_Finalize();
return 1;
}
qDebug() << "Python module imported successfully.";
-
PyUnicode_FromString()
:将 C 字符串转换为 Python 字符串对象。"script"
是 Python 脚本的名称(假设脚本文件名为script.py
)。 -
PyImport_Import()
:该函数用于导入指定名称的 Python 模块。这里尝试导入名为"script"
的模块,PyImport_Import
会返回一个 Python 模块对象(PyObject*
)。如果模块导入失败,返回nullptr
。 -
Py_DECREF(moduleName)
:减少moduleName
对象的引用计数。PyUnicode_FromString 创建了一个 Python 字符串对象,使用完毕后应该调用Py_DECREF()
来减少引用计数,防止内存泄漏。 -
PyErr_Print()
:如果导入模块失败,可以调用这个函数来打印 Python 错误信息。
6. 调用 Python 函数
qDebug() << "Calling Python function...";
PyObject *func = PyObject_GetAttrString(module, "my_function"); // 假设脚本中有一个函数 my_function
if (func && PyCallable_Check(func)) {
PyObject *args = PyTuple_Pack(0); // 假设函数没有参数
PyObject *result = PyObject_CallObject(func, args);
Py_XDECREF(result); // 如果有返回值,减少引用计数
Py_DECREF(args);
} else {
qDebug() << "Function not found or not callable!";
}
-
PyObject_GetAttrString()
:获取模块中的属性(通常是函数或类)。这里我们试图获取模块module
中名为my_function
的函数。 -
PyCallable_Check()
:检查func
是否是一个可调用的对象(即函数)。如果是,就可以用PyObject_CallObject()
调用它。 -
PyTuple_Pack()
:创建一个 Python 元组对象,作为函数的参数。这里我们假设my_function
函数没有参数,所以传入0
表示空元组。 -
PyObject_CallObject()
:调用 Python 中的函数,并将参数传递给它。函数的返回值会存储在result
中。 -
Py_XDECREF()
:减少result
的引用计数。如果result
不为nullptr
,调用此函数可以避免内存泄漏。
7. 清理
Py_Finalize();
Py_Finalize()
:这个函数用于清理 Python 解释器,释放所有内存和资源。它应该在程序结束时调用。
以上解析由gpt4o生成
运行结果
对了,记得将Python 的动态链接库文件(DLL)放入debug下,
若用debug运行,报错
但生成releas即可,不要忘记将Python 的动态链接库文件(DLL)放入release下。
运行输出
15:25:15: Starting E:\qtcode\ttt\build\Desktop_Qt_6_7_1_MSVC2019_64bit-Release\ttt.exe...
Initializing Python...
Python initialized successfully.
Importing Python module...
Python module imported successfully.
Calling Python function...
Hello from Python!
Python finalized.
解决debug下不能运行的方法查看作者下一篇文章