在Python开发中,有时候需要使用Cython等工具将C++代码封装起来并在Python中使用。这样做有以下几个好处:
-
性能提升: C++通常比Python更高效。通过将C++代码封装为Python扩展模块,可以在Python中调用底层C++代码,从而获得更高的执行速度。这对于需要处理大量数据或者对性能要求较高的任务尤为重要。
-
现有库和功能的重用: C++拥有丰富的第三方库和功能,有时候我们希望在Python项目中重用这些库。通过将C++代码封装为Python模块,我们可以直接在Python中使用这些功能,而不需要重新实现它们。
-
访问底层系统资源: C++可以更容易地访问底层系统资源,如操作系统API、硬件驱动程序等。通过将C++代码封装为Python模块,我们可以在Python中调用这些底层资源,从而实现更高级别的功能。
-
增加代码保护性: 通过将C++代码封装为Python模块,我们可以将核心算法和实现细节隐藏起来,只暴露必要的接口给Python开发人员使用。这样可以增加代码的保护性,防止源代码被意外泄露或者滥用。
Python的线程功能在某些情况下可能效率较低。Python中的线程是通过全局解释器锁(Global Interpreter Lock,GIL)实现的,它会在任意时刻只允许一个线程执行Python字节码。这意味着在多线程环境下,Python的线程不能充分利用多核处理器的优势,因为同一时刻只有一个线程在执行。
相比之下,C++对多线程的支持更加直接和灵活。C++标准库提供了线程、互斥量、条件变量等多线程相关的类和函数,可以更细粒度地控制线程的创建、同步和通信。C++没有全局解释器锁,因此多个C++线程可以在多核处理器上并行执行,从而实现更高的并发性能。
下面简易举个例子,通过C++代码封装一个CsvSave类,此类功能可以开启一个线程专门保存数据到CSV文件中,从而提高python程序效率
开发环境 windows-x64 VS2022 Python 3.10.11 需要安装python库 pip install cython
项目目录
include C++的头文件,src CPP代码,python python相关文件
#ifndef CSVSAVER_H
#define CSVSAVER_H
#include <fstream>
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <vector>
#include <queue>
#include <atomic>
#include <string>
class CsvSaver
{
public:
CsvSaver();
CsvSaver(const std::string &filename);
~CsvSaver();
void add_data(const std::vector<std::string> &data);
bool Init(const std::string &filename);
private:
void stop();
void run();
void write_row(std::ofstream &file, const std::vector<std::string> &row);
std::string filename_; // CSV文件名
std::thread thread_; // CSV文件保存线程
std::ofstream file_; // CSV文件
std::vector<std::vector<std::string>> buffer_; // 数据缓冲区
std::queue<std::vector<std::string>> data_queue_; // 数据队列
std::mutex mu_; // 互斥量
std::condition_variable cv_; // 条件变量
std::atomic<bool> stopped_; // 线程停止标志
};
#endif // CSVSAVER_H
CPP代码
#include "CsvSaver.h"
#ifdef _WIN32
const std::string kCsvLineEnding = "\r\n"; // 定义CSV行结束符(Windows)
#else
const std::string kCsvLineEnding = "\n"; // 定义CSV行结束符(非Windows)
#endif
static int kBufferSize = 1024; // 缓冲区大小
static int kFlushInterval = 1000; // 写入时间间隔
CsvSaver::CsvSaver() : stopped_(true)
{
}
CsvSaver::CsvSaver(const std::string &filename) : filename_(filename), stopped_(false)
{
file_.open(filename, std::ios::out | std::ios::app);
if (!file_.is_open())
{
std::cerr << "Failed to open file: " << filename << std::endl;
return;
}
thread_ = std::thread(&CsvSaver::run, this);
}
CsvSaver::~CsvSaver()
{
stop();
if (file_.is_open())
{
file_.flush();
file_.close();
}
}
bool CsvSaver::Init(const std::string &filename)
{
if(stopped_){
stopped_ = true;
}else{
std::cerr << "CsvSaver no need Init" << std::endl;
return false;
}
file_.open(filename, std::ios::out | std::ios::app);
if (!file_.is_open())
{
std::cerr << "Failed to open file: " << filename << std::endl;
return false;
}
thread_ = std::thread(&CsvSaver::run, this);
return true;
}
void CsvSaver::add_data(const std::vector<std::string> &data)
{
std::unique_lock<std::mutex> lock(mu_);
data_queue_.push(data);
if (buffer_.size() >= kBufferSize)
{
cv_.notify_one();
}
}
void CsvSaver::stop()
{
{
std::unique_lock<std::mutex> lock(mu_);
stopped_ = true;
cv_.notify_one();
}
thread_.join();
while (!data_queue_.empty())
{
auto frontData = std::move(data_queue_.front());
data_queue_.pop();
write_row(file_, frontData);
}
}
void CsvSaver::run()
{
auto last_flush_time = std::chrono::steady_clock::now();
while (!stopped_)
{
std::unique_lock<std::mutex> lock(mu_);
cv_.wait_for(lock, std::chrono::milliseconds(100));
if (data_queue_.empty())
{
continue;
}
auto now = std::chrono::steady_clock::now();
auto elapsed_time = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_flush_time);
if (data_queue_.size() >= kBufferSize || elapsed_time.count() >= kFlushInterval || stopped_)
{
while (!data_queue_.empty())
{
auto frontData = std::move(data_queue_.front());
data_queue_.pop();
write_row(file_, frontData);
}
last_flush_time = std::chrono::steady_clock::now();
}
}
}
void CsvSaver::write_row(std::ofstream &file, const std::vector<std::string> &row)
{
for (size_t i = 0; i < row.size(); ++i)
{
file << row[i];
if (i < row.size() - 1) {
file << ",";
}
}
file << kCsvLineEnding;
}
写了一个MyClass类,最终要提供到python使用的C++API接口
// MyClass.h
// C++类的声明和定义
#include "CsvSaver.h"
class MyClass
{
public:
MyClass(); // 构造函数
~MyClass();
bool initFunction(std::string filepath);
void saveData(const std::vector<std::string> &strings);
private:
CsvSaver *m_CsvSaver;
};
#include "MyClass.h"
#include <iostream>
using namespace std;
MyClass::MyClass()
{
// 构造函数的实现
}
MyClass::~MyClass()
{
if (m_CsvSaver)
{
delete m_CsvSaver;
m_CsvSaver = nullptr;
}
}
bool MyClass::initFunction(std::string filepath)
{
// 类的功能函数的实现
cout << "C++ InitFunction\n";
cout << "filepath:" << filepath << "\n";
m_CsvSaver = new CsvSaver(filepath);
return true;
}
void MyClass::saveData(const std::vector<std::string> &strings)
{
m_CsvSaver->add_data(strings);
}
python目录
- setup.py
- myclass_wrapper.pyx
- test.py
myclass_wrapper.pyx将C++API接口封装python接口.同时C++数据类型,要用对应python数据类型转化
# myclass_wrapper.pyx
# cython: language_level=3
import ctypes
from libcpp.vector cimport vector
from libcpp.string cimport string
cdef extern from "MyClass.h":
cdef cppclass MyClass:
MyClass() except +
int initFunction(string filePath)
void saveData(const vector[string]& strings)
cdef class PyMyClass:
cdef MyClass *thisptr
def __cinit__(self):
self.thisptr = new MyClass()
def __dealloc__(self):
del self.thisptr
def initFunction(self, filePath, ip, port) -> int:
#decode方法将C++字符串解码为Python字符串
#encode方法将Python字符串编码为C++字符串
return self.thisptr.initFunction(filePath.encode(), ip.encode(), port)
def saveData(self, string_list):
cdef vector[string] cpp_strings
for s in string_list:
cpp_strings.push_back(s.encode())
self.thisptr.saveData(cpp_strings)
setup.py 用于管理cython构建文件的来源,类似CMakeLists和makefile
- include_dirs 头文件目录,在python上层的include中
- sources cpp文件目录 myclass_wrapper.pyx文件
- extra_compile_args 编译标准参数
- language 语言
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
extensions = [
Extension(
"myclass_wrapper",
sources=["myclass_wrapper.pyx", "../src/MyClass.cpp","../src/CsvSaver.cpp","../src/UDPClient.cpp"],
language="c++",
extra_compile_args=["/std:c++latest"],
include_dirs=["../include"],
#如果要链接其他库
#libraries=["my_library"], # 添加要链接的库的名称
#library_dirs=["/path/to/library"], # 添加库文件的路径
)
]
setup(
ext_modules=cythonize(extensions)
)
python测试文件
import myclass_wrapper
import ctypes
# 创建 MyClass 实例并调用其方法
my_object = myclass_wrapper.PyMyClass()
#初始化CSV保存
my_object.initFunction("data.csv")
strings = ["Hello", "World"]
my_object.saveData(strings) # 将整个列表传递给 saveData 方法
data_list = [] # 创建一个空列表
while True:
data_buffer = '12345'
data_list.append(data_buffer) # 将接收到的数据添加到列表中
if len(data_list) >= 10: # 判断 data_list 的长度是否达到指定阈值
my_object.saveData(data_list) # 将整个列表传递给 saveData 方法
print(data_list)
data_list.clear() # 清空 data_list
运行说明 在python目录下输入
python setup.py build_ext --inplace
python .\test.py
注意事项
- python setup.py build_ext --inplace
- 此时会编译生成myclass_wrapper.cp310-win_amd64.pyd,myclass_wrapper.cpp
- 这里取决你setup.py起的名字和python版本以及C++VS编译器MSVC,我的版本时3.10.11所有名字带有310
- 如果代码有错误,这里编译就会提示,建议MyClass类和CsvSaver类的功能先用C++编译测试后再封装python,
- 如果有错误,基本就是myclass_wrapper.pyx,封装python api问题了
- 如果没问题再运行python test.py测试例子
运行效果