经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Python » 查看文章
C#/.net程序调用python
来源:cnblogs  作者:步、步、为营  时间:2023/3/3 8:53:33  对本文有异议

C#/.net程序调用python

C#的优势在于window下的开发,不仅功能强大而且开发周期短。而python则有众多的第三方库,可以避免自己造轮子,利用C#来做界面,而具体实现使用python来实现可以大大提高开发效率。本文介绍如何使用pythonnet来执行python脚本,使用pythonnet既可以具有较高的交互性,又可以使用第三方python库,同时可以将程序需要的python环境及第三方库打包到软件中,避免用户进行python的环境配置。

C#调用python的常见方法

调用python常见的方法有4种

方式 优点 缺点
使用IronPython 无需安装python运行环境,交互性强,C#和python无缝连接 某些python第三方库不支持,如numpy
使用C++调用Python,然后将C++程序做成动态链接库 交互性较强 需要用户配置Python环境,实现方式复杂
利用C#命令行调用py文件 执行速度快 需要用户配置Python环境,交互性差
将python文件打包成exe进行调用 无需安装python运行环境, 执行速度慢,传递数据复杂,交互性差

可以看出4种方式均有限制,很难同时满足交互性强、可调用第三方python库、无需用户配置Python环境要求,而这几项要求恰恰是一款成熟软件所必须的。而使用pythonnet库可满足以上三点要求。

本文均在.net 6环境下测试

使用pythonnet

  1. Nuget安装pythonnet

  2. 设置Runtime.PythonDLL属性,即pythonxx.dll路径,xx为版本号

  3. 设置PythonEngine.PythonHome,即python.exe所在路径

  4. 设置PythonEngine.PythonPath,python脚本所在目录,可以放置多个路径,以分号隔开,但是pathToVirtualEnv\Lib\site-packages和pathToVirtualEnv\Lib应放在最后

  5. 调用PythonEngine.Initialize();

    1. string pathToVirtualEnv = ".\\envs\\pythonnetTest";
    2. Runtime.PythonDLL = Path.Combine(pathToVirtualEnv, "python39.dll");
    3. PythonEngine.PythonHome = Path.Combine(pathToVirtualEnv, "python.exe");
    4. PythonEngine.PythonPath = $"{pathToVirtualEnv}\\Lib\\site-packages;{pathToVirtualEnv}\\Lib";
    5. PythonEngine.Initialize();
    6. //调用无参无返回值方法
    7. using (Py.GIL()) //执行python的调用应该放在using (Py.GIL())块内
    8. {
    9. //python对象应声明为dynamic类型
    10. dynamic np = Py.Import("test");
    11. np.hello();
    12. }
    13. //调用有参有返回值方法
    14. using (Py.GIL())
    15. {
    16. dynamic np = Py.Import("test");
    17. int r = np.add(1, 2);
    18. Console.WriteLine($"计算结果{r}");
    19. }

python文件,必须放在PythonEngine.PythonPath设定的目录下

  1. def hello():
  2. print("hello")
  3. def add(a,b):
  4. return a+b

嵌入Python环境及使用第三方库

程序中包含Python脚本所需要的所有环境以及第三方库可以免去用户的自定义配置。本文使用Anaconda来构建专用的虚拟环境。

  1. 创建专用虚拟环境(windows下首先切换到要建立虚拟环境的根目录下),执行conda create --prefix=F:\condaenv\env_name python=3.7 路径及python版本根据需要自定义。

  2. 使用Anaconda Prompt,激活虚拟环境conda activate F:\condaenv\env_name

  3. 本次测试第三方库Numpy(如果需要其他库,安装方法相同),安装Numpypip install numpy

    1. string pathToVirtualEnv = ".\\envs\\pythonnetTest";
    2. Runtime.PythonDLL = Path.Combine(pathToVirtualEnv, "python39.dll");
    3. PythonEngine.PythonHome = Path.Combine(pathToVirtualEnv, "python.exe");
    4. PythonEngine.PythonPath = $"{pathToVirtualEnv}\\Lib\\site-packages;{pathToVirtualEnv}\\Lib";
    5. PythonEngine.Initialize()
    6. //使用第三方库
    7. using (Py.GIL())
    8. {
    9. dynamic np = Py.Import("numpy");
    10. Console.WriteLine(np.cos(np.pi * 2));
    11. dynamic sin = np.sin;
    12. Console.WriteLine(sin(5));
    13. double c = (double)(np.cos(5) + sin(5));
    14. Console.WriteLine(c);
    15. dynamic a = np.array(new List<float> { 1, 2, 3 });
    16. Console.WriteLine(a.dtype);
    17. dynamic b = np.array(new List<float> { 6, 5, 4 }, dtype: np.int32);
    18. Console.WriteLine(b.dtype);
    19. Console.WriteLine(a * b);
    20. Console.ReadKey();
    21. }

    image-20230301123243892

    注意:C#和python对象进行数学运算时,必须将Python对象放到前面,例如np.pi*2,不能是2*np.pi

