diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -24,7 +24,7 @@ When invoking Python, you may specify any of these options:: - python [-bBdEhiOqsSuvVWx?] [-c command | -m module-name | script | - ] [args] + python [-bBdEhiOpPqsSuvVWx?] [-c command | -m module-name | script | - ] [args] The most common use case is, of course, a simple invocation of a script:: @@ -220,6 +220,26 @@ Discard docstrings in addition to the :option:`-O` optimizations. +.. cmdoption:: -p dir + + Explicitly initialize ``sys.path[0]`` to ``dir``. If used multiple times, + all but the last one are ignored. The :option:`-P` option will negate the + usage of any preceding ``-p``. + + See the entry on :data:`~sys.path` for more information on the normal + implicit behavior the interpreter regarding ``sys.path[0]``. + + .. versionadded:: 3.3 + + +.. cmdoption:: -P + + Disable initialization of ``sys.path[0]``. The :option:`-p` option will + negate the usage of any preceding ``-P``. + + .. versionadded:: 3.3 + + .. cmdoption:: -q Don't display the copyright and version messages even in interactive mode. diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -61,6 +61,39 @@ opts = eval(out.splitlines()[0]) self.assertEqual(opts, {'a': True, 'b': 'c,d=e'}) + def test_path(self): + path = tempfile.gettempdir() + + rc, out, err = assert_python_ok('-c', 'import sys; print(sys.path)') + path0 = eval(out.splitlines()[0])[0] + self.assertEqual(path0, '') + + rc, out, err = assert_python_ok('-p', path, + '-c', 'import sys; print(sys.path)') + path0 = eval(out.splitlines()[0])[0] + self.assertEqual(path0, path) + + rc, out, err = assert_python_ok('-P', + '-c', 'import sys; print(sys.path)') + path0 = eval(out.splitlines()[0])[0] + self.assertNotEqual(path0, '') + + rc, out, err = assert_python_ok('-p', path, '-P', + '-c', 'import sys; print(sys.path)') + path0 = eval(out.splitlines()[0])[0] + self.assertNotEqual(path0, path) + self.assertNotEqual(path0, '') + + rc, out, err = assert_python_ok('-P', '-p', path, + '-c', 'import sys; print(sys.path)') + path0 = eval(out.splitlines()[0])[0] + self.assertEqual(path0, path) + + rc, out, err = assert_python_ok('-p', '/tmp', '-p', path, + '-c', 'import sys; print(sys.path)') + path0 = eval(out.splitlines()[0])[0] + self.assertEqual(path0, path) + def test_run_module(self): # Test expected operation of the '-m' switch # Switch needs an argument diff --git a/Modules/main.c b/Modules/main.c --- a/Modules/main.c +++ b/Modules/main.c @@ -47,7 +47,7 @@ static int orig_argc; /* command line options */ -#define BASE_OPTS L"bBc:dEhiJm:OqRsStuvVW:xX:?" +#define BASE_OPTS L"bBc:dEhiJm:Op:PqRsStuvVW:xX:?" #define PROGRAM_OPTS BASE_OPTS @@ -72,11 +72,13 @@ -m mod : run library module as a script (terminates option list)\n\ -O : optimize generated bytecode slightly; also PYTHONOPTIMIZE=x\n\ -OO : remove doc-strings in addition to the -O optimizations\n\ +-p dir : initialize sys.path[0] to dir\n\ +-P : disable sys.path[0] initialization\n\ -q : don't print version and copyright messages on interactive startup\n\ +"; +static char *usage_3 = "\ -s : don't add user site directory to sys.path; also PYTHONNOUSERSITE\n\ -S : don't imply 'import site' on initialization\n\ -"; -static char *usage_3 = "\ -u : unbuffered binary stdout and stderr; also PYTHONUNBUFFERED=x\n\ see man page for details on internal buffering relating to '-u'\n\ -v : verbose (trace import statements); also PYTHONVERBOSE=x\n\ @@ -84,10 +86,10 @@ -V : print the Python version number and exit (also --version)\n\ -W arg : warning control; arg is action:message:category:module:lineno\n\ also PYTHONWARNINGS=arg\n\ +"; +static char *usage_4 = "\ -x : skip first line of source, allowing use of non-Unix forms of #!cmd\n\ -X opt : set implementation-specific option\n\ -"; -static char *usage_4 = "\ file : program read from script file\n\ - : program read from stdin (default; interactive mode if a tty)\n\ arg ...: arguments passed to program in sys.argv[1:]\n\n\ @@ -319,6 +321,7 @@ wchar_t *command = NULL; wchar_t *filename = NULL; wchar_t *module = NULL; + wchar_t *path0 = NULL; FILE *fp = stdin; char *p; #ifdef MS_WINDOWS @@ -460,6 +463,14 @@ /* Ignored */ break; + case 'p': + path0 = _PyOS_optarg; + break; + + case 'P': + path0 = L"\0"; + break; + /* This space reserved for other options */ default: @@ -644,7 +655,48 @@ argv[_PyOS_optind] = L"-m"; } - PySys_SetArgv(argc-_PyOS_optind, argv+_PyOS_optind); + /* if either the -p or -P options were used, path0 will not be NULL */ + PySys_SetArgvEx(argc-_PyOS_optind, argv+_PyOS_optind, (path0 == NULL)); + + /* handle the "-P" command-line option */ + if (path0 != NULL && *path0 == L'\0') { + /* nothing will be inserted to sys.path[0] */ + } + + /* handle the "-p dir" command-line option */ + else if (path0 != NULL) { + /* path0 will be inserted to sys.path[0] */ + int res; + PyObject *newpath0, *syspath; + + newpath0 = PyUnicode_FromWideChar(path0, wcslen(path0)); + if (newpath0 == NULL) { + /* XXX shouldn't this call Py_FatalError() instead? */ + PyErr_Clear(); + fprintf(stderr, "could not create string for sys.path[0]; " + "falling back to default behavior"); + PySys_SetArgv(argc-_PyOS_optind, argv+_PyOS_optind); + } + + syspath = PySys_GetObject("path"); + if (syspath == NULL) { + /* XXX shouldn't this call Py_FatalError() instead? */ + PyErr_Clear(); + fprintf(stderr, "could not get sys.path; " + "falling back to default behavior"); + PySys_SetArgv(argc-_PyOS_optind, argv+_PyOS_optind); + } + + res = PyList_Insert(syspath, 0, newpath0); + Py_DECREF(newpath0); + if (res < 0) { + /* XXX shouldn't this call Py_FatalError() instead? */ + PyErr_Clear(); + fprintf(stderr, "could not add %S to sys.path; " + "falling back to default behavior", path0); + PySys_SetArgv(argc-_PyOS_optind, argv+_PyOS_optind); + } + } if ((Py_InspectFlag || (command == NULL && filename == NULL && module == NULL)) && isatty(fileno(stdin))) {