I try to understand a functionality (or bug ?) of python's argparse.
Here, my simple code:
import argparse
parser = argparse.ArgumentParser(usage="%(prog)s [--start]", add_help=False)
parser.add_argument("--start", help="Start prog", action="store_true")
arguments = parser.parse_args()
start_fpc = arguments.start
print arguments
When I execute this script, both start and star arguments are accepted:
[ rsenet 2015-08-28 12:39:50] /tmp
$ python test.py --star
Namespace(start=True)
[ rsenet 2015-08-28 13:59:16] /tmp
$ python test.py --start
Namespace(start=True)
Any idea why? If yes, is it possible to disable this function?
You need to disable allow_abbrev option, which was introduced in v3.5.
Excerpt from argparse doc:
allow_abbrev_ - Allows long options to be abbreviated if the
abbreviation is unambiguous. (default: True)
This should help:
parser = argparse.ArgumentParser(usage="%(prog)s [--start]",
allow_abbrev=False,
add_help=False)
Related
This script will print env vars.
Using Python 3.9.
The goal is to able to run any subcommands if desired. The error I am getting is that if any additional short flags are added, the "ignore environment" arg is trying to parse it. I dont want this. Additional short flags go to anything assigned after --eval.
parser.py
import argparse, os
def parseargs(p):
p.usage = '%(prog)s [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]'
p.add_argument(
"-i",
"--ignore-environment",
action="store_const",
const=dict(),
dest="env",
help="start with an empty environment",
default=os.environ,
)
p.add_argument(
"--export",
nargs=1,
help="Set argument with --export NAME=VALUE"
)
p.add_argument(
"--eval",
nargs="+",
help="Run any commands with newly updated environment, "
"--eval COMMAND ARGS"
)
return p
Execution as follows
>>> p = argparse.ArgumentParser()
>>> parseargs(p) # assigns arguments to parser
>>> p.parse_args('--export FOO=bar --eval cat test.py'.split()) # This is ok and works correctly. cat is the bash command
Namespace([os.environs..], eval=['cat', 'test.py'], export=['FOO=bar'])
>>>p.parse_args('--export FOO=bar --eval ls -l'.split()) # This is fails
error: unrecognized arguments: -l
How do I get "-l" to be overlook by "-i/ignore environment" but passed to eval, like using cat test.py. I have tried using sub_parser but to no avail. The same result occurs.
The problem is that parse_args tries to identify possible options lexically before ever considering the semantics of any actual option.
Since an option taking a variable number of arguments pretty much has to be the last option used alway, consider making --eval a flag which is used to tell your program how to interpret the remaining positonal arguments. Then ls and -l can be offset by --, preventing parse_args from thinking -l is an undefined option.
p.add_argument(
"--eval",
action='store_true',
help="Run any commands with newly updated environment, "
)
# zero or more, so that you don't have to provide a dummy argument
# when the lack of --eval makes a command unnecessary.
# Wart: you can still use --eval without specifying any commands.
# I don't believe argparse alone is capable of handling this,
# at least not in a way that is simpler than just validating
# arguments after calling parse_args().
p.add_argument('cmd_and_args', nargs='*')
Then your command line could look like
>>> p.parse_args('--export FOO=bar --eval -- ls -l'.split())
or even
>>> p.parse_args('--eval --export FOO=bar -- ls -l'.split())
Later, you'll use the boolean value of args.eval to decide how to treat the list args.cmd_and_args.
Important: One wrinkle with this is that you are attaching these options to arbitrary pre-existing parsers, which may have their own positional arguments defined, so getting this to play nice with the original parser might be difficult, if not impossible.
The other option is to take a single argument to be parsed internally.
p.add_arguments("--eval")
...
args = p.parse_args()
cmd_and_args = shlex.split(args.eval) # or similar
Then
>>> p.parse_args(['--export', 'FOO=bar', '--eval', 'ls -l'])
(Note that using str.split isn't going to work for a command line like --export FOO=bar --eval "ls -l".)
From the Argparse documentation:
If you have positional arguments that must begin with - and don’t look like negative numbers, you can insert the pseudo-argument '--' which tells parse_args() that everything after that is a positional argument [...]
So in your case, there are no changes you can make to how you add or define the arguments, but the string you provide to be parsed should have -- preceding the arguments to the eval option, as such:
--export FOO=bar --eval ls -- -l
In Python, how can I (without reinventing the argparse wheel) support a command-line option syntax à la sed -i in which one option takes an optional argument if & only if there is no whitespace between the option and its argument?
Naïvely, I'd expect argparse to support this by setting nargs='?', like so:
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-i', dest='backup', nargs='?', const='')
>>> parser.add_argument('arg', nargs='*')
... but it doesn't:
>>> parser.parse_args(['-i', '~'])
Namespace(backup='~', arg=[]) # wanted: Namespace(backup='', arg=['~'])
What options are available to me? I'd prefer answers that work in both Python 2.7 and Python 3.3+.
Quick and dirty workaround here.
First, manually check the sys.argv[1:] and check if the option (with nargs='?', const='') is used with no optional argument (including -o posarg case that we are trying to resolve here).
If that so, modify that in a way to prevent it from consuming the trailing positional arguments, by appending = at the end of it.
Then explicitly give the modified args to the parser.parse_args(argv) function.
Following is the example script:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-i', dest='backup', nargs='?', const='')
parser.add_argument('arg', nargs='*')
print(parser.parse_args(['-i', '~'])) # gives Namespace(arg=[], backup='~')
print(parser.parse_args(['-i=', '~'])) # gives Namespace(arg=['~'], backup='')
How do I add an optional flag to my command line args?
eg. so I can write
python myprog.py
or
python myprog.py -w
I tried
parser.add_argument('-w')
But I just get an error message saying
Usage [-w W]
error: argument -w: expected one argument
which I take it means that it wants an argument value for the -w option. What's the way of just accepting a flag?
I'm finding http://docs.python.org/library/argparse.html rather opaque on this question.
As you have it, the argument w is expecting a value after -w on the command line. If you are just looking to flip a switch by setting a variable True or False, have a look here (specifically store_true and store_false)
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-w', action='store_true')
where action='store_true' implies default=False.
Conversely, you could haveaction='store_false', which implies default=True.
Adding a quick snippet to have it ready to execute:
Source: myparser.py
import argparse
parser = argparse.ArgumentParser(description="Flip a switch by setting a flag")
parser.add_argument('-w', action='store_true')
args = parser.parse_args()
print args.w
Usage:
python myparser.py -w
>> True
Your script is right. But by default is of None type. So it considers true of any other value other than None is assigned to args.argument_name variable.
I would suggest you to add a action="store_true". This would make the True/False type of flag. If used its True else False.
import argparse
parser = argparse.ArgumentParser('parser-name')
parser.add_argument("-f","--flag",action="store_true",help="just a flag argument")
usage
$ python3 script.py -f
After parsing when checked with args.f it returns true,
args = parser.parse_args()
print(args.f)
>>>true
If you are looking for a binary flag, then the argparse actions store_true or store_false provide exactly this. This approach is well explained in the accepted answer by #Jdog.
The official docs are also fairly clear. I would only complete the example with one line, so to make it very clear how the store_true/store_false act:
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('--foo', action='store_true')
>>> parser.add_argument('--fov', action='store_true') # this is not in the docs!
>>> parser.add_argument('--bar', action='store_false')
>>> parser.add_argument('--baz', action='store_false')
>>> parser.parse_args('--foo --bar'.split()) # --baz and --fov are missing
Out[4]: Namespace(bar=False, baz=True, foo=True, fov=False) # mind the fov=False
A slightly more powerful approach is to use the count action. You typically have used this type of flag already when setting the verbosity level when running a command.
For example ssh's verbose mode flag -v is a counter:
-v Verbose mode. Causes ssh to print debugging messages about its progress. This is helpful in debugging connection, authentication, and configuration problems. Multiple -v
options increase the verbosity. The maximum is 3.
So if you run ssh it's non verbose, ssh -v is slightly verbose and ssh -vvv is maximally verbose.
With argparse in python such a counter flag can be defined as follows:
parser.add_argument('--verbose', '-v', action='count', default=0)
If you want to use it as a boolena (True/False) flag, then you need to cast args.verbose into a boolean. You can either do this explicitly yourself, or rely a conditional statement like if args.verbose: ....
Here is a full working example to illustrate how you can use the counter flag:
With the script test.py:
#!/usr/bin/env python3
# test.py
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--verbose', '-v', action='count', default=0)
args = parser.parse_args()
if args.verbose:
print('verbose')
print(f'verbosity level: {args.verbose}')
else:
print('non-verbose')
You get the following outputs:
python test.py
>> non-verbose
python test.py -v
>> verbose
>> verbosity level: 1
python test.py -vvv
>> verbose
>> verbosity level: 3
Here's a quick way to do it, won't require anything besides sys.. though functionality is limited:
flag = "--flag" in sys.argv[1:]
[1:] is in case if the full file name is --flag
Here is an example code:
import argparse
parser=argparse.ArgumentParser()
parser.add_argument('-main_arg')
subparser=parser.add_subparser()
a=subparser.add_parser('run')
a.add_argument('required_sub_arg')
a.add_argument('arg_a')
b=subparser.add_parser('b')
parser.parse_args()
I want it to read in -main_arg if I enter program run required_sub_arg -main_arg -arg_a
Right now, it doesn't recognize -main_arg as a valid argument.
PSA to recent readers
As this question still has visits in 2018, before doing anything this complex with argparse, please consider using docopt or click instead. It will improve both your sanity and that of anyone who might read or modify your code. Thank you.
Original answer
As is, you have a few issues.
First, parser.parse_args is a method that returns a namespace of parser's arguments, so you should do something like
args = parser.parse_args()
Then args.main_args to get-main_arg from a call like
program -main_arg run required_sub_arg -arg_a
Your issue with main_arg is that you have created a argument to parser named main_arg, and you make a call like
program run required_sub_arg -main_arg -arg_a
that refers to an argument to a named main_arg. Since a doesn't have such an argument, it is invalid.
In order to refer to a parser's argument from one of its subparser, you have to make said subparser inherit the arguments of its parent. This is done with
a=parser.add_subparser('run', parents=[parser])
You have mistaken subparser for child parser. See http://docs.python.org/dev/py3k/library/argparse.html and https://code.google.com/p/argparse/issues/detail?id=54 for more informations.
For anyone else using argparse that arrives here looking for a way to display "common" sub-parser arguments in the "main" help screen, here's one approach:
import argparse
common = argparse.ArgumentParser(add_help=False)
common.add_argument('--shared', action='store_true', help='some shared arg')
parser = argparse.ArgumentParser(formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('--parent', action='store_true', help='parent only arg')
subparsers = parser.add_subparsers()
run = subparsers.add_parser('run', parents=[common])
run.add_argument('--fast', action='store_true', help='run only arg')
parser.epilog = "--- Arguments common to all sub-parsers ---" \
+ common.format_help().replace(common.format_usage(), '')
args = parser.parse_args()
Main help:
$ program.py -h
usage: program.py [-h] {run} ...
positional arguments:
{run}
optional arguments:
-h, --help show this help message and exit
--parent parent only arg
--- Arguments common to all sub-parsers ---
optional arguments:
--shared some shared arg
run sub-parser help:
$ program.py run -h
usage: program.py run [-h] [--shared]
optional arguments:
-h, --help show this help message and exit
--shared some shared arg
--fast run only arg
To address the actual question, since the accepted answer doesn't run for me, here's some additional information on why it doesn't seem possible to truly share argparse arguments with the same name across both parent and child/sub-parser parsers.
First, the problem with the following code:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('-main_arg')
subparsers = parser.add_subparsers()
run = subparsers.add_parser('run', parents=[parser])
args = parser.parse_args()
Is that it leads to the following error, because both parent parser and sub-parser run define the -h/--help argument (by default).
Argparse.ArgumentError: argument -h/--help: conflicting option strings: -h, --help
While this error can be avoided by suppressing the -h/--help option (with add_help=False) on either the parent or the child, it's nice to have the help option at both levels.
Another potential way to avoid conflicting help options is to move common arguments to a shared parser, common:
import argparse
common = argparse.ArgumentParser(add_help=False)
common.add_argument('-main_arg', action='store_true')
parser = argparse.ArgumentParser(parents=[common])
subparsers = parser.add_subparsers()
run = subparsers.add_parser('run', parents=[common])
args = parser.parse_args()
print(args)
While this appears to work on the surface, in practice, it doesn't work as intended:
$ program.py run # OK
Namespace(main_arg=False)
$ program.py run -main_arg # OK
Namespace(main_arg=True)
$ program.py -main_arg run # BAD: expected main_arg to be True
Namespace(main_arg=False)
The behavior observed when parsing program.py -main_arg run illustrates a key relationship: a parent argparser and its sub-parsers are independent parsers, where the parent parses all arguments up to the sub-parser "command" positional argument, and then the selected sub-parser parses the remaining arguments in the same Namespace as the parent with no regard for attributes that may have been set by the parent.
How can I make a command line, so I can execute my program on Windows with some parameters...
For example:
C:/Program/App.exe -safemode
have a look at the getopt and optparse modules from the standard lib, many good things could be also said about more advanced argparse module.
Generally you just need to access sys.argv.
I sense that you also want to generate an 'executable' that you can run standalone.... For that you use py2exe
Here is a complete example.py:
import optparse
parser = optparse.OptionParser()
parser.add_option("-s", "--safemode",
default = False,
action = "store_true",
help = "Should program run in safe mode?")
parser.add_option("-w", "--width",
type = "int",
default = 1024,
help = "Desired screen width in pixels")
options, arguments = parser.parse_args()
if options.safemode:
print "Proceeding safely"
else:
print "Proceeding dangerously"
if options.width == 1024:
print "running in 1024-pixel mode"
elif options.width == 1920:
print "running in 1920-pixel mode"
And here is a complete setup.py that will turn the above example.py into example.exe (in the dist subdirectory):
from distutils.core import setup
import py2exe
import sys
sys.argv.append('py2exe')
setup(
options = {'py2exe': dict(bundle_files=1, optimize=2)},
console = ["example.py"],
zipfile = None,
)
Are you speaking about parameter passed to a python script?
'couse you can access them by
import sys
print sys.argv
Or can use a more sophisticated getopt module.
Not a python guy (yet anyway) but my Google-fu found this assuming you meant "handling command line arguments":
http://www.faqs.org/docs/diveintopython/kgp_commandline.html
Use optparse.OptionParser.
from optparse import OptionParser
import sys
def make_cli_parser():
"""Makes the parser for the command line interface."""
usage = "python %prog [OPTIONS]"
cli_parser = OptionParser(usage)
cli_parser.add_option('-s', '--safemode', action='store_true',
help="Run in safe mode")
return cli_parser
def main(argv):
cli_parser = make_cli_parser()
opts, args = cli_parser.parse_args(argv)
if opts.safemode:
print "Running in safe mode."
else:
print "Running with the devil."
if __name__ == '__main__':
main(sys.argv[1:])
In use:
$ python opt.py
Running with the devil.
$ python opt.py -s
Running in safe mode.
$ python opt.py -h
Usage: python opt.py [OPTIONS]
Options:
-h, --help show this help message and exit
-s, --safemode Run in safe mode
Or are you just asking how to open a command line?
go to the start menu, click "run" (or just type, in Windows 7), type "cmd"
This will open up a command shell.
Given that your question is tagged python, I'm not sure it's going to be compiled into an exe, you might have to type "python (your source here).py -safemode".
The other comments addressed how to handle parameters. If you want to make your python program an exe you might want to look at py2exe.
This is not required but you mentioned App.exe and not App.py
You are asking a question that has several levels of answers.
First, command line is passed into the array sys.argv. argv is a historic name from C and Unix languages. So:
~/p$ cat > args.py
import sys
print "You have ", len(sys.argv), "arguments."
for i in range(len(sys.argv)):
print "argv[", i, "] = ", sys.argv[i]
~/p$ python args.py 34 2 2 2
You have 5 arguments.
argv[ 0 ] = args.py
argv[ 1 ] = 34
argv[ 2 ] = 2
argv[ 3 ] = 2
argv[ 4 ] = 2
This works both in MS Windows and Unix.
Second, you might be asking "How do I get nice arguments? Have it handle /help in
MS Windows or --help in Linux?"
Well, there are three choices which try to do what you want. Two, optparse and getopt are already in the standard library, while argparse is on its way. All three of these are libraries that start with the sys.argv array of strings, a description of you command line arguments, and return some sort of data structure or class from which
you can get the options you mean.
getopt does the minimal job. It does not provide "/help" or "--help".
optparse does a more detailed job. It provides "/help" and both short and long
versions of options, e.g., "-v" and "--verbose".
argparse handles the kitchen sink, including "/help", short and long commands,
and also subcommand structures, as you see in source control "git add ....", and
positional arguments.
As you move to the richer parsing, you need to give the parser more details about what you want the command line arguments to be. For example, you need to pass a long written
description of the argument if you want the --help argument to print it.
Third, you might be asking for a tool that just deals with the options from the command
line, environment variables and configuration files. Python currently has separate tools
for each of these. Perhaps I'll write a unified one, You will need:
- Command line arguments parsed by argparse, or getopt, etc.
- Environment variables, from os.environ[]
- Configuration files from ConfigFile or plistlib, etc.
and build your own answer to "what are the settings"?
Hope this fully answers your questions
One of the many ways:
import sys
print sys.argv
>>>python arg.py arg1 arg2
['arg.py', 'arg1', 'arg2']
sys.argv is a list containing all the arguments (also the name of script/program) as string.