argparse missing optparse capabilities?

R

rurpy

I have optparse code that parses a command line containing
intermixed positional and optional arguments, where the optional
arguments set the context for the following positional arguments.
For example,

myprogram.py arg1 -c33 arg2 arg3 -c44 arg4

'arg1' is processed in a default context, 'args2' and 'arg3' in
context '33', and 'arg4' in context '44'.

I am trying to do the same using argparse but it appears to be
not doable in a documented way.

Here is the working optparse code (which took 30 minutes to write
using just the optparse docs):

import optparse
def append_with_pos (option, opt_str, value, parser):
if getattr (parser.values, option.dest, None) is None:
setattr (parser.values, option.dest, [])
getattr (parser.values, option.dest).append ((value, len
(parser.largs)))
def opt_parse():
p = optparse.OptionParser()
p.add_option ("-c", type=int,
action='callback', callback=append_with_pos)
opts, args = p.parse_args()
return args, opts
if __name__ == '__main__':
args, opts = opt_parse()
print args, opts

Output from the command line above:
['arg1', 'arg2', 'arg3', 'arg4'] {'c': [(33, 1), (44, 3)]}
The -c values are stored as (value, arglist_position) tuples.

Here is an attempt to convert to argparse using the guidelines
in the argparse docs:

import argparse
class AppendWithPos (argparse.Action):
def __call__ (self, parser, namespace, values,
option_string=None):
if getattr (namespace, self.dest, None) is None:
setattr (namespace, self.dest, [])
getattr (namespace, self.dest).extend ((values, len
(parser.largs)))
def arg_parse():
p = argparse.ArgumentParser (description='description')
p.add_argument ('src', nargs='*')
p.add_argument ('-c', type=int, action=AppendWithPos)
opts = p.parse_args()
return opts
if __name__ == '__main__':
opts = arg_parse()
print opts

This fails with,
AttributeError: 'ArgumentParser' object has no attribute 'largs'
and of course, the argparse.parser is not documented beyond how
to instantiate it. Even were that not a problem, argparse complains
about "unrecognised arguments" for any positional arguments that
occur after an optional one. I've been farting with this code for
a day now.

Any suggestions on how I can convince argparse to do what optparse
does easily will be very welcome. (I tried parse_known_args() but
that breaks help and requires me to detect truly unknown arguments.)

(Python 2.7.1 if it matters and apologies if Google mangles
the formatting of this post.)
 
R

rurpy

  class AppendWithPos (argparse.Action):
    def __call__ (self, parser, namespace, values,
option_string=None):
        if getattr (namespace, self.dest, None) is None:
            setattr (namespace, self.dest, [])
        getattr (namespace, self.dest).extend ((values, len (parser.largs)))

I realized right after posting that the above line should
be I think,
getattr (namespace, self.dest).extend ((values, len
(namespace.src)))

but that still doesn't help with the "unrecognised arguments"
problem.
 
U

Ulrich Eckhardt

Am 05.01.2012 09:05, schrieb (e-mail address removed):
I have optparse code that parses a command line containing
intermixed positional and optional arguments, where the optional
arguments set the context for the following positional arguments.
For example,

myprogram.py arg1 -c33 arg2 arg3 -c44 arg4

'arg1' is processed in a default context, 'args2' and 'arg3' in
context '33', and 'arg4' in context '44'.

Question: How would you e.g. pass the string "-c33" as first argument,
i.e. to be parsed in the default context?

The point is that you separate the parameters in a way that makes it
possible to parse them in a way that works 100%, not just a way that
works in 99% of all cases. For that reason, many commandline tools
accept "--" as separator, so that "cp -- -r -x" will copy the file "-r"
to the folder "-x". In that light, I would consider restructuring your
commandline.
I am trying to do the same using argparse but it appears to be
not doable in a documented way.

As already hinted at, I don't think this is possible and that that is so
by design.

Sorry..

Uli
 
R

rurpy

Am 05.01.2012 09:05, schrieb (e-mail address removed):

Question: How would you e.g. pass the string "-c33" as first argument,
i.e. to be parsed in the default context?

There will not be a need for that.
The point is that you separate the parameters in a way that makes it
possible to parse them in a way that works 100%, not just a way that
works in 99% of all cases.

I agree that one should strive for a syntax that "works
100%" but in this case, the simplicity and intuitiveness
of the existing command syntax outweigh by far the need
for having it work in very improbable corner cases.
(And I'm sure I've seen this syntax used in other unix
command line tools in the past though I don't have time
to look for examples now.)

