在运行时修改PYTHONPATH是否可能?

11
我有一个使用Python解释器动态链接的C++应用程序。我希望能够从特定目录导入Python模块。我想修改我的进程的PYTHONPATH,以便sys.path包括我添加到PYTHONPATH中的路径。根据这份文档,这似乎是它的工作方式:http://docs.python.org/c-api/intro.html#embedding-python
但是,当我在Python代码中打印sys.path时,它显示的是原始的PYTHONPATH内容,而不是我设置的内容。下面是我正在做的示例代码(使用Boost.Python):
int main(int argc, char* argv[])
{
  _putenv_s("PYTHONPATH", "C:\\source\\\\modules");
  Py_Initialize();
  object main = import("__main__");
  object global = (main.attr("__dict__"));
  exec("import sys\nprint sys.path"), global, global);
}

PS - 我知道有其他方法可以实现我的目标,但这不是我所询问的。我想知道为什么 Py_Initialize() 在设置 sys.path 时不使用当前 PYTHONPATH 的值。也许我误解了它应该如何工作?


看起来应该可以工作。(尽管您可能希望在源代码和模块之间使用2个而不是4个反斜杠。)sys.path的值打印出来是什么? - metamatt
['C:\source\test','C:\windows\system32\python27.zip', 'C:\Python27\Lib', 'C:\Python27\DLLs', 'C:\Python27\lib\lib-tk', 'C:\Python27', 'C:\Python27\lib\site-packages']请注意,我的系统PYTHONPATH设置为C:\ source \ test,而我的代码将其更改为C:\ source \ modules。因此,sys.path获取原始值。 - Skrymsli
你检查过 _putenv_s 是否成功,即返回值为0吗? - Troubadour
奇怪。我不知道嵌入式Python解释器如何能够看到在此过程中更改之前的环境变量,假设您成功更改了它们。两种不太可能的可能性:putenv失败,或者putenv仅影响环境的CRT副本,并且由SetEnvironmentVariable维护环境的单独Win32副本,Python解释器正在使用该副本。http://lists.boost.org/Archives/boost/2002/01/23504.php表示putenv应该同时更改,但请尝试SetEnvironmentVariable并查看是否有帮助? - metamatt
2
由于您正在嵌入Python,因此应该能够直接调整sys.path,而不使用环境变量方法。 - Keith
显示剩余3条评论
7个回答

13

我找到了一个跨平台的解决方案。在调用其他Python代码之前,只需执行以下Python代码:

import sys
sys.path.append("C:\\source\\\\modules")

6
这对我非常有效!在boost::python中,只需要执行import("sys").attr("path").attr("append")("path/to/files")。 - sirbrialliance
我相信你可以用C++来实现这个:PyRun_SimpleString("import sys; sys.path.append('/some/path')\n");唯一的优点是它将魔法留在了嵌入Python的C++代码中(同时也方便了Python中的路径操作)。 - demented hedgehog
@dementedhedgehog- 嗯,我提到了这种方法是跨平台的,这意味着(至少对我来说)在C++上处理路径比在Python上更困难。 - Dewfy
@dewfy 我同意你的观点,Python路径操作比C++更容易。如果我没有表达清楚,我向你道歉。顺便说一下,我使用Py_SetProgramName(argv[0])解决了这个问题。它会为你添加dirname(prog)到PYTHONPATH中。 - demented hedgehog

3
如果同时使用多个C运行时库,则会出现此问题。在这种情况下,您的应用程序和Python DLL可能链接到不同的CRT。每个CRT都有自己的一组环境变量;使用putenv从一个CRT进行的环境更改对于使用不同CRT进行的getenv调用是不可见的。
请参见http://msdn.microsoft.com/en-us/library/ms235460%28v=vs.80%29.aspx中的“readEnv”示例。
您可以通过确保仅使用单个CRT来解决此问题,但实际上这很棘手。程序的调试构建通常使用调试CRT(启用堆检查和API断言等功能);即使在调试中使用,生产DLL通常也使用MSVCRT,即生产线程安全版本。我通过完全禁用调试CRT、将所有构建设置为“多线程动态”,以及维护单独的调试DLL过于麻烦来解决这个问题。这样做会失去一些调试能力。

是的,正如我在之前的评论中所怀疑的那样,这正是发生的事情。 - Skrymsli

3

请查看:

void PySys_SetPath(char *path) 将sys.path设置为路径列表,这些路径在path中被发现,path应该是一个由平台搜索路径分隔符(:在Unix上,;在Windows上)分隔的路径列表。

或者

Py_SetProgramName(argv[0]); 这将为您添加dirname(argv[0])到PYTHONPATH。


2

正如其他人所说,您可能遇到了CRT不匹配的问题。我能够在Python 2.6和Visual C++ 2008下使其工作:

#include "stdafx.h"
#include "Python.h"

int _tmain(int argc, _TCHAR* argv[])
{
  _putenv_s("PYTHONPATH", "C:\\source\\\\modules");
  Py_Initialize();
  PyRun_SimpleString("import sys\nprint sys.path");
  PyRun_SimpleString("raw_input()");
  return 0;
}

这个输出:

['C:\\Python26\\lib\\site-packages\\distribute-0.6.13-py2.6.egg', 'C:\\Python26\
\lib\\site-packages\\virtualenv-1.4.9-py2.6.egg', 'C:\\source\\modules', ...

另一个选择可能是切换到该目录,因为当前目录通常会出现在路径中,例如:

    _chdir("c:\\");
    Py_Initialize();
[...]

这给了我:

['C:\\Python26\\lib\\site-packages\\distribute-0.6.13-py2.6.egg', 'C:\\Python26\
\lib\\site-packages\\virtualenv-1.4.9-py2.6.egg', 'C:\\Windows\\system32\\python
26.zip', 'C:\\Python26\\Lib', 'C:\\Python26\\DLLs', 'C:\\Python26\\Lib\\lib-tk',
 'c:\\', ...

2
您可以直接执行 python3 -c "import sys; sys.path.append('C:\Path\To\Modules')"

这是一个13:37的回答。 - Skrymsli

1
#include "Python.h"
int main()
{
  Py_Initialize();
  PyRun_SimpleString("import sys");
  PyRun_SimpleString("sys.path.append(\"<some_path>\")");
  return 0;
}

这适用于所有的Python版本(2.6, 2.7, 3.1, 3.2, 3.3和3.4)。 关于 <some_path> 的几点说明:
  • 它应该只包含一个目录。具有有效分隔符的目录列表(d:/path1;d:/path2等)不起作用。
  • 仅对 Python 3 之前的版本,像这样的 Windows 路径:d:\\path1 才会生效,对于更高版本的 Python,则应使用 d:\\\\path1。建议将 Windows 路径分隔符替换为 Unix 分隔符。以下代码块执行此操作。

    std::string my_path = "<some_path>"; std::replace(my_path.begin(), my_path.end(), '\\', '/');

温馨提示: 如果要支持不同的 Python 版本,请不要浪费时间尝试使用以下任一 API 方法修改 PYTHONPATH

  • Py_SetPythonHome() - 对于 Python 2 需要使用 ASCII 字符串,对于 Python 3 则需要使用 Unicode 字符串。但是在 3.1 版本之后可能不太可靠。
  • Py_SetPath() - 在 Python 3 中引入,但存在错误(参见 http://bugs.python.org/issue11320

总的来说,在 Py_Initialize() 调用之后,上述 API 方法不会再起作用。


1

当Python DLL被加载时,它可能会获得自己的环境副本。尝试在更改环境后使用LoadLibrary和GetProcAddress加载它,看看是否有任何变化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接