当前位置:   article > 正文

C# .Net通过pythonnet调用python pyd文件

pythonnet

开发环境:windows, python310, dotnet 6.0

说明:python文件编译成pyd

1.新建控制台应用程序

2.添加nuget

3.C#调用代码

  1. using Python.Runtime;
  2. Runtime.PythonDLL= @"D:\Programs\Python\Python310\python310.dll";
  3. PythonEngine.Initialize();
  4. using (Py.GIL())
  5. {
  6. dynamic np = Py.Import("PythonTest");
  7. var dd = np.cal("aa");
  8. Console.ReadLine();
  9. }

 调试可以看到python脚本返回的代码。

注意:请将PythonDLL路径改为自己的python安装路径;PythonTest为编译好的pyd文件,请将该文件复制到控制台程序debug目录,或者复制到控制台程序里面,将其属性复制到输出目录改为始终复制。

4.附录python脚本

  1. def cal(param):
  2. return f'this is from python program, and the parameter is {param}'

******************************************2022-10-13 更新*************************************************

对于简单的py文件上面的方法可以很容易执行,但是对于引用外部package,比如pandas, numpy等等,我们调用的时候会抛出异常 “未找到相应的模块”。仔细一想就会知道,我们只打包了单个py文件,它所依赖的package当然会找不到。下面就是来解决这个问题。

我所测试的环境python版本变成了python 3.8.10。

我们只需要将下面代码放在using(Py.GIL())之前就可以:

  1. string pathToVirtualEnv = @"D:\Programs\Python\Python3.8.10";
  2. Environment.SetEnvironmentVariable("PATH", pathToVirtualEnv, EnvironmentVariableTarget.Process);
  3. Environment.SetEnvironmentVariable("PYTHONHOME", pathToVirtualEnv, EnvironmentVariableTarget.Process);
  4. Environment.SetEnvironmentVariable("PYTHONPATH", $"{pathToVirtualEnv}\\Lib\\site-packages;{pathToVirtualEnv}\\Lib", EnvironmentVariableTarget.Process);
  5. Runtime.PythonDLL= @"D:\Programs\Python\Python3.8.10\python38.dll";
  6. PythonEngine.PythonHome = pathToVirtualEnv;
  7. PythonEngine.PythonPath = PythonEngine.PythonPath + ";" + Environment.GetEnvironmentVariable("PYTHONPATH", EnvironmentVariableTarget.Process);
  8. PythonEngine.Initialize();

在你执行的过程中如果抛出找不到对应包的异常,你需要在PyCharm里面执行pip install <package-name>。这个命令会将package安装在我们的python安装目录下的Lib\site-package下。

当然你也可以使用指定的路径,具体方法可以参考下面的链接:

Setting Virtual Environment while Embedding Python in C#

Using Python.NET with Virtual Environments

不管哪一种,最重要的是需要在代码里配置PYTHONPATH, 让我们的程序可以有地方去找package。

比如.net core项目,我复制了python安装包的DLLs, Lib文件夹和python38.dll文件到bin\\dubug\\net6.0\\python\python3.8。

只需要将上面代码的pathToVirtualEnv改成新的路径即可:

  1. //string pathToVirtualEnv = @"D:\Programs\Python\Python3.8.10"
  2. string pathToVirtualEnv = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location), "Python\\Python3.8");


踩了不少坑,目前稳定运行在生产环境,代码贴出来,可以直接用。

1. ICallPyService, 定义接口

  1. /// <summary>
  2. /// 调用python模块服务
  3. /// </summary>
  4. public interface ICallPyService
  5. {
  6. /// <summary>
  7. /// 执行方法
  8. /// </summary>
  9. /// <param name="model">参数model</param>
  10. /// <returns>PyObject</returns>
  11. public string Exec(ModuleInfoModel model);
  12. public void ShutDown();
  13. }

2. ModuleInfoModel ,定义了调用pyhton代码的基础信息。

  1. /// <summary>
  2. /// 模块信息
  3. /// </summary>
  4. public class ModuleInfoModel
  5. {
  6. /// <summary>
  7. /// 初始化
  8. /// </summary>
  9. public ModuleInfoModel()
  10. {
  11. Params = new List<ParamInfo>();
  12. }
  13. /// <summary>
  14. /// 导入的模块名
  15. /// </summary>
  16. public string ModuleName { get; set; }
  17. /// <summary>
  18. /// 调用的模块方法
  19. /// </summary>
  20. public string MethodName { get; set; }
  21. /// <summary>
  22. /// 参数列表
  23. /// </summary>
  24. public List<ParamInfo> Params { get; set; }
  25. }

