赞
踩
最近试着用C#调用我写好的含有多种第三方库的python程序,碰到了挺多问题,但最终还是通过使用GPT以及查看许多博客,并自己实践的方式,使用Process 以及 Pythonnet包 的方式成功调用了我的python程序。在此我直接展示代码,在代码中做了大量注释。VS2013更难得是一定要注意包的版本问题。
如果你只需要简单地调用一个 Python 脚本并获取输出,Process 是一个简便的方法。
如果需要更为复杂的交互,考虑使用一些库(例如 pybind11、pythonnet 等)将 Python 代码嵌入到 C# 中,以便更灵活地进行集成。
VS2013版本较早,因此要注意一下版本的问题,以下是我使用的版本环境:
.NET FrameWork 4.5.1 vs2013支持的最高版本
python.net 2.5.2 此包去 NuGet Gallery | Home 直接搜索就行
python 3.8 我是用的Anaconda创建的一个虚拟环境的python程序执行环境
matplotlib 3.3.4 虚拟环境中py3.8适配的一个版本
代码如下(示例):
C# winform代码:
/// <summary> /// 运行python脚本 /// </summary> /// <param name="pyName">python文件名</param> /// <param name="strArgs">传递的参数</param> /// <param name="mode">脚本的标准输出行为</param> public static void RunPythonScript(string pyName, string[] strArgs, string mode) { Process p = new Process(); string path = System.AppDomain.CurrentDomain.SetupInformation.ApplicationBase + pyName;// 获得python文件的绝对路径(将文件放在c#的debug文件夹中可以这样操作) path = @"C:\Users\Administrator\PycharmProjects\practice_first\" + pyName;//(因为我没放debug下,所以直接写的绝对路径,替换掉上面的路径了) string sArguments = path; foreach (string strArg in strArgs) { sArguments += " " + strArg;//传递参数 } sArguments += " " + mode;//C:\\Users\\Administrator\\PycharmProjects\\practice_first\\execute.py 2 3 -u //E:\Anaconda\envs\pytorch\python.exe 我的某个python执行器的绝对路径 //没有配环境变量的话,可以写python.exe的绝对路径(绝对路径的话具体哪个python执行器都行)。如果配了,直接写 "python.exe" 或者 python 即可 p.StartInfo.FileName = @"python";//指定python执行器 //-u 参数会影响 execute.py 脚本的标准输出行为。如果 execute.py 中有使用 print 输出信息,使用 -u 参数可以让输出更即时地显示在终端上。如果不使用 -u,输出可能会在缓冲区满或程序结束时才刷新。 //-u 参数在调试或需要实时输出信息的情况下比较有用 p.StartInfo.Arguments = sArguments;//启动应用程序时要使用的一组命令行参数 p.StartInfo.UseShellExecute = false; p.StartInfo.RedirectStandardOutput = true; p.StartInfo.RedirectStandardInput = true; p.StartInfo.RedirectStandardError = true; p.StartInfo.CreateNoWindow = true; p.Start(); // 读取 Python 脚本的输出和错误信息 string output = p.StandardOutput.ReadToEnd(); string error = p.StandardError.ReadToEnd(); // 等待进程退出 p.WaitForExit(); // 处理输出和错误信息 Console.WriteLine("Output:"); Console.WriteLine(output); Console.WriteLine("Error:"); Console.WriteLine(error); // Check the exit code int exitCode = p.ExitCode; //Console.WriteLine($"Exit Code: {exitCode}"); } 按钮调用: private void button2_Click(object sender, EventArgs e) { string[] strArgs = new string[2];//参数列表 string pyName = @"test2.py";//这里是python的文件名字 strArgs[0] = "2"; strArgs[1] = "3"; string mode = "-u"; RunPythonScript(pyName, strArgs, mode); }
python文件 test2:
#import matplotlib as mpl 这个模块此时是找不到的 import numpy as np import sys x1_data = np.array([1, 2, 3, 4, 5]) x2_data = np.array([1, 2, 3, 4, 5]) y_data = np.array([3.1, 7.8, 14.2, 22.5, 32.7]) print(5*x1_data) print(x1_data*x2_data) res = sys.argv[1] + sys.argv[2] print(res)
补充:当使用python.net包后,配置方式二主程序的环境变量后,方式一居然能够调用main文件了,不清楚为啥
代码如下(示例):
C# winform代码:
static class Program { /// <summary> /// 应用程序的主入口点。 /// </summary> [STAThread] static void Main() { //另外我发现 若配置好了下面的环境变量,那么对于使用process方式的代码也可以调用下面的main.py程序了 //设置环境变量 string pathToVirtualEnv = @"E:\Anaconda\envs\pytorch";//python执行器的上级目录 Environment.SetEnvironmentVariable("PATH", pathToVirtualEnv, EnvironmentVariableTarget.Process);//第三个参数是一个用于指定环境变量的位置的枚举值。 Environment.SetEnvironmentVariable("PYTHONHOME", pathToVirtualEnv, EnvironmentVariableTarget.Process); Environment.SetEnvironmentVariable("PYTHONPATH", pathToVirtualEnv + "\\Lib\\site-packages;" + pathToVirtualEnv + "\\Lib", EnvironmentVariableTarget.Process); // 设置Python解释器的位置 PythonEngine.PythonHome = pathToVirtualEnv; //这里几个个目录 PythonEngine.PythonPath = PythonEngine.PythonPath + ";" + Environment.GetEnvironmentVariable("PYTHONPATH", EnvironmentVariableTarget.Process); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } } 按钮调用: private void button1_Click(object sender, EventArgs e) { try { using (Py.GIL())// 获取 Python GIL(全局解释器锁) { dynamic sys = Py.Import("sys"); sys.path.append(@"C:\Users\Administrator\PycharmProjects\practice_first"); // 添加Python文件的路径 dynamic module = Py.Import("main"); // 导入你的Python文件(不含.py扩展名) // 调用Python文件中的函数或执行其他操作 Object result = module.test(); // 调用Python文件中的某个函数 // 处理 Python 函数的返回值 Console.WriteLine("计算结果为:" + result); } } catch (Exception ex) { Console.WriteLine("Error: " + ex.Message); } finally { //PythonEngine.Shutdown(); // 关闭Python引擎 //PythonEngine.Shutdown(); // 关闭Python引擎 // 此处不关闭是因为我如果关闭了PythonEngine后,若再次点击按钮执行该程序时会报 //“System.AccessViolationException”类型的未经处理的异常在 Python.Runtime.dll 中发生 的错误 // 我采取的措施:考虑在使用完 Python 引擎之后调用 `PythonEngine.Shutdown()`, // 例如在应用程序关闭时或在不再需要 Python 引擎的地方。这样能够确保资源得到正确地释放,避免潜在的问题。 } }
python文件 main:
""" 这个python程序的目的 是根据某组数据来对某个模型进行拟合和优化的 所用的csv文件可以随意创建 """ from scipy.optimize import minimize import numpy as np import pandas as pd import show as s import os # 获取 'nba.csv' 文件的绝对路径 此处直接配置绝对路径是因为 # 如果给的是nba.csv相对路径,则在C#调用时会从bin\debug目录下寻找该文件 csv_file_path = os.path.abspath(r'C:\Users\Administrator\PycharmProjects\practice_first\nba.csv')#这句话本身就是获取某个文件的绝对路径的 r的作用是反转义-比如:\ # 加载读取实际数据 df = pd.read_csv(csv_file_path,sep='\s+',header=0,names=['产液量','含水率','温度']) g_data = np.array(df['产液量']) w_data = np.array(df['含水率']) t_data = np.array(df['温度']) # 定义原有油温计算公式(经验公式) def origin_oil_temperature_function(params, g,w): """ 定义公式 :param params: 待确定的多个系数,初始值看情况给就行 :param g: 输入数据一 此处是出液量 :param w: 输入数据二 此处是含水率 :return: 公式运行结果 """ a, b, c, d = params return (g*(1+w) - a*(1-w))/(g*b*(1+w)+c*(1-w))+d def last_oil_temperature_function(params, g, w): a, b, c, d = params return (g*(1+w) - a)/(b+c*g*(1+w))+d # 定义损失函数(拟合数据与实际数据的差异) def origin_loss_function(params): return np.sum((origin_oil_temperature_function(params, g_data, w_data) - t_data)**2) def last_loss_function(params): return np.sum((last_oil_temperature_function(params, g_data, w_data) - t_data)**2) # 初始猜测参数 initial_guess = [23.00, 0.0549, 3.5447, 21.5] # 计算原公式的优化前结果 origin_before_t = origin_oil_temperature_function(initial_guess, g_data, w_data) # 使用 BFGS 方法进行参数优化 result_origin = minimize(origin_loss_function, initial_guess, method='BFGS') initial_guess = [5.00, 2.9449, 0.0763, 22.0] # 计算新公式的优化前结果 last_before_t = last_oil_temperature_function(initial_guess, g_data, w_data) result_last = minimize(last_loss_function, initial_guess, method='BFGS') # 输出优化得到的参数 optimal_params_origin = result_origin.x optimal_params_last = result_last.x print("Origin Optimal Parameters:", optimal_params_origin) print("Last Optimal Parameters:", optimal_params_last) # 计算原公式的优化后结果 origin_after_t = origin_oil_temperature_function(optimal_params_origin, g_data, w_data) last_after_t = last_oil_temperature_function(optimal_params_origin, g_data, w_data) # 使用优化得到的参数绘制拟合的曲线 t_fit_origin = origin_oil_temperature_function(optimal_params_origin, g_data, w_data) # 计算新公式的优化后结果 t_fit_last = last_oil_temperature_function(optimal_params_last, g_data, w_data) def test(): # 显示结果 s.show_info(g_data, w_data, t_data, t_fit_origin,'origin') s.show_info(g_data, w_data, t_data, t_fit_last,'last') s.show_gap_info(g_data,w_data, origin_before_t - t_data,'origin-before') s.show_gap_info(g_data,w_data, origin_after_t - t_data,'origin-after') s.show_gap_info(g_data,w_data, last_before_t - t_data,'last-before') s.show_gap_info(g_data,w_data, last_after_t - t_data,'last-after') a = np.array([1,2,3,5,6]) b = np.array([1,2,3,5,6]) c = a * b print("success") return c if __name__ == '__main__': test()
main文件中用到的show文件:
import matplotlib as mpl import matplotlib.pyplot as plt mpl.use('TkAgg') # !IMPORTANT 更改在这里!!!!!!!!! def show_info(x, y, z1, z2, type): """ 将每一个条数据的 原始结果 和 优化后结果 显示出来 :param x: 输入变量一 :param y: 输入变量二 :param z1: 原结果 :param z2: 优化后结果 :param type: 公式类型 :return: """ # 创建一个 3D 图形窗口 fig = plt.figure() # 添加 3D 子图 ax = fig.add_subplot(111, projection='3d') # 绘制多条线 ax.plot(x, y, zs=z1, zdir='z', label='actual value '+type)# zs: 这是一个标量或一维数组,表示线的 z 坐标。 ax.plot(x, y, zs=z2, zdir='z', label='optimize value '+type)# zdir: 这是一个字符串,表示线在 3D 空间中的方向。 # 设置坐标轴标签 ax.set_xlabel('sole well liquid quantity') ax.set_ylabel('contain water rate') ax.set_zlabel('out oil tem') # 添加图例 ax.legend() # 显示图形 # plt.rcParams['font.sans-serif'] = ['esri_730'] # plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 plt.show() def show_gap_info(x, y, gap_data,type): """ 将每一条数据的 当前计算结果 和 真实结果 的差距显示出来 :param x: 输入变量一 :param y: 输入变量二 :param gap_data: 差距结果 :param type: 差距类型 :return: """ # 创建一个 3D 图形窗口 fig = plt.figure() # 添加 3D 子图 ax = fig.add_subplot(111, projection='3d') # 绘制多条线 ax.plot(x, y, zs=gap_data, zdir='z', label='gap-'+type)# zs: 这是一个标量或一维数组,表示线的 z 坐标。 # 设置坐标轴标签 ax.set_xlabel('sole well liquid quantity') ax.set_ylabel('contain water rate') ax.set_zlabel('gap-'+type) # 添加图例 ax.legend() # 显示图形 plt.show()
补充:使用方式二时碰到了一个注释中没有说明的bug,即:TclError: Can‘t find a usable init.tcl in the following directories,解决方法是将tcl8.6 以及 tk8.6 复制到报错信息中指定的目录下即可,比如 当前python虚拟环境中的Lib目录下。
以上就是今天要讲的内容,我用了几天的时间才解决这个问题,在此记录一下,说到底还是VS2013版本太老了,但我又必须要用这个版本,就这样。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。