If argparse does not handle this syntax for some such
purity reason (as opposed to, for example. it is hard
to do in argparse's current design) then argparse is
mistakenly putting purity before practicality.
For that reason, many commandline tools
accept "--" as separator, so that "cp -- -r -x" will copy the file "-r"
to the folder "-x". In that light, I would consider restructuring your
commandline.

In my case that's not possible since I am replacing an
existing tool with a Python application and changing the
command line syntax is not an option.
As already hinted at, I don't think this is possible and that that is so
by design.

Thanks for the confirmation. I guess that shows that
optparse has a reason to exist beyond backwards compatibility.
 
I

Ian Kelly

I have optparse code that parses a command line containing
intermixed positional and optional arguments, where the optional
arguments set the context for the following positional arguments.
For example,

 myprogram.py arg1 -c33 arg2 arg3 -c44 arg4

'arg1' is processed in a default context, 'args2' and 'arg3' in
context '33', and 'arg4' in context '44'.

I am trying to do the same using argparse but it appears to be
not doable in a documented way.

Here is the working optparse code (which took 30 minutes to write
using just the optparse docs):

 import optparse
 def append_with_pos (option, opt_str, value, parser):
       if getattr (parser.values, option.dest, None) is None:
           setattr (parser.values, option.dest, [])
       getattr (parser.values, option.dest).append ((value, len
(parser.largs)))
 def opt_parse():
       p = optparse.OptionParser()
       p.add_option ("-c", type=int,
           action='callback', callback=append_with_pos)
       opts, args = p.parse_args()
       return args, opts
 if __name__ == '__main__':
       args, opts = opt_parse()
       print args, opts

Output from the command line above:
 ['arg1', 'arg2', 'arg3', 'arg4'] {'c': [(33, 1), (44, 3)]}
The -c values are stored as (value, arglist_position) tuples.

Here is an attempt to convert to argparse using the guidelines
in the argparse docs:

 import argparse
 class AppendWithPos (argparse.Action):
   def __call__ (self, parser, namespace, values,
option_string=None):
       if getattr (namespace, self.dest, None) is None:
           setattr (namespace, self.dest, [])
       getattr (namespace, self.dest).extend ((values, len
(parser.largs)))
 def arg_parse():
       p = argparse.ArgumentParser (description='description')
       p.add_argument ('src', nargs='*')
       p.add_argument ('-c', type=int, action=AppendWithPos)
       opts = p.parse_args()
       return opts
 if __name__ == '__main__':
       opts = arg_parse()
       print opts

This fails with,
 AttributeError: 'ArgumentParser' object has no attribute 'largs'
and of course, the argparse.parser is not documented beyond how
to instantiate it.  Even were that not a problem, argparse complains
about "unrecognised arguments" for any positional arguments that
occur after an optional one.  I've been farting with this code for
a day now.

Any suggestions on how I can convince argparse to do what optparse
does easily will be very welcome.  (I tried parse_known_args() but
that breaks help and requires me to detect truly unknown arguments.)

(Python 2.7.1 if it matters and apologies if Google mangles
the formatting of this post.)

You have the namespace object in your custom action. Instead of
"len(parser.largs)", couldn't you just do "len(namespace.src)"?

Cheers,
Ian
 
I

Ian Kelly

I have optparse code that parses a command line containing
intermixed positional and optional arguments, where the optional
arguments set the context for the following positional arguments.
For example,

 myprogram.py arg1 -c33 arg2 arg3 -c44 arg4

'arg1' is processed in a default context, 'args2' and 'arg3' in
context '33', and 'arg4' in context '44'.

I am trying to do the same using argparse but it appears to be
not doable in a documented way.

Here is the working optparse code (which took 30 minutes to write
using just the optparse docs):

 import optparse
 def append_with_pos (option, opt_str, value, parser):
       if getattr (parser.values, option.dest, None) is None:
           setattr (parser.values, option.dest, [])
       getattr (parser.values, option.dest).append ((value, len
(parser.largs)))
 def opt_parse():
       p = optparse.OptionParser()
       p.add_option ("-c", type=int,
           action='callback', callback=append_with_pos)
       opts, args = p.parse_args()
       return args, opts
 if __name__ == '__main__':
       args, opts = opt_parse()
       print args, opts

Output from the command line above:
 ['arg1', 'arg2', 'arg3', 'arg4'] {'c': [(33, 1), (44, 3)]}
The -c values are stored as (value, arglist_position) tuples.

Here is an attempt to convert to argparse using the guidelines
in the argparse docs:

 import argparse
 class AppendWithPos (argparse.Action):
   def __call__ (self, parser, namespace, values,
option_string=None):
       if getattr (namespace, self.dest, None) is None:
           setattr (namespace, self.dest, [])
       getattr (namespace, self.dest).extend ((values, len
(parser.largs)))
 def arg_parse():
       p = argparse.ArgumentParser (description='description')
       p.add_argument ('src', nargs='*')
       p.add_argument ('-c', type=int, action=AppendWithPos)
       opts = p.parse_args()
       return opts
 if __name__ == '__main__':
       opts = arg_parse()
       print opts

This fails with,
 AttributeError: 'ArgumentParser' object has no attribute 'largs'
and of course, the argparse.parser is not documented beyond how
to instantiate it.  Even were that not a problem, argparse complains
about "unrecognised arguments" for any positional arguments that
occur after an optional one.  I've been farting with this code for
a day now.

Any suggestions on how I can convince argparse to do what optparse
does easily will be very welcome.  (I tried parse_known_args() but
that breaks help and requires me to detect truly unknown arguments.)

(Python 2.7.1 if it matters and apologies if Google mangles
the formatting of this post.)

You have the namespace object in your custom action.  Instead of
"len(parser.largs)", couldn't you just do "len(namespace.src)"?

Sorry, I missed the second part of that. You seem to be right, as far
as I can tell from tinkering with it, all the positional arguments
have to be in a single group. If you have some positional arguments
followed by an option followed by more positional arguments, and any
of the arguments have a loose nargs quantifier ('?' or '*' or '+'),
then you get an error.
 
R

rurpy

[...]

Sorry, I missed the second part of that. You seem to be right, as far
as I can tell from tinkering with it, all the positional arguments
have to be in a single group. If you have some positional arguments
followed by an option followed by more positional arguments, and any
of the arguments have a loose nargs quantifier ('?' or '*' or '+'),
then you get an error.

OK, thanks for the second confirmation. I was hoping there
was something I missed or some undocumented option to allow
intermixed optional and positional arguments with Argparse
but it appears not.

I notice that Optparse seems to intentionally provide this
capability since it offers a "disable_interspersed_args()"
method. It is unfortunate that Argparse chose to not to
provide backward compatibility for this thus forcing some
users to continue using a deprecated module.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,731
Messages
2,569,432
Members
44,832
Latest member
GlennSmall

Latest Threads

Top