I am trying to add command line options to program. The program should accept both a list of numbers, and parse options. For instance a call to the program could look like:
python3 ./parsetest.py 4 3 v=100
The 4 and the 3 will be processed in one way whereas the v=100 command will set the internal variable v to 100. I found the getopt library is supposed to have this functionality: http://stackabuse.com/command-line-arguments-in-python/ Is there a better/simpler way to do this?
When I try to parse the input like the above, I believe getopt should place arguments which trigger its keywords into a special list, and those which do not trigger are placed elsewhere. However, when I try to use getopt following the example above, no matter what I do, the v=100 option does not appear to trigger the special arguments list. When passed to getopt, I expected the 'v=100' string to be split up into argument v and value 100. For instance, the print(arguments,values) command below results in: [] ['4', '3', 'v=100']. There are values, but there are no arguments.
Here is my code:
import sys,getopt
v = 1e100
fullCmdArguments = sys.argv
argumentList = fullCmdArguments[1:]
unixOptions = "v:"
gnuOptions = ["v="]
print(argumentList)
try:
arguments, values = getopt.getopt(argumentList, unixOptions, gnuOptions)
except getopt.error as err:
# output error, and return with an error code
print (str(err))
sys.exit(2)
print(arguments, values)
for currentArgument, currentValue in arguments:
print (currentArgument, currentValue)
if currentArgument in ("-v", "v"):
v = currentValue
else:
#Its an integer then, do something with it
print()
If you can use argparse the following will work:
Example
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('numbers', type=int, nargs='+')
parser.add_argument('-v', type=int, default=-1)
args = parser.parse_args()
print(args.numbers)
print(args.v)
# due to the default set above, `args.v` will always have an int value
v = args.v
Using the nargs keyword argument you can set it to '+' requiring one or more numbers. The v with a hyphen is by default optional. If this is not wanted, you can add the required=True option.
Usage
python my_file.py 1 2 3 5 -v=12
[1, 2, 3, 5]
12
python my_file.py 1 2 3 5
[1, 2, 3, 5]
-1
Further reading
For more information about the argparse module you can have a look at:
The official tutorial: https://docs.python.org/3/howto/argparse.html
The module doc: https://docs.python.org/3/library/argparse.html#module-argparse
Related
I need to let the end user of my python script types something like:
script.py -sizes <2,2> <3,3> <6,6>
where each element of the -sizes option is a pair of two positive integers. How can I achieve this with argparse ?
Define a custom type:
def pair(arg):
# For simplity, assume arg is a pair of integers
# separated by a comma. If you want to do more
# validation, raise argparse.ArgumentError if you
# encounter a problem.
return [int(x) for x in arg.split(',')]
then use this as the type for a regular argument:
p.add_argument('--sizes', type=pair, nargs='+')
Then
>>> p.parse_args('--sizes 1,3 4,6'.split())
Namespace(sizes=[[1, 3], [4, 6]])
Argparse doesn't try to cover all possible formats of input data. You can always get sizes as string and parse them with few lines of code:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--sizes', nargs='+')
args = parser.parse_args()
try:
sizes = [tuple(map(int, s.split(',', maxsplit=1))) for s in args.sizes]
except Exception:
print('sizes cannot be parsed')
print(sizes)
"Special cases aren't special enough to break the rules."
I am trying to use argparse in my python code to set 5 different variables equal certain values based on command line. And if these variables are not entered, it will be set to default which was already defined in my code.
So if i run the code:
python test.py CSV = "/Home/data/file.csv" length = 1 cluster = 7 dim = 5
fnumber = 8
It will set the CSV = "/Home/data/file.csv", length = 1, cluster = 7, dim = 5 and fnumber = 8.
But if instead i run the code:
python test.py CSV = "/Home/data/file.csv" cluster = 7 fnumber = 8
It will set the CSV = "/Home/data/file.csv", length = 4, cluster = 7, dim = 9 and fnumber = 8 because the length was already pre-defined in my code to be 7 and dim was pre-defined to be 9.
Whatever i type in the command line should replace the already defined value in my code below
The difficult part is that i have to type out the string like "length = 1" in the command line instead of just the value 1. This is because i want the user-interface to be clearer as some variables can be left blank if i have to use spaces to replace those empty arguments and it will look bad. So i am required to type out the declaration of variables value in command line
import argparse
CSV = "Data/a1.csv"
length = 4
cluster = 3
dim = 9
fnumber = 15
Here's something simple showing how it could be done with argparse and allow you to define the default values. To use it you will need to follow its argument-passed conventions/style, which doesn't have the spaces around the = and requires each argument to start with a - (or --, see the docs).
It's possible to get fancy and, for example, allow an argument to be specified in more than one way. For that and many other options, I suggest you consult the module's documentation.
Note that the values shown being passed in the parse_args() calsl are what might be in sys.argv for a command-line composed of all of them joined together and separated by spaces.
import argparse
DEFAULT = dict(length=1, cluster=2, dim=3, fnumber=4)
parser = argparse.ArgumentParser(description='Test app')
parser.add_argument('-CSV', action="store")
parser.add_argument('-length', action="store", default=DEFAULT['length'])
parser.add_argument('-cluster', action="store", type=int, default=DEFAULT['cluster'])
parser.add_argument('-dim', action="store", type=int, default=DEFAULT['dim'])
parser.add_argument('-fnumber', action="store", type=int, default=DEFAULT['fnumber'])
args = parser.parse_args(['-CSV="/Home/data/file.csv"', '-length=1', '-cluster=7',
'-dim=5'])
print('args.CSV:', args.CSV)
print('args.length:', args.length)
print('args.cluster:', args.cluster)
print('args.dim:', args.dim)
print('args.fnumber:', args.fnumber)
print()
args2 = parser.parse_args(['-CSV="/Home/data/file2.csv"', '-cluster=42', '-fnumber=5'])
print('args2.CSV:', args2.CSV)
print('args2.length:', args2.length)
print('args2.cluster:', args2.cluster)
print('args2.dim:', args2.dim)
print('args2.fnumber:', args2.fnumber)
Output:
args.CSV: "/Home/data/file.csv"
args.length: 1
args.cluster: 7
args.dim: 5
args.fnumber: 4
args2.CSV: "/Home/data/file2.csv"
args2.length: 1
args2.cluster: 42
args2.dim: 3
args2.fnumber: 5
Input all the command line arguments without spaces around the equal signs (length=1 instead of length = 1) since it's a command line convention anyways. Then, iterate through all the command line arguments and call the built in Python function exec (more information here) to set all the configuration variables. Note this should come after all the defaults are set.
Please note it is usually not advisable to use exec because it will run anything in the input. If you are planning to ever publish this code, you should think about creating a filter for the command line arguments. To do that, first split the string of the command line argument by the equals sign and check if the first part of the split is in your set of configurable variables and if the second part is actually a number (if that's what you want).
Try this:
def parse_assign(args, keywords):
"""Parse args for keyword assignments.
Arguments:
args: List of arguments in the form of repeated 'foo = bar' assignments.
keywords: List of keywords to look for.
Returns:
A dict of found keywords with their values.
"""
rv = {}
for word in keywords:
try:
rv[word] = args[args.index(word) + 2]
except (IndexError, ValueError):
pass
return rv
Explanation: if word is not in args, then index would generate an ValueError exeption. If args.index(word) + 2 is larger than the length of args, you'd get an IndexError.
A test in IPython:
In [15]: args
Out[15]:
['CSV',
'=',
'"/Home/data/file.csv"',
'length',
'=',
'1',
'cluster',
'=',
'7',
'dim',
'=',
'5',
'fnumber',
'=',
'8']
In [16]: parse_assign(args, ['CSV', 'length', 'cluster', 'dim', 'fnumber'])
Out[16]:
{'CSV': '"/Home/data/file.csv"',
'length': '1',
'cluster': '7',
'dim': '5',
'fnumber': '8'}
Adding the defaults makes it slightly more complicated:
def parse_assign2(args, defaults):
"""Parse args for keyword assignments.
Arguments:
args: List of arguments in the form of repeated 'foo = bar' assignments.
keywords: dict of defaults.
Returns:
A dict of found keywords with their values, merged with the defaults.
"""
rv = {}
keywords = defaults.keys()
for word in keywords:
try:
rv[word] = args[args.index(word) + 2]
except (IndexError, ValueError):
pass
return {**defaults, **rv}
I want to call a python script through the command line with this kind of parameter (list could be any size, eg with 3):
python test.py --option1 ["o11", "o12", "o13"] --option2 ["o21", "o22", "o23"]
using click. From the docs, it is not stated anywhere that we can use a list as parameter to #click.option
And when I try to do this:
#!/usr/bin/env python
import click
#click.command(context_settings=dict(help_option_names=['-h', '--help']))
#click.option('--option', default=[])
def do_stuff(option):
return
# do stuff
if __name__ == '__main__':
do_stuff()
in my test.py, by calling it from the command line:
python test.py --option ["some option", "some option 2"]
I get an error:
Error: Got unexpected extra argument (some option 2])
I can't really use variadic arguments as only 1 variadic arguments per command is allowed (http://click.pocoo.org/5/arguments/#variadic-arguments)
So if anyone can point me to the right direction (using click preferably) it would be very much appreciated.
If you don't insist on passing something that looks like a list, but simply want to pass multiple variadic arguments, you can use the multiple option.
From the click documentation
#click.command()
#click.option('--message', '-m', multiple=True)
def commit(message):
click.echo('\n'.join(message))
$ commit -m foo -m bar
foo
bar
You can coerce click into taking multiple list arguments, if the lists are formatted as a string literals of python lists by using a custom option class like:
Custom Class:
import click
import ast
class PythonLiteralOption(click.Option):
def type_cast_value(self, ctx, value):
try:
return ast.literal_eval(value)
except:
raise click.BadParameter(value)
This class will use Python's Abstract Syntax Tree module to parse the parameter as a python literal.
Custom Class Usage:
To use the custom class, pass the cls parameter to #click.option() decorator like:
#click.option('--option1', cls=PythonLiteralOption, default=[])
How does this work?
This works because click is a well designed OO framework. The #click.option() decorator usually instantiates a click.Option object but allows this behavior to be over ridden with the cls parameter. So it is a relatively easy matter to inherit from click.Option in our own class and over ride the desired methods.
In this case we over ride click.Option.type_cast_value() and then call ast.literal_eval() to parse the list.
Test Code:
#click.command(context_settings=dict(help_option_names=['-h', '--help']))
#click.option('--option1', cls=PythonLiteralOption, default=[])
#click.option('--option2', cls=PythonLiteralOption, default=[])
def cli(option1, option2):
click.echo("Option 1, type: {} value: {}".format(
type(option1), option1))
click.echo("Option 2, type: {} value: {}".format(
type(option2), option2))
# do stuff
if __name__ == '__main__':
import shlex
cli(shlex.split(
'''--option1 '["o11", "o12", "o13"]'
--option2 '["o21", "o22", "o23"]' '''))
Test Results:
Option 1, type: <type 'list'> value: ['o11', 'o12', 'o13']
Option 2, type: <type 'list'> value: ['o21', 'o22', 'o23']
The following can be an easier hack fix:
#!/usr/bin/env python
import click
import json
#click.command(context_settings=dict(help_option_names=['-h', '--help']))
#click.option('--option', help='Whatever')
def do_stuff(option):
try:
option = json.loads(option)
except ValueError:
pass
# do stuff
if __name__ == '__main__':
do_stuff()
This can help you to use 'option' as a list or a str.
#Murphy's "easy hack" almost worked for me, but the thing is that option will be a string unless you single quote the options, so I had to do this in order to recompose the list:
#!/usr/bin/env python
import click
import json
#click.command(context_settings=dict(help_option_names=['-h', '--help']))
#click.option('--option', help='Whatever')
def do_stuff(option):
try:
option = json.loads(option)
# option = str(option) # this also works
except ValueError:
pass
option = option[1:-1] # trim '[' and ']'
options = option.split(',')
for i, value in enumerate(options):
# catch integers
try:
int(value)
except ValueError:
options[i] = value
else:
options[i] = int(value)
# Here use options as you need
# do stuff
if __name__ == '__main__':
do_stuff()
You could catch some other types
To use it, enclose the list into quotes:
python test.py --option "[o11, o12, o13]"
Or you can avoid quotes by not leaving spaces:
python test.py --option [o11,o12,o13]
The answer of Stephen Rauch gave me some issues when working with Kubernetes arguments. This because the string formatting becomes a hassle and often new lines, single quotation marks or spaces were added before and after the array. Hence I made my own parser to improve this behaviour.
This code should be self explanatory.
Note that this code does not support single or double quotation marks ' or " in the variables itself.
Custom class:
class ConvertStrToList(click.Option):
def type_cast_value(self, ctx, value) -> List:
try:
value = str(value)
assert value.count('[') == 1 and value.count(']') == 1
list_as_str = value.replace('"', "'").split('[')[1].split(']')[0]
list_of_items = [item.strip().strip("'") for item in list_as_str.split(',')]
return list_of_items
except Exception:
raise click.BadParameter(value)
Custom class usage:
#click.option('--option1', cls=ConvertStrToList, default=[])
Click supports an option taking multiple arguments, as long as the number of arguments is predetermined. The arguments are separated by whitespace with no list-like syntax: one would write --my-2-arg-option arg1 arg2 rather than --my-2-arg-option ["arg1", "arg2"].
This response does answer your question about "how to pass several lists of arguments to #click.option," just that the lists need to be given without brackets or commas on the command line. A Python list could be formatted this way as follows, using shlex.join to add quotation marks when necessary:
>>> import shlex
>>> args = ["o11", "o12", "o13"]
>>> shlex.join(args)
'o11 o12 o13'
>>> args_needing_quotes = ["I have whitespace",
"$I_LOOK_LIKE_A_VARIABLE",
"$(i_have_special_characters)#!\n > *"]
>>> shlex.join(args_needing_quotes)
"'I have whitespace' '$I_LOOK_LIKE_A_VARIABLE' '$(i_have_special_characters)#!\n > *'"
Option that takes multiple arguments, all of the same type
This kind of option seems to be what you are looking for, and can be implemented with the following code (call it script1.py):
import click
#click.command()
#click.option("--option1", nargs=3)
def do_stuff(option1):
print("Option 1 is", option1)
do_stuff()
The parameter nargs tells Click how many arguments the option must accept; by default, nargs is 1.
Running this code prints the following output:
$ python script1.py --option1 four 4 IV
Option 1 is ('four', '4', 'IV')
We can see that the variable option1 is a tuple of three str values.
If fewer or more than 3 arguments are given for option1, then the script exits immediately:
$ python script1.py --option1 four 4
Error: Option '--option1' requires 3 arguments.
If option1 is omitted, then it defaults to None:
$ python script1.py
Option 1 is None
Default values of multi-argument options
A default value can be specified when the option is created. Note that the default value should also be a tuple of three str values to avoid any unexpected behavior. Particularly, it should not be a list (or any other mutable type), as mutable default arguments can cause unexpected behavior. Call the code with this change script2.py:
#click.option("--option1", nargs=3, default=('', '', ''))
Now the script prints the default when run with no arguments:
$ python script2.py
Option 1 is ('', '', '')
Types other than strings
Click will also automatically cast values to a different data type if given. Let script3.py be
import click
#click.command()
#click.option("--option1", nargs=3, type=float)
def do_stuff(option1):
print("Option 1 is", option1)
do_stuff()
Now option1 is a tuple of three float values:
$ python script3.py --option1 1 2.718 3.142
Option 1 is (1.0, 2.718, 3.142)
Option that takes multiple arguments of different data types
The previous examples showed how to create an option that takes multiple values of the same data type. What about an option that takes arguments of multiple data types?
Click provides a way to do this as well. Instead of setting nargs, set type to be a tuple of the desired data types. For example, let script4.py be
import click
#click.command()
#click.option("--comp", type=(float, str, float))
def calculate(comp):
num1, op, num2 = comp
if op == "+":
result = num1 + num2
elif op == "-":
result = num1 - num2
else:
raise ValueError(f"Unsupported operation: {op}")
print(f"{num1} {op} {num2} = {result}")
calculate()
Then we can use our rudimentary calculator:
$ python script4.py --comp 3 + 6
3.0 + 6.0 = 9.0
$ python script4.py --comp -1.2 - 3.7
-1.2 - 3.7 = -4.9
If any of the values are invalid, then click will raise an error automatically:
$ python script4.py --comp 1 + two
Usage: script4.py [OPTIONS]
Try 'script4.py --help' for help.
Error: Invalid value for '--comp': 'two' is not a valid float.
Option that can be given multiple times and takes multiple values
Click can also create options that can be given multiple times using the multiple keyword (script5.py):
import click
#click.command()
#click.option("--option1", nargs=2, multiple=True)
def do_stuff(option1):
print(option1)
do_stuff()
Now we see that option1 becomes a tuple of tuples of two strs each:
$ python script5.py --option1 3 a --option1 b 7
(('3', 'a'), ('b', '7'))
We can also mix multiple=True with the type keyword in script6.py:
import click
#click.command()
#click.option("--comps", type=(float, str, float), multiple=True)
def calculate(comps):
for comp in comps:
num1, op, num2 = comp
if op == "+":
result = num1 + num2
elif op == "-":
result = num1 - num2
else:
raise ValueError(f"Unsupported operation: {op}")
print(f"{num1} {op} {num2} = {result}")
calculate()
This functionality can allow us to, for example, code a simple calculator that performs multiple operations in one call to script6.py:
python script6.py --comps 4 - -7 --comps -8 + 4.2 --comps 16 - 34.1
4.0 - -7.0 = 11.0
-8.0 + 4.2 = -3.8
16.0 - 34.1 = -18.1
The full documentation for multi-value options in Click can be found here.
Note: I ran all code examples in Python 3.11.0 with Click 8.1.3
I have some command line arguments categorized in groups as follows:
cmdParser = argparse.ArgumentParser()
cmdParser.add_argument('mainArg')
groupOne = cmdParser.add_argument_group('group one')
groupOne.add_argument('-optA')
groupOne.add_argument('-optB')
groupTwo = cmdParser.add_argument_group('group two')
groupTwo.add_argument('-optC')
groupTwo.add_argument('-optD')
How can I parse the above, such that I end up with three different Namespace objects?
global_args - containing all the arguments not part of any group
groupOne_args - containing all the arguments in groupOne
groupTwo_args - containing all the arguments in groupTwo
Thank you!
you can do it in this way:
import argparse
parser = argparse.ArgumentParser()
group1 = parser.add_argument_group('group1')
group1.add_argument('--test1', help="test1")
group2 = parser.add_argument_group('group2')
group2.add_argument('--test2', help="test2")
args = parser.parse_args('--test1 one --test2 two'.split())
arg_groups={}
for group in parser._action_groups:
group_dict={a.dest:getattr(args,a.dest,None) for a in group._group_actions}
arg_groups[group.title]=argparse.Namespace(**group_dict)
This will give you the normal args, plus dictionary arg_groups containing namespaces for each of the added groups.
(Adapted from this answer)
Nothing in argparse is designed to do that.
For what it's worth, the parser starts off with two argument groups, one that displays as positionals and the other as optionals (I forget the exact titles). So in your example there will actually be 4 groups.
The parser only uses argument groups when formatting the help. For parsing all arguments are put in a master parser._actions list. And during parsing the parser only passes around one namespace object.
You could define separate parsers, with different sets of arguments, and call each with parse_known_args. That works better with optionals (flagged) arguments than with positionals. And it fragments your help.
I have explored in other SO questions a novel Namespace class that could nest values based on some sort of dotted dest (names like group1.optA, group2.optC, etc). I don't recall whether I had to customize the Action classes or not.
The basic point is that when saving a value to the namespace, a parser, or actually a Action (argument) object does:
setattr(namespace, dest, value)
That (and getattr/hasattr) is all that the parser expects of the namespace. The default Namespace class is simple, little more than a plain object subclass. But it could be more elaborate.
I was looking for a solution for this for a very long time,
And I think I finally got it.
So I will just put it here...
from argparse import ArgumentParser
def _parse_args():
parser = ArgumentParser()
parser.add_argument('-1', '--flag-1', action='store_true', default=False)
parser.add_argument('-2', '--flag-2', action='store_true', default=False)
parser.add_argument('-3', '--flag-3', action='store_true', default=False)
args, unknown = parser.parse_known_args()
print(f"args : {args}")
print(f"unknown : {unknown}")
hidden = ArgumentParser(add_help=False)
hidden.add_argument('-d', '--debug', action='store_true', default=False)
hidden_args = hidden.parse_args(unknown)
print(f"hidden_args : {hidden_args}")
if __name__ == "__main__":
_parse_args()
as a result:
show help:
ubuntu → playAround $ ./test.py -h
usage: test.py [-h] [-1] [-2] [-3]
optional arguments:
-h, --help show this help message and exit
-1, --flag-1
-2, --flag-2
-3, --flag-3
With debug flag:
ubuntu → playAround $ ./test.py -d
args : Namespace(flag_1=False, flag_2=False, flag_3=False)
unknown : ['-d']
hidden_args : Namespace(debug=True)
with flags 1 and 2:
ubuntu → playAround $ ./test.py -12
args : Namespace(flag_1=True, flag_2=True, flag_3=False)
unknown : []
hidden_args : Namespace(debug=False)
with flags 1 and 2 and debug:
ubuntu → playAround $ ./test.py -12 -d
args : Namespace(flag_1=True, flag_2=True, flag_3=False)
unknown : ['-d']
hidden_args : Namespace(debug=True)
The only thing you can't do with this approch is to pass the debug short flag along side to the other short flags:
ubuntu → playAround $ ./test.py -12d
usage: test.py [-h] [-1] [-2] [-3]
test.py: error: argument -2/--flag-2: ignored explicit argument 'd'
Here's a simple method that calls the parse_known_args() method after each group is defined to get the names for those arguments separately.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument('mainArg')
global_names = set(vars(parser.parse_known_args()[0]).keys())
group1 = parser.add_argument_group('group 1')
group1.add_argument('-optA')
group1.add_argument('-optB')
group1_names = set(vars(parser.parse_known_args()[0]).keys()) - global_names
group2 = parser.add_argument_group('group 2')
group2.add_argument('-optC')
group2.add_argument('-optD')
group2_names = set(vars(parser.parse_known_args()[0]).keys()) - global_names - group1_names
args = parser.parse_args()
global_args = argparse.Namespace(**dict((k, v) for k, v in vars(args).items() if k in global_names))
group1_args = argparse.Namespace(**dict((k, v) for k, v in vars(args).items() if k in group1_names))
group2_args = argparse.Namespace(**dict((k, v) for k, v in vars(args).items() if k in group2_names))
print(global_args)
print(group1_args)
print(group2_args)
E.g. python args.py hi -optA fooA -optB fooB -optC fooC -optD fooD will output:
Namespace(mainArg='hi')
Namespace(optA='fooA', optB='fooB')
Namespace(optC='fooC', optD='fooD')
I'm writing a utility for running bash commands that essentially takes as input a string and a list optional argument and uses the optional arguments to interpolate string.
I'd like it to work like this:
interpolate.py Hello {user_arg} my name is {computer_arg} %% --user_arg=john --computer_arg=hal
The %% is a separator, it separates the string to be interpolated from the arguments. What follows is the arguments used to interpolate the string. In this case the user has chosen user_arg and computer_arg as arguments. The program can't know in advance which argument names the user will choose.
My problem is how to parse the arguments? I can trivially split the input arguments on the separator but I can't figure out how to get optparse to just give the list of optional args as a dictionary, without specifying them in advance. Does anyone know how to do this without writing a lot of regex?
Well, if you use '--' to separate options from arguments instead of %%, optparse/argparse will just give you the arguments as a plain list (treating them as positional arguments instead of switched). After that it's not 'a lot of' regex, it's just a mere split:
for argument in args:
if not argument.startswith("--"):
# decide what to do in this case...
continue
arg_name, arg_value = argument.split("=", 1)
arg_name = arg_name[2:]
# use the argument any way you like
With argparse you could use the parse_known_args method to consume predefined arguments and any additional arguments. For example, using the following script
import sys
import argparse
def main(argv=None):
parser = argparse.ArgumentParser()
parser.add_argument('string', type=str, nargs='*',
help="""String to process. Optionally with interpolation
(explain this here...)""")
args, opt_args = parser.parse_known_args(argv)
print args
print opt_args
return 0
if __name__=='__main__':
sys.exit(main(sys.argv[1:]))
and calling with
python script.py Hello, my name is {name} --name=chris
yields the following output:
Namespace(string=['Hello,' 'my', 'name', 'is', '{name}'])
['--name=chris']
All that is left to do is to loop through the args namespace looking for strings of the form {...} and replacing them with the corresponding element in opt_args, if present. (I'm not sure if argparse can do argument interpolation automatically, the above example is the only immediate solution which comes to mind).
For something like this, you really don't need optparse or argparse - the benefit of such libraries are of little use in this circumstance (things like lone -v type arguments, checking for invalid options, value validation and so on)
def partition_list(lst, sep):
"""Slices a list in two, cutting on index matching "sep"
>>> partition_list(['a', 'b', 'c'], sep='b')
(['a'], ['c'])
"""
if sep in lst:
idx = lst.index(sep)
return (lst[:idx], lst[idx+1:])
else:
return (lst[:], )
def args_to_dict(args):
"""Crudely parses "--blah=123" type arguments into dict like
{'blah': '123'}
"""
ret = {}
for a in args:
key, _, value = a.partition("=")
key = key.replace("--", "", 1)
ret[key] = value
return ret
if __name__ == '__main__':
import sys
# Get stuff before/after the "%%" separator
string, args = partition_list(sys.argv[1:], "%%")
# Join input string
string_joined = " ".join(string)
# Parse --args=stuff
d = args_to_dict(args)
# Do string-interpolation
print string_joined.format(**d)