文章目录

前言一、Process 和 Pythonnet二、.net 和 python 以及 部分包 的版本问题三、代码实现1.Process方式2.使用python.net包的方式

总结

前言

最近试着用C#调用我写好的含有多种第三方库的python程序,碰到了挺多问题,但最终还是通过使用GPT以及查看许多博客,并自己实践的方式,使用Process 以及 Pythonnet包 的方式成功调用了我的python程序。在此我直接展示代码,在代码中做了大量注释。VS2013更难得是一定要注意包的版本问题。

一、Process 和 Pythonnet

如果你只需要简单地调用一个 Python 脚本并获取输出,Process 是一个简便的方法。

如果需要更为复杂的交互,考虑使用一些库(例如 pybind11、pythonnet 等)将 Python 代码嵌入到 C# 中,以便更灵活地进行集成。

二、.net 和 python 以及 部分包 的版本问题

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适配的一个版本

三、代码实现

1.Process方式

代码如下(示例): C# winform代码:

///

/// 运行python脚本

///

/// python文件名

/// 传递的参数

/// 脚本的标准输出行为

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文件了,不清楚为啥

2.使用python.net包的方式

代码如下(示例): C# winform代码:

static class Program

{

///

/// 应用程序的主入口点。

///

[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版本太老了,但我又必须要用这个版本,就这样。

相关链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。