bpo-23427: Add sys.orig_argv attribute (GH-20729)

Add sys.orig_argv attribute: the list of the original command line
arguments passed to the Python executable.

Rename also PyConfig._orig_argv to PyConfig.orig_argv and
document it.
This commit is contained in:
Victor Stinner 2020-06-30 00:49:03 +02:00 committed by GitHub
parent 2fb5f038f2
commit dd8a93e23b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 104 additions and 35 deletions

View File

@ -424,6 +424,8 @@ PyConfig
:c:member:`~PyConfig.argv` is empty, an empty string is added to ensure :c:member:`~PyConfig.argv` is empty, an empty string is added to ensure
that :data:`sys.argv` always exists and is never empty. 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 .. c:member:: wchar_t* base_exec_prefix
:data:`sys.base_exec_prefix`. :data:`sys.base_exec_prefix`.
@ -586,6 +588,23 @@ PyConfig
* 1: Remove assertions, set ``__debug__`` to ``False`` * 1: Remove assertions, set ``__debug__`` to ``False``
* 2: Strip docstrings * 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 .. c:member:: int parse_argv
If non-zero, parse :c:member:`~PyConfig.argv` the same way the regular 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. Get the original command line arguments, before Python modified them.
See also :c:member:`PyConfig.orig_argv` member.
Multi-Phase Initialization Private Provisional API Multi-Phase Initialization Private Provisional API
-------------------------------------------------- --------------------------------------------------

View File

@ -66,6 +66,8 @@ always available.
To loop over the standard input, or the list of files given on the To loop over the standard input, or the list of files given on the
command line, see the :mod:`fileinput` module. command line, see the :mod:`fileinput` module.
See also :data:`sys.orig_argv`.
.. note:: .. note::
On Unix, command line arguments are passed by bytes from OS. Python decodes On Unix, command line arguments are passed by bytes from OS. Python decodes
them with filesystem encoding and "surrogateescape" error handler. 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. 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 .. data:: path
.. index:: triple: module; search; path .. index:: triple: module; search; path

View File

