20

Inspired by another question here, I would like to retrieve the Python interpreter's full command line in a portable way. That is, I want to get the original argv of the interpreter, not the sys.argv which excludes options to the interpreter itself (like -m, -O, etc.).

sys.flags tells us which boolean options were set, but it doesn't tell us about -m arguments, and the set of flags is bound to change over time, creating a maintenance burden.

On Linux you can use procfs to retrieve the original command line, but this is not portable (and it's sort of gross):

open('/proc/{}/cmdline'.format(os.getpid())).read().split('\0')
Community
  • 1
  • 1
John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • 6
    this is a great question ... As far as I can tell, this isn't possible (in CPython). It looks to me like [Py_Main](https://hg.python.org/cpython/file/2bb5fa752bfc/Modules/main.c#l236) does some parsing to get the commandline arguments, then calls [PySys_SetArgv](https://hg.python.org/cpython/file/2bb5fa752bfc/Modules/main.c#l571) with the remaining arguments and does nothing else with `*argc` and `**argv`. There is [Py_GetArgcArgv](https://hg.python.org/cpython/file/2bb5fa752bfc/Modules/main.c#l691), that you could probably hook into -- But I don't see it anywhere in the documented C-API... – mgilson Feb 05 '15 at 06:10
  • 1
    `.split('\0')` would be more correct than `.replace('\0', ' ')` -- otherwise you cannot distinguish between arguments containing a space and separate arguments. – tripleee Feb 05 '15 at 06:15

2 Answers2

13

You can use ctypes

~$ python2 -B -R -u
Python 2.7.9 (default, Dec 11 2014, 04:42:00) 
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
Persistent session history and tab completion are enabled.
>>> import ctypes
>>> argv = ctypes.POINTER(ctypes.c_char_p)()
>>> argc = ctypes.c_int()
>>> ctypes.pythonapi.Py_GetArgcArgv(ctypes.byref(argc), ctypes.byref(argv))
1227013240
>>> argc.value
4
>>> argv[0]
'python2'
>>> argv[1]
'-B'
>>> argv[2]
'-R'
>>> argv[3]
'-u'
bav
  • 1,543
  • 13
  • 13
9

I'm going to add another answer to this. @bav had the right answer for Python 2.7, but it breaks in Python 3 as @szmoore points out (not just 3.7). The code below, however, will work in both Python 2 and Python 3 (the key to that is c_wchar_p in Python 3 instead of c_char_p in Python 2) and will properly convert the argv into a Python list so that it's safe to use in other Python code without segfaulting:

def get_python_interpreter_arguments():
    argc = ctypes.c_int()
    argv = ctypes.POINTER(ctypes.c_wchar_p if sys.version_info >= (3, ) else ctypes.c_char_p)()
    ctypes.pythonapi.Py_GetArgcArgv(ctypes.byref(argc), ctypes.byref(argv))

    # Ctypes are weird. They can't be used in list comprehensions, you can't use `in` with them, and you can't
    # use a for-each loop on them. We have to do an old-school for-i loop.
    arguments = list()
    for i in range(argc.value - len(sys.argv) + 1):
        arguments.append(argv[i])

    return arguments

You'll notice that it also returns only the interpreter arguments and excludes the augments found in sys.argv. You can eliminate this behavior by removing - len(sys.argv) + 1.

Nick Williams
  • 2,864
  • 5
  • 29
  • 43
  • What OS platform and interpreter version did you use? In both tests I tried it crashed. I tested on Windows 10 using Python 3.7, and Debian 9 using Python 3.5. – JamesThomasMoon Sep 15 '19 at 04:18
  • 1
    The above runs on Python 2.7, 3.5, 3.6, and 3.7 on Ubuntu 16.04 and on macOS 10.13. I don't use Windows, so I've no way of testing on that. I basically assume all things C-related in Python work completely differently (or just don't work) in Windows. But that definitely works across what appears to be a broad range of Unix. – Nick Williams Sep 16 '19 at 11:08
  • 1
    @JamesThomasMoon works for me, on Ubuntu 20.04 with Python 3.8.10 and 2.7.18. – Lenormju Jan 20 '22 at 15:34