Python Argparse: Issue with optional arguments which are negative numbers
Question:
I’m having a small issue with argparse
. I have an option xlim
which is the xrange
of a plot. I want to be able to pass numbers like -2e-5
. However this does not work – argparse
interprets this is a positional argument. If I do -0.00002
it works: argparse
reads it as a negative number. Is it possible to have able to read in -2e-3
?
The code is below, and an example of how I would run it is:
./blaa.py --xlim -2.e-3 1e4
If I do the following it works:
./blaa.py --xlim -0.002 1e4
The code:
parser.add_argument('--xlim', nargs = 2,
help = 'X axis limits',
action = 'store', type = float,
default = [-1.e-3, 1.e-3])
Whilst I can get it to work this way I would really rather be able to use scientific notation. Anyone have any ideas?
Cheers
Answers:
As already pointed out by the comments, the problem is that a -
prefix is parsed as an option instead of as an argument. One way to workaround this is change the prefix used for options with prefix_chars
argument:
#!/usr/bin/python
import argparse
parser = argparse.ArgumentParser(prefix_chars='@')
parser.add_argument('@@xlim', nargs = 2,
help = 'X axis limits',
action = 'store', type = float,
default = [-1.e-3, 1.e-3])
print parser.parse_args()
Example output:
$ ./blaa.py @@xlim -2.e-3 1e4
Namespace(xlim=[-0.002, 10000.0])
Edit: Alternatively, you can keep using -
as separator, pass xlim
as a single value and use a function in type
to implement your own parsing:
#!/usr/bin/python
import argparse
def two_floats(value):
values = value.split()
if len(values) != 2:
raise argparse.ArgumentError
values = map(float, values)
return values
parser = argparse.ArgumentParser()
parser.add_argument('--xlim',
help = 'X axis limits',
action = 'store', type=two_floats,
default = [-1.e-3, 1.e-3])
print parser.parse_args()
Example output:
$ ./blaa.py --xlim "-2e-3 1e4"
Namespace(xlim=[-0.002, 10000.0])
If you are up to modifying argparse.py
itself, you could change the negative number matcher to handle scientific notation:
In class _ActionsContainer.__init__()
self._negative_number_matcher = _re.compile(r'^-(d+.?|d*.d+)([eE][+-]?d+)?$')
Or after creating the parser, you could set parser._negative_number_matcher
to this value. This approach might have problems if you are creating groups or subparsers, but should work with a simple parser.
One workaround I’ve found is to quote the value, but adding a space. That is,
./blaa.py --xlim " -2.e-3" 1e4
This way argparse won’t think -2.e-3 is an option name because the first character is not a hyphen-dash, but it will still be converted properly to a float because float(string) ignores spaces on the left.
Here is the code that I use. (It is similar to jeremiahbuddha’s but it answers the question more directly since it deals with negative numbers.)
Put this before calling argparse.ArgumentParser()
for i, arg in enumerate(sys.argv):
if (arg[0] == '-') and arg[1].isdigit(): sys.argv[i] = ' ' + arg
Another workaround is to pass in the argument using ‘=
‘ symbol in addition to quoting the argument – i.e., --xlim="-2.3e14"
Inspired by andrewfn’s approach, I created a separate helper function to do the sys.argv
fiddling:
def _tweak_neg_scinot():
import re
import sys
p = re.compile('-\d*\.?\d*e', re.I)
sys.argv = [' ' + a if p.match(a) else a for a in sys.argv]
The regex looks for:
-
: a negative sign
\d*
: zero or more digits (for oddly formatted values like -.5e-2
or -4354.5e-6
)
\.?
: an optional period (e.g., -2e-5
is reasonable)
\d*
: another set of zero or more digits (for things like -2e-5
and -7.e-3
)
e
: to match the exponent marker
re.I
makes it match both -2e-5
and -2E-5
. Using p.match
means that it only searches from the start of each string.
If you specify the value for your option with an equals sign, argparse
will not treat it as a separate option, even if it starts with -
:
./blaa.py --xlim='-0.002 1e4'
# As opposed to --xlim '-0.002 1e4'
And if the value does not have spaces in it (or other special characters given your shell), you can drop the quotes:
./blaa.py --xlim=-0.002
See: https://www.gnu.org/software/guile/manual/html_node/Command-Line-Format.html
With this, there is no need to write your own type=
parser or redefine the prefix character from -
to @
as the accepted answer suggests.
I’m having a small issue with argparse
. I have an option xlim
which is the xrange
of a plot. I want to be able to pass numbers like -2e-5
. However this does not work – argparse
interprets this is a positional argument. If I do -0.00002
it works: argparse
reads it as a negative number. Is it possible to have able to read in -2e-3
?
The code is below, and an example of how I would run it is:
./blaa.py --xlim -2.e-3 1e4
If I do the following it works:
./blaa.py --xlim -0.002 1e4
The code:
parser.add_argument('--xlim', nargs = 2,
help = 'X axis limits',
action = 'store', type = float,
default = [-1.e-3, 1.e-3])
Whilst I can get it to work this way I would really rather be able to use scientific notation. Anyone have any ideas?
Cheers
As already pointed out by the comments, the problem is that a -
prefix is parsed as an option instead of as an argument. One way to workaround this is change the prefix used for options with prefix_chars
argument:
#!/usr/bin/python
import argparse
parser = argparse.ArgumentParser(prefix_chars='@')
parser.add_argument('@@xlim', nargs = 2,
help = 'X axis limits',
action = 'store', type = float,
default = [-1.e-3, 1.e-3])
print parser.parse_args()
Example output:
$ ./blaa.py @@xlim -2.e-3 1e4
Namespace(xlim=[-0.002, 10000.0])
Edit: Alternatively, you can keep using -
as separator, pass xlim
as a single value and use a function in type
to implement your own parsing:
#!/usr/bin/python
import argparse
def two_floats(value):
values = value.split()
if len(values) != 2:
raise argparse.ArgumentError
values = map(float, values)
return values
parser = argparse.ArgumentParser()
parser.add_argument('--xlim',
help = 'X axis limits',
action = 'store', type=two_floats,
default = [-1.e-3, 1.e-3])
print parser.parse_args()
Example output:
$ ./blaa.py --xlim "-2e-3 1e4"
Namespace(xlim=[-0.002, 10000.0])
If you are up to modifying argparse.py
itself, you could change the negative number matcher to handle scientific notation:
In class _ActionsContainer.__init__()
self._negative_number_matcher = _re.compile(r'^-(d+.?|d*.d+)([eE][+-]?d+)?$')
Or after creating the parser, you could set parser._negative_number_matcher
to this value. This approach might have problems if you are creating groups or subparsers, but should work with a simple parser.
One workaround I’ve found is to quote the value, but adding a space. That is,
./blaa.py --xlim " -2.e-3" 1e4
This way argparse won’t think -2.e-3 is an option name because the first character is not a hyphen-dash, but it will still be converted properly to a float because float(string) ignores spaces on the left.
Here is the code that I use. (It is similar to jeremiahbuddha’s but it answers the question more directly since it deals with negative numbers.)
Put this before calling argparse.ArgumentParser()
for i, arg in enumerate(sys.argv):
if (arg[0] == '-') and arg[1].isdigit(): sys.argv[i] = ' ' + arg
Another workaround is to pass in the argument using ‘=
‘ symbol in addition to quoting the argument – i.e., --xlim="-2.3e14"
Inspired by andrewfn’s approach, I created a separate helper function to do the sys.argv
fiddling:
def _tweak_neg_scinot():
import re
import sys
p = re.compile('-\d*\.?\d*e', re.I)
sys.argv = [' ' + a if p.match(a) else a for a in sys.argv]
The regex looks for:
-
: a negative sign\d*
: zero or more digits (for oddly formatted values like-.5e-2
or-4354.5e-6
)\.?
: an optional period (e.g.,-2e-5
is reasonable)\d*
: another set of zero or more digits (for things like-2e-5
and-7.e-3
)e
: to match the exponent marker
re.I
makes it match both -2e-5
and -2E-5
. Using p.match
means that it only searches from the start of each string.
If you specify the value for your option with an equals sign, argparse
will not treat it as a separate option, even if it starts with -
:
./blaa.py --xlim='-0.002 1e4'
# As opposed to --xlim '-0.002 1e4'
And if the value does not have spaces in it (or other special characters given your shell), you can drop the quotes:
./blaa.py --xlim=-0.002
See: https://www.gnu.org/software/guile/manual/html_node/Command-Line-Format.html
With this, there is no need to write your own type=
parser or redefine the prefix character from -
to @
as the accepted answer suggests.