C# 使用Python.NET执行Python脚本文件踩坑总结
- 在VS,Nuget包管理器搜索“Python.NET”,安装pythonnet包,如下图:
![]()
- C#使用Python.NET执行Python脚本文件,C#代码如下:
1 public class PythonExecuter 2 { 3 private readonly string _pythonDllPath; 4 private readonly string _workDir; 5 6 public PythonExecuter(string dllPath, string workDir) 7 { 8 _pythonDllPath = dllPath; 9 _workDir = workDir; 10 } 11 12 public async Task<bool> ExecutePythonScript(string scriptName) 13 { 14 bool result = false; 15 Runtime.PythonDLL = _pythonDllPath; 16 PythonEngine.Initialize(); 17 dynamic sys = Py.Import("sys"); 18 sys.path.append(_workDir); 19 PythonEngine.BeginAllowThreads(); 20 await Task.Run(() => 21 { 22 try 23 { 24 using (Py.GIL()) 25 { 26 Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} python脚本准备执行"); 27 dynamic testModule = Py.Import(scriptName);// 导入模块(传入py文件名即可) 28 dynamic py = testModule.main();// 执行该py脚本的main函数 29 Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} 执行python脚本成功,返回值:{py}"); 30 result = (int)py == 0; 31 } 32 } 33 catch (Exception ex) 34 { 35 Console.WriteLine(ex.ToString()); 36 } 37 }); 38 // 使用AppContext设置开关来临时启用BinaryFormatter 39 AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true); 40 PythonEngine.Shutdown(); 41 AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", false); 42 43 return result; 44 } 45 }
- 我的Python代码如下(有一个main函数,main函数调用了test函数):
![]()
- 调用和执行结果如下:
-
1 PythonExecuter executer = new(@"D:\Python\python313.dll", @"C:\Users\megarobo\PycharmProjects\pythonProject"); 2 bool result = await executer.ExecutePythonScript("pylabrobot"); 3 Console.ReadKey();
![]()
- 执行结果包括Python脚本执行的打印输出内容。
- 踩坑点说明:
- 确保在代码中设置了
Runtime.PythonDLL属性,指向正确的Python DLL文件。例如,如果你的Python版本是3.8,那么DLL文件名通常是python38.dll(Windows,Python安装根目录下); - 设置工作目录,如果你的Python脚本.py文件在指定“_workDir”目录下(这样你的.py文件才能作为模块被导入),需要调用如下代码:
1 dynamic sys = Py.Import("sys"); 2 sys.path.append(_workDir);
- using (Py.GIL())代码块的作用:在
Python.NET中,Py.GIL()用于确保在当前线程中执行Python代码时,GIL被持有,这样可以安全地调用Python代码而不会破坏Python对象的状态。using语句确保在代码块执行完毕后,GIL会被正确释放; - 如果你想在C#多线程中,使用Python对象(如上图我在Task.Run()中运行),需要调用PythonEngine.BeginAllowThreads(),否则无法达到预期效果;
- PythonEngine初始化后,执行完Python脚本后,需要释放资源。但直接调用PythonEngine.Shutdown(),会报错:BinaryFormatter serialization and deserialization are disabled within this application。这是因为 .NET 5 及更高版本出于安全考虑默认禁用了
BinaryFormatter。Python.NET在执行PythonEngine.Shutdown()时依赖于BinaryFormatter。所以我们可以使用AppContext设置开关来临时启用BinaryFormatter。
- 踩坑续!!!(实际工作中,我调用的Python脚本的方法为异步方法)
- 如果我们调用的Python代码中的方法为异步方法,需要做额外处理(Python代码修改为如下异步代码):
![]()
-
此时,我们的C#调用代码需要做如下修改:
-
1 await Task.Run(() => 2 { 3 try 4 { 5 using (Py.GIL()) 6 { 7 Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} python脚本准备执行"); 8 dynamic asyncio = Py.Import("asyncio"); // 导入异步模块 9 dynamic pylab = Py.Import(scriptName); // 导入模块(传入py文件名即可) 10 dynamic py = asyncio.run(pylab.main()); // 执行该py脚本的异步main函数 11 Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} 执行python脚本成功,返回值:{py}"); 12 result = (int)py == 0; 13 } 14 } 15 catch (PythonException ex) 16 { 17 Console.WriteLine(ex.ToString()); 18 } 19 });
我们将Python的异步编程模块:"asyncio"导入后,调用 asyncio.run(pylab.main()); 将目标模块的main方法作为参数传入,这样我们才能在python脚本执行之后拿到执行结果,相当于C#同步调用异步方法,等待方法执行完毕(否则会直接返回!!!)。





浙公网安备 33010602011771号