11

I have a C++ application dynamically linked to the Python interpreter. I want to be able to import python modules from a particular directory. I want to modify the PYTHONPATH for my process so that sys.path will include the paths that I added to the PYTHONPATH. That seems to be the way it works according to this documentation:

http://docs.python.org/c-api/intro.html#embedding-python

However, when I print sys.path from Python-land it has the original contents of PYTHONPATH and not the one I set. Here's an example of what I'm doing (using 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 - I know there are other ways to accomplish my goal, but that's not what I'm asking about. I am wondering why Py_Initialize() doesn't use the current value of PYTHONPATH when setting up sys.path. Or perhaps I've misunderstood how it is supposed to work?

Amro
  • 123,847
  • 25
  • 243
  • 454
Skrymsli
  • 5,173
  • 7
  • 34
  • 36
  • Looks like it should work. (Though you probably want 2, not 4, backslashes between source and modules.) What's it printing for the value of sys.path? – metamatt Mar 15 '11 at 21:54
  • ['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'] Note that my system PYTHONPATH is set to C:\source\test and my code changes it to C:\source\modules. So, sys.path is getting the original value. – Skrymsli Mar 15 '11 at 22:10
  • Have you checked that `_putenv_s` is succesful i.e. returns 0? – Troubadour Mar 15 '11 at 22:11
  • Strange. I can't see how the embedded python interpreter could see environment variables from before you changed them in this process, assuming you did successfully change them. 2 unlikely possibilities: putenv is failing, or putenv affects only the CRT copy of the environment and there's a separate Win32 copy of the environment maintained by SetEnvironmentVariable, which the python interpreter is using. http://lists.boost.org/Archives/boost/2002/01/23504.php says putenv should change both, but try SetEnvironmentVariable and see if it helps? – metamatt Mar 15 '11 at 22:16
  • (what do getenv and GetEnvironmentVariable show for PYTHONPATH, from C code, before and after your call to putenv?) – metamatt Mar 15 '11 at 22:17
  • Yes, I just went and looked at the Python sources and it just calls getenv. There must be another environment table in use by the python DLL. Nothing else makes sense. If I call getenv before I putenv, I get: C:\source\test after I get: C:\source\modules I think it is because the python DLL is the standard binary distribution and its probably built with some other version of visual studio and hence links some other CRT? I'll try building my own and see if that fixes it. Very strange to me. – Skrymsli Mar 15 '11 at 22:28
  • 2
    Since you are embedding Python you should be able to adjust sys.path directly and not use the environment variable method at all. – Keith Mar 16 '11 at 05:46
  • Keith, yes, there are several ways to work around it. I just wanted to understand why this method wasn't working. Thx everyone. – Skrymsli Mar 17 '11 at 19:32

7 Answers7

13

I found cross-platform solution. Before invoke any other python code just execute following python lines:

import sys
sys.path.append("C:\\source\\\\modules")
Dewfy
  • 23,277
  • 13
  • 73
  • 121
  • 6
    This worked perfectly for me! In boost::python all it takes is import("sys").attr("path").attr("append")("path/to/files") – sirbrialliance Oct 20 '12 at 17:44
  • You can do that from C++ like this I believe: PyRun_SimpleString("import sys; sys.path.append('/some/path')\n"); Only advantage is that it keeps the magic out of the embedded python and in the C++ where the embedding is being done (Also ease of path manipulation in python). – demented hedgehog Jan 26 '14 at 22:43
  • @dementedhedgehog - well, I've mention that way is cross-platform that is mean (at least for me) manipulation with path on C++ is more difficult than on Python – Dewfy Jan 26 '14 at 22:56
  • @dewfy I agree with you python path manipulation is much easier than c++ - I apologize if I didn't make myself clear. As a note I solved this problem for myself using Py_SetProgramName(argv[0]); That adds dirname(prog) to you PYTHONPATH for you. – demented hedgehog Jan 26 '14 at 23:57
3

Check out:

void PySys_SetPath(char *path) Set sys.path to a list object of paths found in path which should be a list of paths separated with the platform’s search path delimiter (: on Unix, ; on Windows).

or

Py_SetProgramName(argv[0]); That adds dirname(argv[0]) to your PYTHONPATH for you.

demented hedgehog
  • 7,007
  • 4
  • 42
  • 49
3

This happens if you're using more than one C runtime library at a time. In this case, your application and the Python DLL are probably linked against different CRTs. Each CRT has its own set of environment variables; changes to the environment made with putenv from one CRT are not visible from getenv calls made with a different CRT.

See the "readEnv" example at http://msdn.microsoft.com/en-us/library/ms235460%28v=vs.80%29.aspx.

You can fix this by making sure to use only a single CRT, but that's tricky in practice. Debug builds of programs typically use debug CRTs (which enable things like heap checks and API assertions); production DLLs, even when used in debugging, typically use MSVCRT, the production, threadsafe version. I've worked around this by disabling the debug CRTs entirely, setting all builds to "multithreaded dynamic", since maintaining separate debug DLLs is too much of a hassle. You lose some debugging capabilities by doing that.

Glenn Maynard
  • 55,829
  • 10
  • 121
  • 131
2

You could just do python3 -c "import sys; sys.path.append('C:\Path\To\Modules')"

user9311010
  • 79
  • 11
2

As other people have said, you may be running into a CRT mismatch. I was able to get this to work with Python 2.6 and 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;
}

This output:

['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', ...

Another option may be to change to that directory, since the current directory typically ends up on the path, e.g.:

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

which gives me:

['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:\\', ...
Nicholas Riley
  • 43,532
  • 6
  • 101
  • 124
1
#include "Python.h"
int main()
{
  Py_Initialize();
  PyRun_SimpleString("import sys");
  PyRun_SimpleString("sys.path.append(\"<some_path>\")");
  return 0;
}

This worked for all python version (2.6, 2.7, 3.1, 3.2, 3.3 and 3.4).
Several notes regarding <some_path>:

  • It should contain only a single directory. List of directories with valid separators (d:/path1;d:/path2 etc.) is not working
  • Windows paths like: d:\\path1 will work only for python versions prior to Python 3, for later versions d:\\\\path1 should be used. I'd advice replacing windows path delimiters with unix delimiters. Following code chunk does this.

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

A gentle advise: Don't waste your time trying to modify PYTHONPATH with either of the following API methods if you want to support different python versions:

  • Py_SetPythonHome() - for python 2 requires an ascii string, for python 3 - a unicode string, but doesn't work reliably for versions greater than 3.1
  • Py_SetPath() - introduced in python 3, but is buggy (see http://bugs.python.org/issue11320)

In general API methods listed above do not affect after Py_Initialize() call.

khkarens
  • 1,305
  • 1
  • 11
  • 16
1

It is possible that the Python DLL gets its own copy of the environment when it is loaded. Try loading it with LoadLibrary and GetProcAddress after you've changed the environment and see if that changes anything.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622