Skip to content

Commit 185c666

Browse files
committed
argparse docs
1 parent 837f161 commit 185c666

File tree

7 files changed

+408
-0
lines changed

7 files changed

+408
-0
lines changed

appendix/argparse/README.md

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
The `argparse` module will interpret all the command-line arguments to your program. I suggest you use `argparse` for every command-line program you write so that you always have a standard way to get arguments and present help.
2+
3+
## Types of arguments
4+
5+
Command-line arguments come in a variety of flavors:
6+
7+
* Positional: The order and number of the arguments is what determines their meaning. Some programs might expect, for instance, a file name as the first argument and an output directory as the second.
8+
* Named options: Standard Unix format allows for a "short" name like `-f` (one dash and a single character) or a "long" name like `--file` (two dashes and a string of characters) followed by some value like a file name or a number. This allows for arguments to be provided in any order or not provided in which case the program can use a reasonable default value.
9+
* Flag: A "Boolean" value like "yes"/"no" or `True`/`False` usually indicated by something that looks like a named option but without a value, e.g., `-d` or `--debug` to turn on debugging. Typically the presence of the flag indicates a `True` value for the argument; therefore, it's absence would mean `False`, so `--debug` turns *on* debugging while no `--debug` flag means there should not no debugging.
10+
11+
## Datatypes of values
12+
13+
The `argparse` module can save you enormous amounts of time by forcing the user to provide arguments of a particular type. If you run `new.py`, all of the above types of arguments are present along with suggestions for how to get string or integer values:
14+
15+
````
16+
# --------------------------------------------------
17+
def get_args():
18+
"""Get command-line arguments"""
19+
20+
parser = argparse.ArgumentParser(
21+
description='Argparse Python script',
22+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
23+
24+
parser.add_argument('positional',
25+
metavar='str',
26+
help='A positional argument')
27+
28+
parser.add_argument('-a',
29+
'--arg',
30+
help='A named string argument',
31+
metavar='str',
32+
type=str,
33+
default='')
34+
35+
parser.add_argument('-i',
36+
'--int',
37+
help='A named integer argument',
38+
metavar='int',
39+
type=int,
40+
default=0)
41+
42+
parser.add_argument('-f',
43+
'--flag',
44+
help='A boolean flag',
45+
action='store_true')
46+
47+
return parser.parse_args()
48+
````
49+
50+
You should change the `description` to a short sentence describing your program. The `formatter_class` argument tells `argparse` to show the default values in the the standard help documentation.
51+
52+
The `positional` argument's definition indicates we expect exactly one positional argument. The `-a` argument's `type` must be a `str` while the `-i` option must be something that Python can convert to the `int` type (you can also use `float`). Both of these arguments have `default` values which means the user is not required to provide them. You could instead define them with `required=True` to force the user to provide values themselves.
53+
54+
The `-f` flag notes that the `action` is to `store_true` which means the value's default with be `True` if the argument is present and `False` otherwise.
55+
56+
The `type` of the argument can be something much richer than simple Python types like strings or numbers. You can indicate that an argument must be a existing, readable file. Here is a simple implementation in Python of `cat -n`:
57+
58+
````
59+
#!/usr/bin/env python3
60+
"""Python version of `cat -n`"""
61+
62+
import argparse
63+
64+
65+
# --------------------------------------------------
66+
def get_args():
67+
"""Get command-line arguments"""
68+
69+
parser = argparse.ArgumentParser(
70+
description='Argparse Python script',
71+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
72+
73+
parser.add_argument('file',
74+
metavar='FILE',
75+
type=argparse.FileType('r'),
76+
help='Input file')
77+
78+
return parser.parse_args()
79+
80+
81+
# --------------------------------------------------
82+
def main():
83+
"""Make a jazz noise here"""
84+
85+
args = get_args()
86+
fh = args.file
87+
88+
print('Reading "{}"'.format(fh.name))
89+
for i, line in enumerate(fh):
90+
print(i, line, end='')
91+
92+
93+
# --------------------------------------------------
94+
if __name__ == '__main__':
95+
main()
96+
````
97+
98+
The `type` of the input `file` argument is an *open file handle* which we can directly read line-by-line with a `for` loop! Because it's a file *handle* and not a file *name*, I chose to call the variable `fh` to help me remember what it is. You can access the file's name via `fh.name`.
99+
100+
## Number of arguments
101+
102+
If you want one positional argument, you can define them like so:
103+
104+
````
105+
#!/usr/bin/env python3
106+
"""One positional argument"""
107+
108+
import argparse
109+
110+
parser = argparse.ArgumentParser(
111+
description='One positional argument',
112+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
113+
114+
parser.add_argument('first', metavar='str', help='First argument')
115+
args = parser.parse_args()
116+
print('first =', args.first)
117+
````
118+
119+
If the user provides anything other exactly one argument, they get a help message:
120+
121+
````
122+
$ ./one_arg.py
123+
usage: one_arg.py [-h] str
124+
one_arg.py: error: the following arguments are required: str
125+
$ ./one_arg.py foo bar
126+
usage: one_arg.py [-h] str
127+
one_arg.py: error: unrecognized arguments: bar
128+
$ ./one_arg.py foo
129+
first = foo
130+
````
131+
132+
If you want two different positional arguments:
133+
134+
````
135+
#!/usr/bin/env python3
136+
"""Two positional arguments"""
137+
138+
import argparse
139+
140+
parser = argparse.ArgumentParser(
141+
description='Two positional arguments',
142+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
143+
144+
parser.add_argument('first', metavar='str', help='First argument')
145+
146+
parser.add_argument('second', metavar='int', help='Second argument')
147+
148+
return parser.parse_args()
149+
150+
print('first =', args.first)
151+
print('second =', args.second)
152+
````
153+
154+
Again, the user must provide exactly this number of positional arguments:
155+
156+
````
157+
$ ./two_args.py
158+
usage: two_args.py [-h] str str
159+
two_args.py: error: the following arguments are required: str, str
160+
$ ./two_args.py foo
161+
usage: two_args.py [-h] str str
162+
two_args.py: error: the following arguments are required: str
163+
$ ./two_args.py foo bar
164+
first = foo
165+
second = bar
166+
````
167+
168+
You can also use the `nargs=N` option to specify some number of arguments. It only makes sense if the arguments are the same thing like two files:
169+
170+
````
171+
#!/usr/bin/env python3
172+
"""nargs=2"""
173+
174+
import argparse
175+
176+
parser = argparse.ArgumentParser(
177+
description='nargs=2',
178+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
179+
180+
parser.add_argument('files', metavar='FILE', nargs=2, help='Two files')
181+
182+
args = parser.parse_args()
183+
184+
file1, file2 = args.files
185+
print('file1 =', file1)
186+
print('file2 =', file2)
187+
````
188+
189+
The help indicates we want two files:
190+
191+
````
192+
$ ./nargs2.py foo
193+
usage: nargs2.py [-h] FILE FILE
194+
nargs2.py: error: the following arguments are required: FILE
195+
````
196+
197+
And we can unpack the two file arguments and use them:
198+
199+
````
200+
$ ./nargs2.py foo bar
201+
file1 = foo
202+
file2 = bar
203+
````
204+
205+
If you want one or more of some argument, you can use `nargs='+'`:
206+
207+
````
208+
$ cat nargs+.py
209+
#!/usr/bin/env python3
210+
"""nargs=+"""
211+
212+
import argparse
213+
214+
parser = argparse.ArgumentParser(
215+
description='nargs=+',
216+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
217+
218+
parser.add_argument('files', metavar='FILE', nargs='+', help='Some files')
219+
220+
args = parser.parse_args()
221+
files = args.files
222+
223+
print('number = {}'.format(len(files)))
224+
print('files = {}'.format(', '.join(files)))
225+
````
226+
227+
Note that this will return a `list` -- even a single argument will become a `list` of one value:
228+
229+
````
230+
$ ./nargs+.py
231+
usage: nargs+.py [-h] FILE [FILE ...]
232+
nargs+.py: error: the following arguments are required: FILE
233+
$ ./nargs+.py foo
234+
number = 1
235+
files = foo
236+
$ ./nargs+.py foo bar
237+
number = 2
238+
files = foo, bar
239+
````
240+
241+
## Automatic help
242+
243+
The `argparse` module reserves the `-h` and `--help` flags for generating help documentation. You do not need to add these nor are you allowed to use these flags for other purposes. Using the above definition, this is the help that `argparse` will generate:
244+
245+
````
246+
$ ./foo.py
247+
usage: foo.py [-h] [-a str] [-i int] [-f] str
248+
foo.py: error: the following arguments are required: str
249+
[cholla@~/work/python/playful_python/article]$ ./foo.py -h
250+
usage: foo.py [-h] [-a str] [-i int] [-f] str
251+
252+
Argparse Python script
253+
254+
positional arguments:
255+
str A positional argument
256+
257+
optional arguments:
258+
-h, --help show this help message and exit
259+
-a str, --arg str A named string argument (default: )
260+
-i int, --int int A named integer argument (default: 0)
261+
-f, --flag A boolean flag (default: False)
262+
````
263+
264+
Notice how unhelpful a name like `positional` is?
265+
266+
## Getting the argument values
267+
268+
The values for the arguments will be accessible through the "long" name you define and will have been coerced to the Python data type you indicated. If I change `main` to this:
269+
270+
````
271+
# --------------------------------------------------
272+
def main():
273+
"""Make a jazz noise here"""
274+
275+
args = get_args()
276+
str_arg = args.arg
277+
int_arg = args.int
278+
flag_arg = args.flag
279+
pos_arg = args.positional
280+
281+
print('str_arg = "{}" ({})'.format(str_arg, type(str_arg)))
282+
print('int_arg = "{}" ({})'.format(int_arg, type(int_arg)))
283+
print('flag_arg = "{}" ({})'.format(flag_arg, type(flag_arg)))
284+
print('positional = "{}" ({})'.format(pos_arg, type(pos_arg)))
285+
````
286+
287+
And then run it:
288+
289+
````
290+
$ ./foo.py -a foo -i 4 -f bar
291+
str_arg = "foo" (<class 'str'>)
292+
int_arg = "4" (<class 'int'>)
293+
flag_arg = "True" (<class 'bool'>)
294+
positional = "bar" (<class 'str'>)
295+
````
296+
297+
Notice how we might think that `-f` takes the argument `bar`, but it is defined as a `flag` and the `argparse` knows that the program take
298+
299+
````
300+
$ ./foo.py foo -a bar -i 4 -f
301+
str_arg = "bar" (<class 'str'>)
302+
int_arg = "4" (<class 'int'>)
303+
flag_arg = "True" (<class 'bool'>)
304+
positional = "foo" (<class 'str'>)
305+
````

appendix/argparse/cat_n.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/env python3
2+
"""Python version of `cat -n`"""
3+
4+
import argparse
5+
6+
7+
# --------------------------------------------------
8+
def get_args():
9+
"""Get command-line arguments"""
10+
11+
parser = argparse.ArgumentParser(
12+
description='Argparse Python script',
13+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
14+
15+
parser.add_argument('file',
16+
metavar='FILE',
17+
type=argparse.FileType('r'),
18+
help='Input file')
19+
20+
return parser.parse_args()
21+
22+
23+
# --------------------------------------------------
24+
def main():
25+
"""Make a jazz noise here"""
26+
27+
args = get_args()
28+
fh = args.file
29+
30+
print('Reading "{}"'.format(fh.name))
31+
for i, line in enumerate(fh):
32+
print(i, line, end='')
33+
34+
35+
# --------------------------------------------------
36+
if __name__ == '__main__':
37+
main()

appendix/argparse/nargs+.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env python3
2+
"""nargs=+"""
3+
4+
import argparse
5+
6+
parser = argparse.ArgumentParser(
7+
description='nargs=+',
8+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
9+
10+
parser.add_argument('files', metavar='FILE', nargs='+', help='Some files')
11+
12+
args = parser.parse_args()
13+
files = args.files
14+
15+
print('number = {}'.format(len(files)))
16+
print('files = {}'.format(', '.join(files)))

appendix/argparse/nargs2.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env python3
2+
"""nargs=2"""
3+
4+
import argparse
5+
6+
parser = argparse.ArgumentParser(
7+
description='nargs=2',
8+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
9+
10+
parser.add_argument('files', metavar='FILE', nargs=2, help='Two files')
11+
12+
args = parser.parse_args()
13+
14+
file1, file2 = args.files
15+
print('file1 =', file1)
16+
print('file2 =', file2)

appendix/argparse/one_arg.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env python3
2+
"""One positional argument"""
3+
4+
import argparse
5+
6+
parser = argparse.ArgumentParser(
7+
description='One positional argument',
8+
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
9+
10+
parser.add_argument('first', metavar='str', help='First argument')
11+
args = parser.parse_args()
12+
print('first =', args.first)

appendix/argparse/sys_argv.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
#!/usr/bin/env python3
2+
3+
import sys
4+
5+
print('\n'.join(sys.argv))

0 commit comments

Comments
 (0)