When a script is invoked explicitly with python
, the argv
is mucked with so that argv[0]
is the path to the script being run. This is the case if invoked as python foo/bar.py
or even as python -m foo.bar
.
I need a way to recover the original argv
(ie. the one received by python
). Unfortunately, it's not as easy as prepending sys.executable
to sys.argv
because python foo/bar.py
is different than python -m foo.bar
(the implicit PYTHONPATH
differs, which can be crucial depending on your module structure).
More specifically in the cases of python foo/bar.py some other args
and python -m foo.bar some other args
, I'm looking to recover ['python', 'foo/bar.py', 'some', 'other', 'args']
and ['python', '-m', 'foo.bar', 'some', 'other', 'args']
, respectively.
I am aware of prior questions about this:
But these seem to have a misunderstanding of how shells work and the answers reflect this. I am not interested in undoing the work of the shell (eg. evaluated shell vars and functions are fine), I just want to get at the original argv
given to python
.
The only solution I've found is to use /proc/<PID>/cmdline
:
import os
with open("/proc/{}/cmdline".format(os.getpid()), 'rb') as f:
original_argv = f.read().split('\0')[:-1]
This does work, but it is Linux-only (no OSX, and Windows support seems to require installing the wmi package). Fortunately for my current use case this restriction is fine. But, it would be nice to have a cleaner, cross platform approach.
The fact that that /proc/<PID>/cmdline
approach works gives me hope that python isn't execing before it runs the script (at least not the syscall exec, but maybe the exec
builtin). I remember reading somewhere that all of this argument handling (ex. -m
) is done in pure python, not C (this is confirmed by the fact that python -m this.does.not.exist
will produce an exception that looks like it came from the runtime). So, I'd venture a guess that somewhere in pure python the original argv
is available (perhaps this requires some spelunking through the runtime initialization?).
tl;dr Is there a cross platform (builtin, preferably) way to get at the original argv
passed to python
(before it remove the python
executable and transforms -m blah
into blah.py
)?
edit From spelunking, I discovered Py_GetArgcArgv
, which can be accessed via ctypes (found it here, links to several SO posts that mention this approach):
import ctypes
_argv = ctypes.POINTER(ctypes.c_wchar_p)()
_argc = ctypes.c_int()
ctypes.pythonapi.Py_GetArgcArgv(ctypes.byref(_argc),
ctypes.byref(_argv))
argv = _argv[:_argc.value]
print(argv)
Now this is OS-portable, but not python implementation portable (only works on cpython and ctypes
is yucky if you don't need it). Also, peculiarly, I don't get the right output on Ubunutu 16.04 (python -m foo.bar
gives me ['python', '-m', '-m']
), but I may just be making a silly mistake (I get the same behavior on OSX). It would be great to have a fully portable solution (that doesn't dig into ctypes
).
This seems XY problem and you are getting into the weeds in order to accommodate some existing complicated test setup (I've found the question behind the question in your comment). Further efforts would be better spent writing a sane test setup.
For example if you decide to go with pytest runner, all the test setup can be configured within a [tool:pytest]
section of the setup.cfg
file and/or in fixture setup (conftest.py
). Overriding the default test configuration can be done with environment variables and/or command line arguments, and neither of these approaches will get mucked around by the shell or during Python interpreter startup.
The manner in which to execute the test suite can and should be as simple as executing a single command:
pytest
And then your perceived problem of needing to recover the original sys.argv
will go away.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With