How to get arguments for 2 different objects from the command line when there is a naming conflict in Python argparse -
i have 2 classes, , b, each have own argument parser defined (using argparse) want add functionality a, calls class b. doing using composition (i.e has instance of object b)
i asked here how combine 2 arg parse objects, argparsea include arguments in argparseb in question can 2 python argparse objects combined? problem follows: both , b have arguments same names. but- need 2 different values entered user (ie. argpasea.val1 needs values argparsea.val1 , argparseb.val1)
(the obvious solution renaming val1 in either argparsea or argpaseb, there on 50 scripts inherit class a, , 50 scripts inherit class b, want changes , b minimal possible.)
i thought of adding new , differently named argument argpasea called val2, can passed argparseb val1.
my question is- proper way of doing such conversion or arguments argparsea argparseb?
or there better way design this?
i'm going guess trying parents
suggestion, , illustrate might going on. if you've adopted approach may help.
import argparse parsera=argparse.argumentparser() a1=parsera.add_argument('-f','--foo','--bar') print(a1) print() parserb=argparse.argumentparser() b1=parserb.add_argument('-g','--goo','--bar') print(b1) b1.dest='bar' # can change attributes dest after creation print() # parser parents; not conflict_handler parserc=argparse.argumentparser(conflict_handler='resolve', parents=[parsera, parserb]) print(parserc._actions) # actions (arguments) of c print() parsera.print_help() print() parserc.print_help() # uses c._actions
which produces
1445:~/mypy$ python3 stack38071986.py _storeaction(option_strings=['-f', '--foo', '--bar'], dest='foo', nargs=none, const=none, default=none, type=none, choices=none, help=none, metavar=none) _storeaction(option_strings=['-g', '--goo', '--bar'], dest='goo', nargs=none, const=none, default=none, type=none, choices=none, help=none, metavar=none) [_storeaction(option_strings=['-f', '--foo'], dest='foo', nargs=none, const=none, default=none, type=none, choices=none, help=none, metavar=none), _helpaction(option_strings=['-h', '--help'], dest='help', nargs=0, const=none, default='==suppress==', type=none, choices=none, help='show message , exit', metavar=none), _storeaction(option_strings=['-g', '--goo', '--bar'], dest='bar', nargs=none, const=none, default=none, type=none, choices=none, help=none, metavar=none)] usage: stack38071986.py [-f foo] optional arguments: show message , exit -f foo, --foo foo usage: stack38071986.py [-f foo] [-h] [-g bar] optional arguments: -f foo, --foo foo -h, --help show message , exit -g bar, --goo bar, --bar bar
normal store
behavior store value
@ dest
attribute in args
namespace, setattr(namespace, action.dest, value)
. dest
default first long option string (minus --), may set parameter (for optionals), or after creation.
when 1 or more option string (flag) of new action conflicts existing action, conflict_handler
evoked. default handler raises error, here use resolve
handler. tries remove enough of existing argument resolve conflict.
all 3 parsers create -h
(help) action. resolve
in parserc
removes one. note [-h]
missing parsera
help.
the --bar
defined in parserb
conflicts same string in parsera
. resolve
has removed a's definition, -f
, --foo
remain.
the creation of parserc
has messed other parsers; i'd recommend using parserc.parse_args()
in run.
we write different conflict_handler
method. resolve
1 has rough edges, , doesn't used often.
i using features aren't documented. consider unsafe. if want unusual behavior have accept risks. plus argparse
documentation not last word on behavior, , easier change code itself. developers paranoid backward conflicts when making changes.
==================
here's stab @ customizing resolve
conflict handler
import argparse def pp(adict): k in adict: v=adict[k] print('action %10s:'%k,v.option_strings, v.dest) def new_resolve(self, action, conflicting_actions): rename_dict={'--var':'--var1'} option_string, action in conflicting_actions: new_string = rename_dict.get(option_string, none) if new_string: # rename rather replace print(action.option_strings) action.option_strings = [new_string] action.dest = new_string[2:] pp(self._option_string_actions) a1=self._option_string_actions.pop(option_string, none) print(a1) self._option_string_actions[new_string] = a1 pp(self._option_string_actions) else: # regular remove action action.option_strings.remove(option_string) self._option_string_actions.pop(option_string, none) # if option has no option string, remove # container holding if not action.option_strings: action.container._remove_action(action) argparse._actionscontainer._handle_conflict_resolve=new_resolve parsera=argparse.argumentparser() a1=parsera.add_argument('-f','--foo') a1=parsera.add_argument('--var') parserb=argparse.argumentparser() b1=parserb.add_argument('-g','--goo') b1=parserb.add_argument('--var') parserc=argparse.argumentparser(conflict_handler='resolve', parents=[parsera, parserb], add_help=false) parsera.print_help() print() parserc.print_help() print(parserc.parse_args())
which produces
1027:~/mypy$ python3 stack38071986.py --var1 1 --var 3 ['--var'] action --var: ['--var1'] var1 action -g: ['-g', '--goo'] goo action --foo: ['-f', '--foo'] foo action -h: ['-h', '--help'] action --goo: ['-g', '--goo'] goo action --help: ['-h', '--help'] action -f: ['-f', '--foo'] foo _storeaction(option_strings=['--var1'], dest='var1', nargs=none, const=none, default=none, type=none, choices=none, help=none, metavar=none) action -g: ['-g', '--goo'] goo action --var1: ['--var1'] var1 action --foo: ['-f', '--foo'] foo action -h: ['-h', '--help'] action --goo: ['-g', '--goo'] goo action --help: ['-h', '--help'] action -f: ['-f', '--foo'] foo usage: stack38071986.py [-f foo] [--var1 var1] optional arguments: show message , exit -f foo, --foo foo --var1 var1 usage: stack38071986.py [-f foo] [--var1 var1] [-h] [-g goo] [--var var] optional arguments: -f foo, --foo foo --var1 var1 -h, --help show message , exit -g goo, --goo goo --var var namespace(foo=none, goo=none, var='3', var1='1')
the conflict handler functions defined in super class, can't subclass argumentparser
add new one. it's easy add custom action
, formathandler
classes, isn't easy. guess noone has tried customization.
so kludge write modification of resolve
method, , link in - on fly. not clean, enough testing.
this handler knows identity of existing action (action
argument), not new 1 (i.e. --var
parsera, not --var
parserb). modifying 'name' of existing one. method has know, via rename_dict
option string replaced , new name.
having done this, i'm thinking might easier write custom version of parents
mechanism. 1 used as:
parserc = argparse.argumentparser() parserc.add_argument(...) # c's own arguments copy_arguments(parserc, [parsera, parserb], rename_dict={...})
==========================
i better - custom parents
mechanism, 1 lets me specify skip_list
, replace_dict
. (i may delete solution).
import argparse def add_actions(parser, other, skip_list=none, rename_dict=none): # adapted _add_container_actions (used parents) # copy (by reference) selected actions other parser # can skip actions (to avoid use of conflict_handler) # can rename other actions (again avoid conflict) if skip_list none: skip_list = ['-h','--help'] if rename_dict none: rename_dict = {} # group handling before # collect groups titles title_group_map = {} group in parser._action_groups: if group.title in title_group_map: msg = _('cannot merge actions - 2 groups named %r') raise valueerror(msg % (group.title)) title_group_map[group.title] = group # map each action group group_map = {} group in other._action_groups: # if group title exists, use that, otherwise # create new group matching other's group if group.title not in title_group_map: title_group_map[group.title] = parser.add_argument_group( title=group.title, description=group.description, conflict_handler=group.conflict_handler) # map actions new group action in group._group_actions: group_map[action] = title_group_map[group.title] # add other's mutually exclusive groups # note: if add_mutually_exclusive_group ever gains title= , # description= code need expanded above group in other._mutually_exclusive_groups: mutex_group = parser.add_mutually_exclusive_group( required=group.required) # map actions new mutex group action in group._group_actions: group_map[action] = mutex_group # add actions other or group # addition skip , rename action in other._actions: option_strings = action.option_strings if any([s s in option_strings if s in skip_list]): print('skipping ', action.dest) continue else: sl = [s s in option_strings if s in rename_dict] if len(sl): mod = rename_dict[sl[0]] action.dest = action.dest+mod action.option_strings = [option_strings[0]+mod] group_map.get(action, parser)._add_action(action) parsera=argparse.argumentparser() a1=parsera.add_argument('-f','--foo') a1=parsera.add_argument('--var') parserb=argparse.argumentparser() b1=parserb.add_argument('-g','--goo') b1=parserb.add_argument('--var') parserc=argparse.argumentparser() # parserc.add_argument('baz') add_actions(parserc, parsera, rename_dict={'--var':'a'}) add_actions(parserc, parserb, rename_dict={'--var':'b'}) parserc.print_help() print(parserc.parse_args())
and sample run
2245:~/mypy$ python3 stack38071986_1.py --vara 1 --varb 3 skipping skipping usage: stack38071986_1.py [-h] [-f foo] [--vara vara] [-g goo] [--varb varb] optional arguments: -h, --help show message , exit -f foo, --foo foo --vara vara -g goo, --goo goo --varb varb namespace(foo=none, goo=none, vara='1', varb='3')
=============================
if add
print('parserc actions') action in parserc._actions: print(action.option_strings, action.dest)
i printout
parserc actions ['-h', '--help'] ['-f', '--foo'] foo ['--vara'] vara ['-g', '--goo'] goo ['--varb'] varb
_actions
list of actions (argument) of parser. 'hidden' use caution, don't anticipate changes in fundamental property this. can modify many of attributes of these actions, such dest
for example if rename of dest
:
for action in parserc._actions: if action.dest.startswith('var'): action.dest = action.dest+'_c' print(parserc.parse_args())
the namespace like
namespace(foo=none, goo=none, vara_c=none, varb_c='one')
Comments
Post a Comment