diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst index 9b0728d9621..84064d93ea3 100644 --- a/Doc/c-api/init_config.rst +++ b/Doc/c-api/init_config.rst @@ -424,6 +424,8 @@ PyConfig :c:member:`~PyConfig.argv` is empty, an empty string is added to ensure that :data:`sys.argv` always exists and is never empty. + See also the :c:member:`~PyConfig.orig_argv` member. + .. c:member:: wchar_t* base_exec_prefix :data:`sys.base_exec_prefix`. @@ -586,6 +588,23 @@ PyConfig * 1: Remove assertions, set ``__debug__`` to ``False`` * 2: Strip docstrings + .. c:member:: PyWideStringList orig_argv + + The list of the original command line arguments passed to the Python + executable. + + If :c:member:`~PyConfig.orig_argv` list is empty and + :c:member:`~PyConfig.argv` is not a list only containing an empty + string, :c:func:`PyConfig_Read()` copies :c:member:`~PyConfig.argv` into + :c:member:`~PyConfig.orig_argv` before modifying + :c:member:`~PyConfig.argv` (if :c:member:`~PyConfig.parse_argv` is + non-zero). + + See also the :c:member:`~PyConfig.argv` member and the + :c:func:`Py_GetArgcArgv` function. + + .. versionadded:: 3.10 + .. c:member:: int parse_argv If non-zero, parse :c:member:`~PyConfig.argv` the same way the regular @@ -982,6 +1001,8 @@ Py_GetArgcArgv() Get the original command line arguments, before Python modified them. + See also :c:member:`PyConfig.orig_argv` member. + Multi-Phase Initialization Private Provisional API -------------------------------------------------- diff --git a/Doc/library/sys.rst b/Doc/library/sys.rst index 880f252f84a..d201d7061f9 100644 --- a/Doc/library/sys.rst +++ b/Doc/library/sys.rst @@ -66,6 +66,8 @@ always available. To loop over the standard input, or the list of files given on the command line, see the :mod:`fileinput` module. + See also :data:`sys.orig_argv`. + .. note:: On Unix, command line arguments are passed by bytes from OS. Python decodes them with filesystem encoding and "surrogateescape" error handler. @@ -1037,6 +1039,16 @@ always available. deleting essential items from the dictionary may cause Python to fail. +.. data:: orig_argv + + The list of the original command line arguments passed to the Python + executable. + + See also :data:`sys.argv`. + + .. versionadded:: 3.10 + + .. data:: path .. index:: triple: module; search; path diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 0c4ff026bd2..a755d2672ae 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -110,6 +110,13 @@ Added the *root_dir* and *dir_fd* parameters in :func:`~glob.glob` and :func:`~glob.iglob` which allow to specify the root directory for searching. (Contributed by Serhiy Storchaka in :issue:`38144`.) +sys +--- + +Add :data:`sys.orig_argv` attribute: the list of the original command line +arguments passed to the Python executable. +(Contributed by Victor Stinner in :issue:`23427`.) + Optimizations ============= @@ -150,10 +157,14 @@ C API Changes New Features ------------ - The result of :c:func:`PyNumber_Index` now always has exact type :class:`int`. +* The result of :c:func:`PyNumber_Index` now always has exact type :class:`int`. Previously, the result could have been an instance of a subclass of ``int``. (Contributed by Serhiy Storchaka in :issue:`40792`.) +* Add a new :c:member:`~PyConfig.orig_argv` member to the :c:type:`PyConfig` + structure: the list of the original command line arguments passed to the + Python executable. + (Contributed by Victor Stinner in :issue:`23427`.) Porting to Python 3.10 ---------------------- diff --git a/Include/cpython/initconfig.h b/Include/cpython/initconfig.h index 5b05eab63bb..bbe83876777 100644 --- a/Include/cpython/initconfig.h +++ b/Include/cpython/initconfig.h @@ -408,13 +408,15 @@ typedef struct { Default: 0. */ int _isolated_interpreter; - /* Original command line arguments. If _orig_argv is empty and _argv is - not equal to [''], PyConfig_Read() copies the configuration 'argv' list - into '_orig_argv' list before modifying 'argv' list (if parse_argv - is non-zero). + /* The list of the original command line arguments passed to the Python + executable. + + If 'orig_argv' list is empty and 'argv' is not a list only containing an + empty string, PyConfig_Read() copies 'argv' into 'orig_argv' before + modifying 'argv' (if 'parse_argv is non-zero). _PyConfig_Write() initializes Py_GetArgcArgv() to this list. */ - PyWideStringList _orig_argv; + PyWideStringList orig_argv; } PyConfig; PyAPI_FUNC(void) PyConfig_InitPythonConfig(PyConfig *config); @@ -445,7 +447,7 @@ PyAPI_FUNC(PyStatus) PyConfig_SetWideStringList(PyConfig *config, /* Get the original command line arguments, before Python modified them. - See also PyConfig._orig_argv. */ + See also PyConfig.orig_argv. */ PyAPI_FUNC(void) Py_GetArgcArgv(int *argc, wchar_t ***argv); #endif /* !Py_LIMITED_API */ diff --git a/Lib/test/test_embed.py b/Lib/test/test_embed.py index fe47289777a..174892a22b4 100644 --- a/Lib/test/test_embed.py +++ b/Lib/test/test_embed.py @@ -365,7 +365,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'program_name': GET_DEFAULT_CONFIG, 'parse_argv': 0, 'argv': [""], - '_orig_argv': [], + 'orig_argv': [], 'xoptions': [], 'warnoptions': [], @@ -739,11 +739,11 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'pycache_prefix': 'conf_pycache_prefix', 'program_name': './conf_program_name', 'argv': ['-c', 'arg2'], - '_orig_argv': ['python3', - '-W', 'cmdline_warnoption', - '-X', 'cmdline_xoption', - '-c', 'pass', - 'arg2'], + 'orig_argv': ['python3', + '-W', 'cmdline_warnoption', + '-X', 'cmdline_xoption', + '-c', 'pass', + 'arg2'], 'parse_argv': 1, 'xoptions': [ 'config_xoption1=3', @@ -874,7 +874,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): } config = { 'argv': ['script.py'], - '_orig_argv': ['python3', '-X', 'dev', 'script.py'], + 'orig_argv': ['python3', '-X', 'dev', 'script.py'], 'run_filename': os.path.abspath('script.py'), 'dev_mode': 1, 'faulthandler': 1, @@ -896,7 +896,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): "script.py"] config = { 'argv': argv, - '_orig_argv': argv, + 'orig_argv': argv, 'isolated': 0, } self.check_all_configs("test_preinit_dont_parse_argv", config, preconfig, @@ -975,9 +975,9 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'ignore:::sysadd_warnoption', 'ignore:::config_warnoption', ], - '_orig_argv': ['python3', - '-W', 'ignore:::cmdline_warnoption', - '-X', 'cmdline_xoption'], + 'orig_argv': ['python3', + '-W', 'ignore:::cmdline_warnoption', + '-X', 'cmdline_xoption'], } self.check_all_configs("test_init_sys_add", config, api=API_PYTHON) @@ -986,7 +986,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'print(json.dumps(_testinternalcapi.get_configs()))') config = { 'argv': ['-c', 'arg2'], - '_orig_argv': ['python3', '-c', code, 'arg2'], + 'orig_argv': ['python3', '-c', code, 'arg2'], 'program_name': './python3', 'run_command': code + '\n', 'parse_argv': 1, @@ -998,9 +998,9 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'print(json.dumps(_testinternalcapi.get_configs()))') config = { 'argv': ['-c', 'arg2'], - '_orig_argv': ['python3', - '-c', code, - 'arg2'], + 'orig_argv': ['python3', + '-c', code, + 'arg2'], 'program_name': './python3', 'run_command': code + '\n', 'parse_argv': 1, @@ -1014,7 +1014,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): config = { 'parse_argv': 1, 'argv': ['-c', 'arg1', '-v', 'arg3'], - '_orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'], + 'orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'], 'program_name': './argv0', 'run_command': 'pass\n', 'use_environment': 0, @@ -1028,7 +1028,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): config = { 'parse_argv': 0, 'argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'], - '_orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'], + 'orig_argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'], 'program_name': './argv0', } self.check_all_configs("test_init_dont_parse_argv", config, pre_config, @@ -1316,9 +1316,9 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase): 'faulthandler': 1, 'bytes_warning': 1, 'warnoptions': warnoptions, - '_orig_argv': ['python3', - '-Wignore:::cmdline1', - '-Wignore:::cmdline2'], + 'orig_argv': ['python3', + '-Wignore:::cmdline1', + '-Wignore:::cmdline2'], } self.check_all_configs("test_init_warnoptions", config, preconfig, api=API_PYTHON) diff --git a/Lib/test/test_sys.py b/Lib/test/test_sys.py index 194128e5c6b..aaba6630ff4 100644 --- a/Lib/test/test_sys.py +++ b/Lib/test/test_sys.py @@ -434,6 +434,11 @@ class SysModuleTest(unittest.TestCase): def test_attributes(self): self.assertIsInstance(sys.api_version, int) self.assertIsInstance(sys.argv, list) + for arg in sys.argv: + self.assertIsInstance(arg, str) + self.assertIsInstance(sys.orig_argv, list) + for arg in sys.orig_argv: + self.assertIsInstance(arg, str) self.assertIn(sys.byteorder, ("little", "big")) self.assertIsInstance(sys.builtin_module_names, tuple) self.assertIsInstance(sys.copyright, str) @@ -930,6 +935,21 @@ class SysModuleTest(unittest.TestCase): out = out.decode('ascii', 'replace').rstrip() self.assertEqual(out, 'mbcs replace') + def test_orig_argv(self): + code = textwrap.dedent(''' + import sys + print(sys.argv) + print(sys.orig_argv) + ''') + args = [sys.executable, '-I', '-X', 'utf8', '-c', code, 'arg'] + proc = subprocess.run(args, check=True, capture_output=True, text=True) + expected = [ + repr(['-c', 'arg']), # sys.argv + repr(args), # sys.orig_argv + ] + self.assertEqual(proc.stdout.rstrip().splitlines(), expected, + proc) + @test.support.cpython_only class UnraisableHookTest(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2020-06-08-18-59-16.bpo-23427.ilg1Cz.rst b/Misc/NEWS.d/next/Library/2020-06-08-18-59-16.bpo-23427.ilg1Cz.rst new file mode 100644 index 00000000000..37382975bb4 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-06-08-18-59-16.bpo-23427.ilg1Cz.rst @@ -0,0 +1,2 @@ +Add :data:`sys.orig_argv` attribute: the list of the original command line +arguments passed to the Python executable. diff --git a/Python/initconfig.c b/Python/initconfig.c index 96169454506..86285c77e23 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -601,7 +601,7 @@ PyConfig_Clear(PyConfig *config) CLEAR(config->run_filename); CLEAR(config->check_hash_pycs_mode); - _PyWideStringList_Clear(&config->_orig_argv); + _PyWideStringList_Clear(&config->orig_argv); #undef CLEAR } @@ -856,7 +856,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2) COPY_ATTR(pathconfig_warnings); COPY_ATTR(_init_main); COPY_ATTR(_isolated_interpreter); - COPY_WSTRLIST(_orig_argv); + COPY_WSTRLIST(orig_argv); #undef COPY_ATTR #undef COPY_WSTR_ATTR @@ -957,7 +957,7 @@ config_as_dict(const PyConfig *config) SET_ITEM_INT(pathconfig_warnings); SET_ITEM_INT(_init_main); SET_ITEM_INT(_isolated_interpreter); - SET_ITEM_WSTRLIST(_orig_argv); + SET_ITEM_WSTRLIST(orig_argv); return dict; @@ -1864,8 +1864,8 @@ _PyConfig_Write(const PyConfig *config, _PyRuntimeState *runtime) preconfig->use_environment = config->use_environment; preconfig->dev_mode = config->dev_mode; - if (_Py_SetArgcArgv(config->_orig_argv.length, - config->_orig_argv.items) < 0) + if (_Py_SetArgcArgv(config->orig_argv.length, + config->orig_argv.items) < 0) { return _PyStatus_NO_MEMORY(); } @@ -2501,11 +2501,11 @@ PyConfig_Read(PyConfig *config) config_get_global_vars(config); - if (config->_orig_argv.length == 0 + if (config->orig_argv.length == 0 && !(config->argv.length == 1 && wcscmp(config->argv.items[0], L"") == 0)) { - if (_PyWideStringList_Copy(&config->_orig_argv, &config->argv) < 0) { + if (_PyWideStringList_Copy(&config->orig_argv, &config->argv) < 0) { return _PyStatus_NO_MEMORY(); } } @@ -2589,7 +2589,7 @@ PyConfig_Read(PyConfig *config) assert(config->check_hash_pycs_mode != NULL); assert(config->_install_importlib >= 0); assert(config->pathconfig_warnings >= 0); - assert(_PyWideStringList_CheckConsistency(&config->_orig_argv)); + assert(_PyWideStringList_CheckConsistency(&config->orig_argv)); status = _PyStatus_OK(); diff --git a/Python/sysmodule.c b/Python/sysmodule.c index f3b5a6afdf1..9fcdb5dbc49 100644 --- a/Python/sysmodule.c +++ b/Python/sysmodule.c @@ -2931,6 +2931,7 @@ _PySys_InitMain(PyThreadState *tstate) } COPY_LIST("argv", config->argv); + COPY_LIST("orig_argv", config->orig_argv); COPY_LIST("warnoptions", config->warnoptions); PyObject *xoptions = sys_create_xoptions_dict(config);