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