summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@python.org>2020-06-29 22:49:03 (GMT)
committerGitHub <noreply@github.com>2020-06-29 22:49:03 (GMT)
commitdd8a93e23b5c4f9290e1cea6183d97eb9b5e61c0 (patch)
treee313d1898cff674146bfb1fef17f082f56628ed4
parent2fb5f038f2a2e91a7293d62dfd5601e6eb500c55 (diff)
downloadcpython-dd8a93e23b5c4f9290e1cea6183d97eb9b5e61c0.zip
cpython-dd8a93e23b5c4f9290e1cea6183d97eb9b5e61c0.tar.gz
cpython-dd8a93e23b5c4f9290e1cea6183d97eb9b5e61c0.tar.bz2
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.
-rw-r--r--Doc/c-api/init_config.rst21
-rw-r--r--Doc/library/sys.rst12
-rw-r--r--Doc/whatsnew/3.10.rst13
-rw-r--r--Include/cpython/initconfig.h14
-rw-r--r--Lib/test/test_embed.py40
-rw-r--r--Lib/test/test_sys.py20
-rw-r--r--Misc/NEWS.d/next/Library/2020-06-08-18-59-16.bpo-23427.ilg1Cz.rst2
-rw-r--r--Python/initconfig.c16
-rw-r--r--Python/sysmodule.c1
9 files changed, 104 insertions, 35 deletions
diff --git a/Doc/c-api/init_config.rst b/Doc/c-api/init_config.rst
index 9b0728d..84064d9 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 880f252..d201d70 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 0c4ff02..a755d26 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 5b05eab..bbe8387 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 fe47289..174892a 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 194128e..aaba663 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 0000000..3738297
--- /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 9616945..86285c7 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 f3b5a6a..9fcdb5d 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);