VS2013用C#的.NET FrameWork 4.5.1 调用python文件的两种方式- Process or Pythonnet 的方式


前言

最近试着用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代码:

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

2.使用python.net包的方式

代码如下(示例):
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版本太老了,但我又必须要用这个版本,就这样。

  • 27
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千行缘始足

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值