Skip to content

Commit b6e614b

Browse files
committed
make suggested changes to argparse lesson
1 parent 5f06914 commit b6e614b

File tree

1 file changed

+71
-20
lines changed

1 file changed

+71
-20
lines changed

_episodes/07-command_line.md

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ objectives:
88
- "Make code executable from the Linux command line."
99
- "Use argparse to accept user inputs."
1010
keypoints:
11-
- "You must `import sys` in your code to accept user arguments."
12-
- "The name of the script itself is always `sys.argv[0]` so the first user input is normally `sys.argv[1]`."
11+
- "You must `import argparse` in your code to accept user arguments."
12+
- "You add must first create an argument parser using `parser = argparse.ArgumentParser`"
13+
- "You add arguments using `parser.add_argument`"
1314
---
1415
## Creating and running a python input file
1516

@@ -80,19 +81,19 @@ import argparse
8081

8182
We are importing a library called [https://docs.python.org/3/library/argparse.html](argparse) which can be used to easily make scripts with command line arguments. `Argparse` has the ability to allow us to easily write documentation for our scripts as well.
8283

83-
First, we have to tell `argparse` what are arguments are. We tell argparse that we want to add some arguments using the following command:
84+
We tell argparse that we want to add a command line interface. The syntax for this is
8485

8586
~~~
8687
parser = argparse.ArgumentParser(description="This script analyzes a user given xyz file and outputs the length of the bonds.")
8788
~~~
8889
{: .language-python}
8990

90-
We've included a description of the script for our users using `description=`.
91+
We've included a description of the script for our users using `description=`. This description does not need to explain what the arguments are, that will be done automatically for us in the next steps.
9192

9293
Next, we have to tell `argparse` what arguments it should expect. In general, the syntax for this is
9394

9495
~~~
95-
parser.add_argument("argument name", help="Your help message for this argument.")
96+
parser.add_argument("argument_name", help="Your help message for this argument.")
9697
~~~
9798
{: .language-python}
9899

@@ -109,8 +110,7 @@ args = parser.parse_args()
109110
~~~
110111
{: .language-python}
111112

112-
Our arguments are in the `args` variable. We can get the value of an argument by using `args.ARGUMENT_NAME`, so to get the xyz file the user puts in, we use `args.xyz_file`. Notice that what follows after the dot is the same thing we but in quotation marks when using `add_argument.`
113-
113+
Our arguments are in the `args` variable. We can get the value of an argument by using `args.argument_name`, so to get the xyz file the user puts in, we use `args.xyz_file`. Notice that what follows after the dot is the same thing we but in quotation marks when using `add_argument.`
114114

115115
~~~
116116
xyzfilename = args.xyz_file
@@ -150,18 +150,76 @@ $ python analyze.py data/water.xyz
150150

151151
Check that the output of your code is what you expected.
152152

153-
154153
What would happen if the user forgot to specify the name of the xyz file?
155154

156155
~~~
157156
usage: analyze.py [-h] xyz_file
158157
analyze.py: error: the following arguments are required: xyz_file
159158
~~~
160-
{: .output}
159+
{: .error}
161160

162161
Argparse handles this for us and prints an error message. It tells us that we must specifiy an xyz file.
163162

164-
## Optional Arguments
163+
Try out your program with other XYZ files in your `data` folder.
164+
165+
## The "main" part of our script
166+
We need to add one more thing to our code. When you write a code that includes function definitions and a main script, you need to tell python which part is the main script. (This becomes very important later when we are talking about testing.) *After* your import statements and function definitions and *before* use `argparse`
167+
```
168+
if __name__ == "__main__":
169+
```
170+
{: .language-python}
171+
172+
Since this is an `if` statement, you now need to indent each line of your main script below this if statement. Be very careful with your indentation! Don't use a mixture of tabs and spaces! A good way to indent multiple lines in many text editors is to highlight the lines you would like to indent, then press `tab`.
173+
174+
Save your code and run it again. It should work exactly as before. If you now get an error message, it is probably due to inconsistent indentation.
175+
176+
~~~
177+
import os
178+
import numpy
179+
import argparse
180+
181+
def calculate_distance(atom1_coord, atom2_coord):
182+
x_distance = atom1_coord[0] - atom2_coord[0]
183+
y_distance = atom1_coord[1] - atom2_coord[1]
184+
z_distance = atom1_coord[2] - atom2_coord[2]
185+
bond_length_12 = numpy.sqrt(x_distance**2+y_distance**2+z_distance**2)
186+
return bond_length_12
187+
188+
def bond_check(atom_distance, minimum_length=0, maximum_length=1.5):
189+
if atom_distance > minimum_length and atom_distance <= maximum_length:
190+
return True
191+
else:
192+
return False
193+
194+
def open_xyz(filename):
195+
xyz_file = numpy.genfromtxt(fname=filename, skip_header=2, dtype='unicode')
196+
symbols = xyz_file[:,0]
197+
coord = (xyz_file[:,1:])
198+
coord = coord.astype(numpy.float)
199+
return symbols, coord
200+
201+
202+
if __name__ == "__main__":
203+
204+
## Get the arguments.
205+
parser = argparse.ArgumentParser(description="This script analyzes a user given xyz file and outputs the length of the bonds.")
206+
parser.add_argument("xyz_file", help="The filepath for the xyz file to analyze.")
207+
208+
args = parser.parse_args()
209+
210+
symbols, coord = open_xyz(args.xyz_file)
211+
num_atoms = len(symbols)
212+
213+
for num1 in range(0,num_atoms):
214+
for num2 in range(0,num_atoms):
215+
if num1<num2:
216+
bond_length_12 = calculate_distance(coord[num1], coord[num2])
217+
if bond_check(bond_length_12, minimum_length=args.minimum_length, maximum_length=args.maximum_length) is True:
218+
print(F'{symbols[num1]} to {symbols[num2]} : {bond_length_12:.3f}')
219+
~~~
220+
{: .language-python}
221+
222+
## Extension - Optional Arguments
165223
What's another argument we might want to include? We also might want to let the user specify a minimum and maximum bond length on the command line. We would want these to be optional, just like they are in our function.
166224

167225
We can add optional arguments by putting a dash (`-`) or two dashes (`--`) in front of the argument name when we add an argument. Add this line below where you added the fist argument. Note that all `add_argument` lines should be above the line with `parse_args`.
@@ -200,16 +258,7 @@ if bond_check(bond_length_12, minimum_length=args.minimum_length, maximum_length
200258
~~~
201259
{: .language-python}
202260

203-
## The "main" part of our script
204-
We need to add one more thing to our code. When you write a code that includes function definitions and a main script, you need to tell python which part is the main script. (This becomes very important later when we are talking about testing.) *After* your import statements and function definitions and *before* use `argparse`
205-
```
206-
if __name__ == "__main__":
207-
```
208-
{: .language-python}
209-
210-
Since this is an `if` statement, you now need to indent each line of your main script below this if statement. Be very careful with your indentation! Don't use a mixture of tabs and spaces! A good way to indent multiple lines in many text editors is to highlight the lines you would like to indent, then press `tab`.
211-
212-
Save your code and run it again. It should work exactly as before. If you now get an error message, it is probably due to inconsistent indentation.
261+
Our final program looks like this:
213262

214263
~~~
215264
import os
@@ -260,4 +309,6 @@ if __name__ == "__main__":
260309
~~~
261310
{: .language-python}
262311

312+
313+
263314
{% include links.md %}

0 commit comments

Comments
 (0)