@ -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. :func:`~glob.iglob` which allow to specify the root directory for searching.
(Contributed by Serhiy Storchaka in :issue:`38144`.) (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 Optimizations
============= =============
@ -150,10 +157,14 @@ C API Changes
New Features 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``. Previously, the result could have been an instance of a subclass of ``int``.
(Contributed by Serhiy Storchaka in :issue:`40792`.) (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 Porting to Python 3.10
---------------------- ----------------------

View File

@ -408,13 +408,15 @@ typedef struct {
Default: 0. */ Default: 0. */
int _isolated_interpreter; int _isolated_interpreter;
/* Original command line arguments. If _orig_argv is empty and _argv is /* The list of the original command line arguments passed to the Python
not equal to [''], PyConfig_Read() copies the configuration 'argv' list executable.
into '_orig_argv' list before modifying 'argv' list (if parse_argv
is non-zero). 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. */ _PyConfig_Write() initializes Py_GetArgcArgv() to this list. */
PyWideStringList _orig_argv; PyWideStringList orig_argv;
} PyConfig; } PyConfig;
PyAPI_FUNC(void) PyConfig_InitPythonConfig(PyConfig *config); 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. /* 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); PyAPI_FUNC(void) Py_GetArgcArgv(int *argc, wchar_t ***argv);
#endif /* !Py_LIMITED_API */ #endif /* !Py_LIMITED_API */

View File

@ -365,7 +365,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'program_name': GET_DEFAULT_CONFIG, 'program_name': GET_DEFAULT_CONFIG,
'parse_argv': 0, 'parse_argv': 0,
'argv': [""], 'argv': [""],
'_orig_argv': [], 'orig_argv': [],
'xoptions': [], 'xoptions': [],
'warnoptions': [], 'warnoptions': [],
@ -739,7 +739,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'pycache_prefix': 'conf_pycache_prefix', 'pycache_prefix': 'conf_pycache_prefix',
'program_name': './conf_program_name', 'program_name': './conf_program_name',
'argv': ['-c', 'arg2'], 'argv': ['-c', 'arg2'],
'_orig_argv': ['python3', 'orig_argv': ['python3',
'-W', 'cmdline_warnoption', '-W', 'cmdline_warnoption',
'-X', 'cmdline_xoption', '-X', 'cmdline_xoption',
'-c', 'pass', '-c', 'pass',
@ -874,7 +874,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
} }
config = { config = {
'argv': ['script.py'], 'argv': ['script.py'],
'_orig_argv': ['python3', '-X', 'dev', 'script.py'], 'orig_argv': ['python3', '-X', 'dev', 'script.py'],
'run_filename': os.path.abspath('script.py'), 'run_filename': os.path.abspath('script.py'),
'dev_mode': 1, 'dev_mode': 1,
'faulthandler': 1, 'faulthandler': 1,
@ -896,7 +896,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
"script.py"] "script.py"]
config = { config = {
'argv': argv, 'argv': argv,
'_orig_argv': argv, 'orig_argv': argv,
'isolated': 0, 'isolated': 0,
} }
self.check_all_configs("test_preinit_dont_parse_argv", config, preconfig, self.check_all_configs("test_preinit_dont_parse_argv", config, preconfig,
@ -975,7 +975,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'ignore:::sysadd_warnoption', 'ignore:::sysadd_warnoption',
'ignore:::config_warnoption', 'ignore:::config_warnoption',
], ],
'_orig_argv': ['python3', 'orig_argv': ['python3',
'-W', 'ignore:::cmdline_warnoption', '-W', 'ignore:::cmdline_warnoption',
'-X', 'cmdline_xoption'], '-X', 'cmdline_xoption'],
} }
@ -986,7 +986,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'print(json.dumps(_testinternalcapi.get_configs()))') 'print(json.dumps(_testinternalcapi.get_configs()))')
config = { config = {
'argv': ['-c', 'arg2'], 'argv': ['-c', 'arg2'],
'_orig_argv': ['python3', '-c', code, 'arg2'], 'orig_argv': ['python3', '-c', code, 'arg2'],
'program_name': './python3', 'program_name': './python3',
'run_command': code + '\n', 'run_command': code + '\n',
'parse_argv': 1, 'parse_argv': 1,
@ -998,7 +998,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'print(json.dumps(_testinternalcapi.get_configs()))') 'print(json.dumps(_testinternalcapi.get_configs()))')
config = { config = {
'argv': ['-c', 'arg2'], 'argv': ['-c', 'arg2'],
'_orig_argv': ['python3', 'orig_argv': ['python3',
'-c', code, '-c', code,
'arg2'], 'arg2'],
'program_name': './python3', 'program_name': './python3',
@ -1014,7 +1014,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
config = { config = {
'parse_argv': 1, 'parse_argv': 1,
'argv': ['-c', 'arg1', '-v', 'arg3'], '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', 'program_name': './argv0',
'run_command': 'pass\n', 'run_command': 'pass\n',
'use_environment': 0, 'use_environment': 0,
@ -1028,7 +1028,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
config = { config = {
'parse_argv': 0, 'parse_argv': 0,
'argv': ['./argv0', '-E', '-c', 'pass', 'arg1', '-v', 'arg3'], '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', 'program_name': './argv0',
} }
self.check_all_configs("test_init_dont_parse_argv", config, pre_config, self.check_all_configs("test_init_dont_parse_argv", config, pre_config,
@ -1316,7 +1316,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
'faulthandler': 1, 'faulthandler': 1,
'bytes_warning': 1, 'bytes_warning': 1,
'warnoptions': warnoptions, 'warnoptions': warnoptions,
'_orig_argv': ['python3', 'orig_argv': ['python3',
'-Wignore:::cmdline1', '-Wignore:::cmdline1',
'-Wignore:::cmdline2'], '-Wignore:::cmdline2'],
} }

View File

@ -434,6 +434,11 @@ class SysModuleTest(unittest.TestCase):
def test_attributes(self): def test_attributes(self):
self.assertIsInstance(sys.api_version, int) self.assertIsInstance(sys.api_version, int)
self.assertIsInstance(sys.argv, list) 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.assertIn(sys.byteorder, ("little", "big"))
self.assertIsInstance(sys.builtin_module_names, tuple) self.assertIsInstance(sys.builtin_module_names, tuple)
self.assertIsInstance(sys.copyright, str) self.assertIsInstance(sys.copyright, str)
@ -930,6 +935,21 @@ class SysModuleTest(unittest.TestCase):
out = out.decode('ascii', 'replace').rstrip() out = out.decode('ascii', 'replace').rstrip()
self.assertEqual(out, 'mbcs replace') 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 @test.support.cpython_only
class UnraisableHookTest(unittest.TestCase): class UnraisableHookTest(unittest.TestCase):

View File

@ -0,0 +1,2 @@
Add :data:`sys.orig_argv` attribute: the list of the original command line
arguments passed to the Python executable.

View File

@ -601,7 +601,7 @@ PyConfig_Clear(PyConfig *config)
CLEAR(config->run_filename); CLEAR(config->run_filename);
CLEAR(config->check_hash_pycs_mode); CLEAR(config->check_hash_pycs_mode);
_PyWideStringList_Clear(&config->_orig_argv); _PyWideStringList_Clear(&config->orig_argv);
#undef CLEAR #undef CLEAR
} }
@ -856,7 +856,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
COPY_ATTR(pathconfig_warnings); COPY_ATTR(pathconfig_warnings);
COPY_ATTR(_init_main); COPY_ATTR(_init_main);
COPY_ATTR(_isolated_interpreter); COPY_ATTR(_isolated_interpreter);
COPY_WSTRLIST(_orig_argv); COPY_WSTRLIST(orig_argv);
#undef COPY_ATTR #undef COPY_ATTR
#undef COPY_WSTR_ATTR #undef COPY_WSTR_ATTR
@ -957,7 +957,7 @@ config_as_dict(const PyConfig *config)
SET_ITEM_INT(pathconfig_warnings); SET_ITEM_INT(pathconfig_warnings);
SET_ITEM_INT(_init_main); SET_ITEM_INT(_init_main);
SET_ITEM_INT(_isolated_interpreter); SET_ITEM_INT(_isolated_interpreter);
SET_ITEM_WSTRLIST(_orig_argv); SET_ITEM_WSTRLIST(orig_argv);
return dict; return dict;
@ -1864,8 +1864,8 @@ _PyConfig_Write(const PyConfig *config, _PyRuntimeState *runtime)
preconfig->use_environment = config->use_environment; preconfig->use_environment = config->use_environment;
preconfig->dev_mode = config->dev_mode; preconfig->dev_mode = config->dev_mode;
if (_Py_SetArgcArgv(config->_orig_argv.length, if (_Py_SetArgcArgv(config->orig_argv.length,
config->_orig_argv.items) < 0) config->orig_argv.items) < 0)
{ {
return _PyStatus_NO_MEMORY(); return _PyStatus_NO_MEMORY();
} }
@ -2501,11 +2501,11 @@ PyConfig_Read(PyConfig *config)
config_get_global_vars(config); config_get_global_vars(config);
if (config->_orig_argv.length == 0 if (config->orig_argv.length == 0
&& !(config->argv.length == 1 && !(config->argv.length == 1
&& wcscmp(config->argv.items[0], L"") == 0)) && 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(); return _PyStatus_NO_MEMORY();
} }
} }
@ -2589,7 +2589,7 @@ PyConfig_Read(PyConfig *config)
assert(config->check_hash_pycs_mode != NULL); assert(config->check_hash_pycs_mode != NULL);
assert(config->_install_importlib >= 0); assert(config->_install_importlib >= 0);
assert(config->pathconfig_warnings >= 0); assert(config->pathconfig_warnings >= 0);
assert(_PyWideStringList_CheckConsistency(&config->_orig_argv)); assert(_PyWideStringList_CheckConsistency(&config->orig_argv));
status = _PyStatus_OK(); status = _PyStatus_OK();

View File

@ -2931,6 +2931,7 @@ _PySys_InitMain(PyThreadState *tstate)
} }
COPY_LIST("argv", config->argv); COPY_LIST("argv", config->argv);
COPY_LIST("orig_argv", config->orig_argv);
COPY_LIST("warnoptions", config->warnoptions); COPY_LIST("warnoptions", config->warnoptions);
PyObject *xoptions = sys_create_xoptions_dict(config); PyObject *xoptions = sys_create_xoptions_dict(config);