The goal of this exercise is to learn some best practices for writing Python code.
- Getting set up
- Learning objective
- The goal
- The script
- Best practice 1: Put code into functions
- Best practice 2: Write modules not scripts
- Best practice 3: Use docstrings to document your code
- Best practice 4: Add tests to your docstrings
- Acknowledgments
- License
At this point, you should have (1) an account on Github and (2) been introduced to the very basics of Git.
-
Login to your Github account.
-
Fork this repository, by clicking the 'Fork' button on the upper right of the page.
After a few seconds, you should be looking at your copy of the repo in your own Github account.
-
Click the 'Clone or download' button, and copy the URL of the repo via the 'copy to clipboard' button.
-
In your terminal, navigate to where you want to keep this repo (you can always move it later, so just your home directory is fine). Then type:
$ git clone the-url-you-just-copied
and hit enter to clone the repository. Make sure you are cloning your fork of this repo.
-
Next,
cd
into the directory:$ cd the-name-of-directory-you-just-cloned
-
At this point, you should be in your own local copy of the repository.
-
As you work on the exercise below, be sure to frequently
add
andcommit
your work andpush
changes to the remote copy of the repo hosted on GitHub. Don't enter these commands now; this is just to jog your memory:$ # Do some work $ git add file-you-worked-on.py $ git commit $ git push origin master
Learn some practices in organizing and documenting your code when writing Python scripts.
The goal of this exercise is for you to take a simple, working Python script and transform it into a much more helpful and useful Python module.
Here is the code that is currently in the smallest_factor.py
file in
this repository:
#! /usr/bin/env python3
# A script for getting the smallest prime factor of an integer.
import sys
if len(sys.argv) != 2:
sys.exit(sys.argv[0] + ": Expecting one command line argument -- the integer for which to get the smallest factor")
n = int(sys.argv[1])
if n < 1:
sys.exit(sys.argv[0] + ": Expecting a positive integer")
smallest_prime_factor = None
for i in range(2, n):
if (n % i) == 0:
smallest_prime_factor = i
break
if smallest_prime_factor is None:
print(n)
else:
print(smallest_prime_factor)
Let's run it as a script and see what happens:
$ python3 smallest_factor.py
smallest_factor.py: Expecting one command line argument -- the integer for which to get the smallest factor
We get a message that we need to provide an integer to factor, so let's try:
$ python3 smallest_factor.py 9
3
OK, the script seems to work, but it isn't all that it could be.
Let's modify the code in smallest_factor.py
and make it better.
Before proceeding, read through the code in the smallest_factor.py
script
and try your best to understand what it is doing.
Feel free to tinker with the code and add print()
statements and re-run the
script to test your hypotheses about what the code is doing.
When you are done tinkering, you can use git
to undo all your changes
before proceeding:
$ git checkout -- smallest_factor.py
It is always good practice to put each block of code that performs a particular task into a function. This will help compartmentalize your code into reusable units.
Let's take this code:
smallest_prime_factor = None
for i in range(2, n):
if (n % i) == 0:
smallest_prime_factor = i
break
and turn it into a function:
def get_smallest_prime_factor(n):
for i in range(2, n):
if (n % i) == 0:
return i
return None
Functions should be defined after we import modules and before other code, so
put the new function after the line where we import the sys
module.
Now, our script looks like:
#! /usr/bin/env python3
# A script for getting the smallest prime factor of an integer.
import sys
def get_smallest_prime_factor(n):
for i in range(2, n):
if (n % i) == 0:
return i
return None
if len(sys.argv) != 2:
sys.exit(sys.argv[0] + ": Expecting one command line argument -- the integer for which to get the smallest factor")
n = int(sys.argv[1])
if n < 1:
sys.exit(sys.argv[0] + ": Expecting a positive integer")
smallest_prime_factor = get_smallest_prime_factor(n)
if smallest_prime_factor is None:
print(n)
else:
print(smallest_prime_factor)
Let's make sure the script still works:
$ python3 smallest_factor.py 9
3
Are you getting errors? Try to fix the bugs in your code. The error messages that Python reports take some getting use to, but they are very helpful and informative, so read them carefully and try to figure out what is going wrong.
What if we want to use the get_smallest_prime_factor
function in other
scripts we are writing?
We don't want to re-write this function in every script where
we want to use it.
Rather, we want to be able to import the function as part of a module.
Let's try importing our code now:
$ python3
>>> import smallest_factor
What happened? Well, the code in smallest_factor.py
ran as a script and
closed the Python interpreter when finished.
That means we cannot use our shiny new function outside of the script
it currently lives in. Not cool! Let's fix this problem.
All of the code after our new function is simply the "command-line interface,"
or CLI, of our script.
This code simply processes the arguments from the command line and reports the
result to standard output.
Let's use the conditional if __name__ == '__main__':
so that this code
only gets run when the file is executed as a script.
After all, that is the only time it needs to run!
if __name__ == '__main__':
if len(sys.argv) != 2:
sys.exit(sys.argv[0] + ": Expecting one command line argument -- the integer for which to get the smallest factor")
n = int(sys.argv[1])
if n < 1:
sys.exit(sys.argv[0] + ": Expecting a positive integer")
smallest_prime_factor = get_smallest_prime_factor(n)
if smallest_prime_factor is None:
print(n)
else:
print(smallest_prime_factor)
Don't worry about what __name__
is. It's just a variable to which Python
assigns the value '__main__'
when the code is being run as script.
Now our script looks like:
#! /usr/bin/env python3
# A script for getting the smallest prime factor of an integer.
import sys
def get_smallest_prime_factor(n):
for i in range(2, n):
if (n % i) == 0:
return i
return None
if __name__ == '__main__':
if len(sys.argv) != 2:
sys.exit(sys.argv[0] + ": Expecting one command line argument -- the integer for which to get the smallest factor")
n = int(sys.argv[1])
if n < 1:
sys.exit(sys.argv[0] + ": Expecting a positive integer")
smallest_prime_factor = get_smallest_prime_factor(n)
if smallest_prime_factor is None:
print(n)
else:
print(smallest_prime_factor)
Try running it:
$ python3 smallest_factor.py 9
3
Now try importing it as a module:
$ python3
>>> import smallest_factor
Notice that our code nested in the if __name__ == '__main__'
statement no longer runs when we import our file as a module!
Now we can use our get_smallest_prime_factor
function outside
of the file it lives in. Try it:
>>> smallest_factor.get_smallest_prime_factor(9)
3
Also, we can use the help
function, just like anything else in Python:
>>> help(smallest_factor)
>>> help(smallest_factor.get_smallest_prime_factor)
NOTE: help
displays the help menu using the program less
.
Hit the q
key to exit the help menu.
Hmmm... our help messages are not very ... helpful. Let's fix that next.
First, let's change our comment at the top of the script to a docstring.
Change
#! /usr/bin/env python3
# A script for getting the smallest prime factor of an integer.
to
#! /usr/bin/env python3
"A module for getting the smallest prime factor of an integer."
Next, let's add a docstring to our function:
def get_smallest_prime_factor(n):
"""
Returns the smallest integer that is a factor of `n`.
If `n` is a prime number `None` is returned.
Parameters
----------
n : int
The integer to be factored.
Returns
-------
int or None
The smallest integer that is a factor of `n`
or None if `n` is a prime.
"""
for i in range(2, n):
if (n % i) == 0:
return i
return None
This docstring is a bit overkill for such a simple function, but it demonstrates some good practices.
Now, let's check our help messages again:
$ python3
>>> import smallest_factor
>>> help(smallest_factor)
>>> help(smallest_factor.get_smallest_prime_factor)
Much better.
We can even add examples to docstrings that can be run
as tests!!
Let's add a section to our docstring for the get_smallest_prime_factor
function with some examples of how its used and the numbers it should return:
def get_smallest_prime_factor(n):
"""
Returns the smallest integer that is a factor of `n`.
If `n` is a prime number `None` is returned.
Parameters
----------
n : int
The integer to be factored.
Returns
-------
int or None
The smallest integer that is a factor of `n`
or None if `n` is a prime.
Examples
--------
>>> get_smallest_prime_factor(7)
>>> get_smallest_prime_factor(8)
2
>>> get_smallest_prime_factor(9)
3
"""
for i in range(2, n):
if (n % i) == 0:
return i
return None
Now, you can run the examples as tests using the doctest
module of Python:
$ python3 -m doctest -v smallest_factor.py
Trying:
get_smallest_prime_factor(7)
Expecting nothing
ok
Trying:
get_smallest_prime_factor(8)
Expecting:
2
ok
Trying:
get_smallest_prime_factor(9)
Expecting:
3
ok
1 items had no tests:
tmp
1 items passed all tests:
3 tests in tmp.get_smallest_prime_factor
3 tests in 2 items.
3 passed and 0 failed.
Test passed.
Nice! The examples provide useful information in the help menu for someone (e.g., you 6 months from now) learning how to use your function. Running the examples as tests help confirm the function is working as expected.
Here's our smallest_factor.py
code in its final form:
#! /usr/bin/env python3
"A module for getting the smallest prime factor of an integer."
import sys
def get_smallest_prime_factor(n):
"""
Returns the smallest integer that is a factor of `n`.
If `n` is a prime number `None` is returned.
Parameters
----------
n : int
The integer to be factored.
Returns
-------
int or None
The smallest integer that is a factor of `n`
or None if `n` is a prime.
Examples
--------
>>> get_smallest_prime_factor(7)
>>> get_smallest_prime_factor(8)
2
>>> get_smallest_prime_factor(9)
3
"""
for i in range(2, n):
if (n % i) == 0:
return i
return None
if __name__ == '__main__':
if len(sys.argv) != 2:
sys.exit(sys.argv[0] + ": Expecting one command line argument -- the integer for which to get the smallest factor")
n = int(sys.argv[1])
if n < 1:
sys.exit(sys.argv[0] + ": Expecting a positive integer")
smallest_prime_factor = get_smallest_prime_factor(n)
if smallest_prime_factor is None:
print(n)
else:
print(smallest_prime_factor)
- The code in our
get_smallest_prime_factor
function is not very efficient. Can you improve it? - Write another script (module) that calculates and returns a list of prime
numbers that are factors of an integer provided at the command line
- Make sure to
import smallest_factor
and use theget_smallest_prime_factor
function in your new script. - Make sure to follow the best practices above!
- Make sure to
This work was made possible by funding provided to Jamie Oaks from the National Science Foundation (DEB 1656004).
This work is licensed under a Creative Commons Attribution 4.0 International License.