传递对象

可以将C#对象传递到python中

在C#中定义对象

  1. public class Person
  2. {
  3. public Person(string firstName, string lastName)
  4. {
  5. FirstName = firstName;
  6. LastName = lastName;
  7. }
  8. public string FirstName { get; set; }
  9. public string LastName { get; set; }
  10. }
  1. string pathToVirtualEnv = ".\\envs\\pythonnetTest";
  2. Runtime.PythonDLL = Path.Combine(pathToVirtualEnv, "python39.dll");
  3. PythonEngine.PythonHome = Path.Combine(pathToVirtualEnv, "python.exe");
  4. PythonEngine.PythonPath = $"{pathToVirtualEnv}\\Lib\\site-packages;{pathToVirtualEnv}\\Lib";
  5. PythonEngine.Initialize();
  6. //将C#中定义的类型传入python
  7. using (Py.GIL())
  8. {
  9. Person p = new Person("John", "Smith");
  10. PyObject pyPerson = p.ToPython();
  11. string r1 = test.FullName(pyPerson);
  12. Console.WriteLine($"全名:{r1}");
  13. }

python脚本

  1. def FullName(p):
  2. return p.FirstName+""+p.LastName

image-20230301140858858

调用pyd文件

pyd文件主要有以下2点作用:

  1. 安全性更高:通过pyd生成的文件,已变成了dll文件,无法查看源码
  2. 编译成pyd后,性能会有提升

将.py文件编译成pyd文件步骤如下:

  1. pip install cython
  2. 在.py文件目录下创建setup.py文件
  1. from distutils.core import setup
  2. from Cython.Build import cythonize
  3. setup(
  4. name = "testName",
  5. ext_modules = cythonize("test.py"), #将test.py文件编译成pyd
  6. )
  1. 执行编译命令

python setup.py build_ext --inplace

最后生成的pyd文件一般是test+cpython版本-平台为文件名,可以重命名为test名称,也可以不管,使用时仍然可以按test调用。

调动pyd文件和调用py文件相同,但是执行效率大大增强,下文会对执行速度进行对比。

执行速度对比

在test.py中定义一个耗时函数

  1. import time
  2. def Count():
  3. start = time.perf_counter()
  4. sum = 0
  5. for i in range(10000):
  6. for j in range(10000):
  7. sum = sum + i + j
  8. print("sum = ", sum)
  9. end = time.perf_counter()
  10. runTime = end - start
  11. runTime_ms = runTime * 1000
  12. print("运行时间:", runTime, "秒")
  • 直接执行test.py脚本,运行结果如下:

image-20230301144439558

  • 在C#中调用Conut()函数
  1. //运行时间测试
  2. Console.WriteLine("C#开始计时");
  3. Stopwatch stopWatch = new Stopwatch();
  4. stopWatch.Start();
  5. test.Count();
  6. stopWatch.Stop();
  7. Console.WriteLine($"C#计时结束{stopWatch.ElapsedMilliseconds}");

执行结果如下:

image-20230301144923477

可以看到,使用pythonnet调用python脚本会有一定的性能损失,不过在对性能要求不是十分高的条件下是可以接受的。

  • 执行test.pyd文件,运行结果如下:

image-20230301145141422

从结果可以看出调用pyd比原生的py文件执行还要快,所以可以使用pythonnet来执行pyd文件,即实现代码保护又提升了执行效率。

原文链接:https://www.cnblogs.com/qsnn/p/17168273.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号