Making code reusable via functions
Now that we have proved to ourselves that it’s possible to generate a catalog, we want to tidy up the code a little so that it can be reused and adapted easily.
At the moment our intended audience is ourselves, so we are content with keeping all the relevant information within the code itself. As a first step we create new variables that record the target location (ref_ra
/ref_dec
), the number of stars to generate (nsources
), and how far from the central location the new positions should be generated (radius
). This is done using global variables at the top of our file like this:
#! /usr/bin/env python
# Demonstrate that we can simulate a catalogue of stars on the sky
# Determine Andromeda location in ra/dec degrees
import numpy as np
import math
# configuration using global variables
nsources = 1000
# from wikipedia
ref_ra = '00:42:44.3'
ref_dec = '41:16:09'
radius = 1
From here we need to make a few changes to our code so that these variables are now used in place of the previously hard-coded values.
We then take all the code that is part of the position generation stage and bundle it all together into a function which we call generate_positions
. This allows us to keep that part of the code separate from the file writing stage. In fact while we are at it we should make a function for doing the file writing. Lets call it write_file
. While we are writing these functions we can use python docstrings to document the intent of each function.
def generate_positions():
"""
Create `nsources` random locations within `radius` degrees of the reference `ref_ra`/`ref_dec`.
Returns
-------
ra, dec : numpy.array
Arrays of ra and dec coordinates in degrees.
"""
# convert DMS -> degrees
d, m, s = ref_dec.split(':')
dec = int(d)+int(m)/60+float(s)/3600
# convert HMS -> degrees
h, m, s = ref_ra.split(':')
ra = 15*(int(h)+int(m)/60+float(s)/3600)
ra = ra/math.cos(dec*math.pi/180) # don't forget projection effects
ra_offsets = np.random.uniform(-1*radius, radius, size=nsources)
dec_offsets = np.random.uniform(-1*radius, radius, size=nsources)
ras = ra + ra_offsets
decs = dec + dec_offsets
return ras, decs
def write_file(ras, decs):
"""
Write the ra/dec catalog to a file, and include a header and IDs.
"""
with open('catalog.csv', 'w') as f:
# creat a header row
print("id,ra,dec", file=f)
for i in range(nsources):
# use a csv format
print("{0}, {1:7.4f}, {2:7.4f}".format(i, ras[i], decs[i]), file=f)
return
Note that the first function takes no parameters, while the second is designed to take two lists of positions as input. By breaking our code into functional blocks we have a number of advantages:
- We can reuse a block of code by calling the function multiple times, and don’t have to bother with duplicated code.
- Duplicated code means duplicated bugs!
- The code within each function will not interfere with code in other functions, allowing us to reuse names of variables, and for unused variables and memory to be deleted upon exiting the function.
- We can document each function separately using a docstring to describe the intent of the code.
- This is in addition to the inline comments that we have used.
Our script can now be finished with a few more lines of code.
# Do the work
ras, decs = generate_positions()
write_file(ras, decs)
As a developer the code is now separated into functional parts, so if something goes wrong or needs changing, we know where to look to make those changes. As a user of the code we can open the file and read the first few lines to see what the default parameters are, and modify them if we choose. Additionally, as a user we can read the names and docstrings of the functions to understand what the code is doing rather than having to read the code itself.
Now we have a piece of code that is easier to use. However, this code is only really usable as is, and requires people to read/edit the source code to understand how it works and adapt it for their use. In the next section we’ll see how to further generalise our code by using more function parameters, and adding a command line interface.