如何为Python.NET配置virtualenv虚拟环境解决模块导入报错
基于IronPython的初始尝试问题
最早尝试使用IronPython加载virtualenv虚拟环境执行Python脚本,实现代码如下:
var eng = IronPython.Hosting.Python.CreateEngine(); var scope = eng.CreateScope(); // 加载虚拟环境搜索路径 ICollection<string> searchPaths = eng.GetSearchPaths(); searchPaths.Add(@"/Users/Desktop/CSharpProjects/demo1/.venv/lib"); searchPaths.Add(@"/Users/Desktop/CSharpProjects/demo1/.venv/lib/site-packages"); searchPaths.Add(AppDomain.CurrentDomain.BaseDirectory); eng.SetSearchPaths(searchPaths); string file = @"script.py"; eng.ExecuteFile(file, scope);
运行后抛出导入异常:
Unhandled exception. IronPython.Runtime.Exceptions.ImportException: No module named 'numpy'
待执行的Python脚本在手动激活虚拟环境的终端中可正常运行,脚本内容:
import numpy as np def name(a, b=1): return np.add(a,b)
切换Python.NET后的报错问题
由于IronPython3对带C扩展的CPython第三方库兼容性极差,无法支持numpy这类库,因此改用Python.NET实现,使用的NuGet包为Pythonnet 3.0.0-preview2022-06-27预览版。
初始调用系统全局Python 3.7环境时功能正常,需要调整代码加载位于C:\envs\venv2路径的virtualenv虚拟环境,初始尝试的实现代码如下:
using Python.Runtime; using System; namespace ConsoleApp1 { public class PythonOperation { PyModule scope; public void Initialize() { Runtime.PythonDLL = @"C:\\Python37\python37.dll"; string pathToVirtualEnv = @"C:\envs\venv2"; string pathToPython = @"C:\Python37\"; Environment.SetEnvironmentVariable("PATH", pathToPython, EnvironmentVariableTarget.Process); Environment.SetEnvironmentVariable("PYTHONHOME", pathToVirtualEnv, EnvironmentVariableTarget.Process); Environment.SetEnvironmentVariable("PYTHONPATH", $"{pathToVirtualEnv}\\Lib\\site-packages;{pathToVirtualEnv}\\Lib", EnvironmentVariableTarget.Process); PythonEngine.PythonHome = pathToVirtualEnv; PythonEngine.PythonPath = Environment.GetEnvironmentVariable("PYTHONPATH", EnvironmentVariableTarget.Process); PythonEngine.Initialize(); scope = Py.CreateScope(); PythonEngine.BeginAllowThreads(); } public void Execute() { using (Py.GIL()) { } } } }
运行上述代码抛出致命初始化错误:
Fatal Python error: initfsencoding: unable to load the file system codec ModuleNotFoundError: No module named 'encodings'
No module named 'encodings'报错的核心原因是环境变量配置逻辑错误:virtualenv创建的虚拟环境本身不包含完整的Python标准库运行时,仅做了依赖隔离,PYTHONHOME直接指向虚拟环境路径会导致Python运行时找不到基础标准库模块。
正确配置逻辑如下:
- 路径参数修正:
PYTHONHOME必须指向创建虚拟环境时使用的基础Python安装根目录,不能指向虚拟环境路径;虚拟环境的site-packages路径仅作为搜索路径追加到PYTHONPATH即可。 - 修正路径转义错误:原代码中
Runtime.PythonDLL的路径字符串在@前缀下多余转义,会导致dll加载路径错误。 - PATH变量配置:不要覆盖进程原有PATH值,将基础Python根目录、基础Python下的DLLs目录、虚拟环境的Scripts目录追加到PATH头部,保证运行时依赖的dll可以正常寻址。
- 严格遵守初始化顺序:先完成所有环境变量配置、指定正确的Python dll路径,再调用
PythonEngine.Initialize()完成引擎初始化。
修正后的可运行代码:
using Python.Runtime; using System; namespace ConsoleApp1 { public class PythonOperation { private PyModule _scope; public void Initialize() { // 指定创建虚拟环境对应的基础Python的dll路径,注意逐字字符串不需要双反斜杠转义 Runtime.PythonDLL = @"C:\Python37\python37.dll"; // 配置基础Python路径、虚拟环境路径 string venvPath = @"C:\envs\venv2"; string basePythonPath = @"C:\Python37"; // 配置进程PATH变量,追加Python相关路径,不覆盖原有值 var currentPath = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process); var newPath = $"{basePythonPath};{basePythonPath}\\DLLs;{venvPath}\\Scripts;{currentPath}"; Environment.SetEnvironmentVariable("PATH", newPath, EnvironmentVariableTarget.Process); // PYTHONHOME必须指向基础Python安装根目录 Environment.SetEnvironmentVariable("PYTHONHOME", basePythonPath, EnvironmentVariableTarget.Process); // PYTHONPATH同时包含基础Python标准库路径、基础Python全局包路径、虚拟环境库路径、虚拟环境第三方包路径 string pythonPath = $"{basePythonPath}\\Lib;{basePythonPath}\\Lib\\site-packages;{venvPath}\\Lib;{venvPath}\\Lib\\site-packages"; Environment.SetEnvironmentVariable("PYTHONPATH", pythonPath, EnvironmentVariableTarget.Process); // 同步设置PythonEngine参数 PythonEngine.PythonHome = basePythonPath; PythonEngine.PythonPath = pythonPath; // 初始化引擎 PythonEngine.Initialize(); _scope = Py.CreateScope(); PythonEngine.BeginAllowThreads(); } public void Execute(string scriptPath) { using (Py.GIL()) { // 执行脚本示例 _scope.ExecFile(scriptPath); // 调用脚本内函数示例 // dynamic calcFunc = _scope.Get("name"); // var res = calcFunc(2, 3); } } } }
注意事项
- IronPython是.NET平台重新实现的Python运行时,不支持CPython生态下带C扩展的第三方库(numpy、pandas、opencv等均属于此类),这类场景必须使用Python.NET实现互操作。
- 项目编译的平台目标必须和本地安装的Python位数一致:64位Python对应x64编译目标,32位Python对应x86编译目标,否则会出现dll加载失败、入口点找不到的问题。
- Python 3.8+版本创建的虚拟环境目录层级可能存在细微差异,配置前先核对本地虚拟环境下Lib、site-packages的实际路径,按实际情况调整参数即可。
- 所有Python相关的路径配置必须在
PythonEngine.Initialize()调用前完成,初始化完成后再修改路径参数不会生效。
内容的提问来源于stack exchange,提问作者Joseph Adam

