24

I am using argparse to parse command line arguments. While going through the documentation for argparse I could only see a provision to use a different program name.

I want to be able to use the default program name without having to import sys. There is nothing in argparse, as far as I can see, that will return the program name.

import argparse

parser = argparse.ArgumentParser()
args = parser.parse_args()

print(dir(args))

And here's the output:

['__class__', '__contains__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_get_args', '_get_kwargs']

Is there any other way of retrieving the program name without having to import the sys module?

martineau
  • 119,623
  • 25
  • 170
  • 301
VSN
  • 386
  • 1
  • 2
  • 11
  • 4
    Why don't you want to import sys in this case? If you wanted, you could just import argv. `from sys import argv`. – Kevin London Aug 13 '14 at 23:24
  • Was I wrong to assume that argparse is meant to be a module that is a wrapper over sys.argv[]? Since argparse parses all the command line arguments, should it not have had the program name as well for cleaner code? Imho, it looks rather odd when sys.argv[0] suddenly butts into the code when it's argparse everywhere else. – VSN Aug 13 '14 at 23:48
  • 1
    `argparse` imports `sys` as `_sys`. So in theory you could use: `argparse._sys.argv[0]`. Note also that `argparse` gets the command line strings (that it parses) from `_sys.argv[1:]`. – hpaulj Aug 14 '14 at 02:18
  • See also: https://stackoverflow.com/a/17406225/1959808 – 0 _ Dec 25 '17 at 15:04

3 Answers3

33

ArgumentParser instances have a prog attribute which I think is what you want.

import argparse

parser = argparse.ArgumentParser()
print('parser.prog: {}'.format(parser.prog))

I discovered this by reading the module's source code in Lib/argparse.py—specifically looking at the class ArgumentParser definition. Since the attribute's name doesn't start with an underscore character, I assume it's public.

Update

I see that, nowadays at least, that the prog attribute of ArgumentParser instance is (or has been since this question was asked) documented in both Python 2's documentation and Python 3's documentation.

So, yes, it's definitely public, and in both versions, if it is not supplied as a keyword argument when creating the ArgumentParser, it defaults to prog = _os.path.basename(_sys.argv[0]) (where _os and _sys are private argparse module attributes that correspond to their non-underscore-prefixed counterparts. Note that because of the use of os.basename(), this will only be the script's filename, not the complete path to it that may (it's OS dependent) have been in sys.argv[0].

martineau
  • 119,623
  • 25
  • 170
  • 301
12

Of course the correct way would be:

>>> import sys
>>> print sys.argv[0]
    scripts/script.py

But let's assume for a moment you have a good reason that prevents you to import sys but allows you to import argparse.

martineau has done a wonderful job discovering prog, let's try it:

>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> print parser.prog
    script.py

As noted by hpaulj, this only has the filename and not the full path like sys.argv[0] because the module argparse.py is using prog = os.path.basename(sys.argv[0]).

But argparse must use sys, so it needs to be accessible in argparse namespace. Let's check it:

>>> import argparse
>>> print argparse.__dict__
    { ..., '_sys': <module 'sys' (built-in)>, ... }

Here it is! Let's try to use _sys:

>>> import argparse
>>> print argparse._sys.argv[0]
    scripts/script.py

You are using sys! Of course, but I haven't imported it, only argparse, that was the question!

Of course this has a number of contraindications:

  • You should not use variables prefixed by _ or __ of other namespaces, they are used internally.
  • You should not rely on imports of other modules, they may change.
  • You should not rely on undocumented api, they may change.

tl;dr

This was fun, but just stick to import sys until argparse releases an api to access sys.argv[0].

Community
  • 1
  • 1
enrico.bacis
  • 30,497
  • 10
  • 86
  • 115
  • 2
    Thanks for all the details! I accepted @martineau's answer, though, because it directly answers my question. – VSN Aug 14 '14 at 17:37
7

%(prog) from inside argparse help texts

This is a common use case when you want to give an example of how to use the command within the help itself.

main.py

#!/usr/bin/env python3
import argparse
parser = argparse.ArgumentParser(
        description="Do something cool. My name is: %(prog)s",
    epilog="""
This is how you do it:

    %(prog)s yourarg
""",
    formatter_class=argparse.RawTextHelpFormatter
)
parser.add_argument('somearg', help='also works here: %(prog)s')
args = parser.parse_args()

Then:

./main.py -h

gives:

usage: main.py [-h] somearg

Do something cool. My name is: main.py

positional arguments:
  somearg     also works here: main.py

optional arguments:
  -h, --help  show this help message and exit

This is how you do it:

    main.py yourarg

One advantage over sys.argv[0] is that the message stays unchanged no matter where you call it from:

cd ..
./mydir/main.py

Documented at: https://docs.python.org/3/library/argparse.html#prog

Note that the program name, whether determined from sys.argv[0] or from the prog= argument, is available to help messages using the %(prog)s format specifier.

Tested on Python 3.5.2.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985