3. ParamInfo, python参数定义(sort是为了定义参数顺序)

  1. /// <summary>
  2. /// 参数信息
  3. /// </summary>
  4. public class ParamInfo
  5. {
  6. /// <summary>
  7. /// 参数顺序
  8. /// </summary>
  9. public int Sort { get; set; }
  10. /// <summary>
  11. /// 参数值
  12. /// </summary>
  13. public object Value { get; set; }
  14. }

4. CallPyService,接口实现

  1. /// <summary>
  2. /// python service
  3. /// </summary>
  4. public class CallPyService: ICallPyService
  5. {
  6. private static string _pythonPath;
  7. /// <summary>
  8. /// 初始化
  9. /// </summary>
  10. public CallPyService()
  11. {
  12. if (!App.GetOptions<PythonInfoOptions>().IsLinux)
  13. {
  14. _pythonPath = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location), Path.Combine("Python", "Python3.8"));
  15. Environment.SetEnvironmentVariable("PATH", _pythonPath, EnvironmentVariableTarget.Process);
  16. Environment.SetEnvironmentVariable("PYTHONHOME", _pythonPath, EnvironmentVariableTarget.Process);
  17. Environment.SetEnvironmentVariable("PYTHONPATH", $"{Path.Combine(_pythonPath, "Lib", "site-packages")};{Path.Combine(_pythonPath, "Lib")}", EnvironmentVariableTarget.Process);
  18. Environment.SetEnvironmentVariable("PYTHONNET_PYDLL", $"{Path.Combine(_pythonPath, "python38.dll")}", EnvironmentVariableTarget.Process);
  19. }
  20. }
  21. /// <summary>
  22. /// 执行python模型算法
  23. /// </summary>
  24. /// <param name="model"></param>
  25. /// <returns>PyObject</returns>
  26. public string Exec(ModuleInfoModel model)
  27. {
  28. Init();
  29. string result;
  30. using (var gil = Py.GIL())
  31. {
  32. PyObject func = Py.Import(model.ModuleName);
  33. dynamic json = Py.Import("orjson");
  34. PyObject[] pyParams = model.Params.OrderBy(o => o.Sort).Select(o => (PyObject)EvalObject(o.Value, json)).ToArray();
  35. PyObject data = func.InvokeMethod(model.MethodName, pyParams);
  36. data = json.dumps(data, option: json.OPT_SERIALIZE_NUMPY).decode();
  37. result = data.ToString();
  38. }
  39. return result;
  40. }
  41. private static void Init()
  42. {
  43. lock (_lockObj)
  44. {
  45. if (!PythonEngine.IsInitialized)
  46. {
  47. if (!App.GetOptions<PythonInfoOptions>().IsLinux)
  48. {
  49. PythonEngine.PythonHome = _pythonPath;
  50. PythonEngine.PythonPath = PythonEngine.PythonPath + ";" + Environment.GetEnvironmentVariable("PYTHONPATH", EnvironmentVariableTarget.Process);
  51. }
  52. else
  53. {
  54. Runtime.PythonDLL = App.GetOptions<PythonInfoOptions>().LinuxPythonDLL;
  55. }
  56. PythonEngine.Initialize();
  57. PythonEngine.BeginAllowThreads();
  58. }
  59. }
  60. }
  61. /// <summary>
  62. ///
  63. /// </summary>
  64. public void ShutDown()
  65. {
  66. PythonEngine.Shutdown();
  67. }
  68. /// <summary>
  69. /// 将C#对象转成PyObject
  70. /// </summary>
  71. /// <param name="value">object</param>
  72. /// <param name="orjson">orjson</param>
  73. /// <returns>PyObject</returns>
  74. private PyObject EvalObject(object value, dynamic orjson)
  75. {
  76. if (value == null || value.ToString() == "None")
  77. {
  78. return PyObject.None;
  79. }
  80. return orjson.loads(JsonConvert.SerializeObject(value));
  81. }
  82. }

上面封装成了服务,所以不管是接口还是其他地方直接调用都很方便。后面因为打包成了docker,部署到linux,所以加了linux相关初始化的东西(主要就是路径区别),用不到Linux系统的可以自己修改代码,删掉无用信息。

另外,服务实现了ShutDown,因为执行完python代码后,内存好像没释放,如果需要,可以手动调用ShutDown来释放内存(多线程要不要执行,至少要确保所有脚本都执行完了)。

转载请注明出处,谢谢!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/478463
推荐阅读
相关标签
  

闽ICP备14008679号