Code Projects#
modular design
minimal viable product
test-driven development
rapid prototyping
project organization
refactoring
code review
Modular Design#
Minimal Viable Product#
Test-Driven Development#
Rapid Prototyping#
Some rapid prototyping examples#
Chatbot: start and end a chat
Data Analysis: read a dataset in, make a basic plot
Car Inventory: object with method that adds a car to the Inventory
Artificial Agents: simple bot moves around randomly
Project Organization#
Notebooks: good for interactive development
For when seeing the inputs and outputs of code running needs to be seen start to finish
file ends in
.ipynb
Modules: for storing mature Python code, that you can import
you don’t use the functions in there, just define them
file ends in
.py
Scripts: a Python file for executing a particular task
this takes an input and does something start to finish
file ends in
.py
Project Workflow#
Typical project workflow:
Develop plan and write tests
Develop code interactively in a Jupyter notebook/text editor
As functions & classes become mature, move them to Python files that you then
import
As you do so, go back through them to check for code style, add documentation, and run your code tests
At the end of a project, (maybe) write a standalone script that runs the project
Project Notes#
Design and write tests
Write some code
Test to make sure it works
Check code style (naming, spacing, etc.)
Add documentation
Move to module (if necessary)
Run all tests
Project Design#
Idea: atbash encryption: return the capitalized, reverse alphabetical letter for each character in the input string
Design:
atbash_encrypt()
: take input string and retrun atbash encrypted stringinputs:
input_string
returns:
atbash_string
atbash_decrypt()
: take encrypted string and decrypt using atbashinputs:
atbash_string
returns:
decrypted_string
atbash_wrapper()
: does either of the above, as specified with input parameterinputs:
input_string
,method
(either ‘encrypt’ or ‘decrypt’, default: ‘encrypt’)returns
output_string
Adding Unit Tests#
# from atbash import *
# def test_atbash_encrypt():
# assert callable(atbash_encrypt)
# assert isinstance(atbash_encrypt('hello'), str)
# assert atbash_encrypt('HELLO') == 'SVOOL'
# assert atbash_encrypt('hello') == 'SVOOL'
# def test_atbash_decrypt():
# assert callable(atbash_decrypt)
# assert isinstance(atbash_decrypt('hello'), str)
# assert atbash_decrypt('SVOOL') == 'HELLO'
# assert atbash_decrypt('svool') == 'HELLO'
# def test_atbash_wrapper():
# assert callable(atbash_wrapper)
# assert isinstance(atbash_wrapper('hello', method='encrypt'), str)
# assert atbash_wrapper('hello', method='encrypt') == 'SVOOL'
# assert atbash_wrapper('HELLO', method='encrypt') == 'SVOOL'
# assert atbash_wrapper('SVOOL', method='decrypt') == 'HELLO'
# assert atbash_wrapper('svool', method='decrypt') == 'HELLO'
# assert atbash_wrapper('svool', method='blargh') == "method should be either 'decrypt' or 'encrypt'"
…move to test file
…will uncomment as I write the code/functions
Writing Code#
atbash_encrypt
#
def atbash_encrypt(input_string):
alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
reverse_alpha = 'ZYXWVUTSRQPONMLKJIHGFEDCBA'
atbash_string = ''
for char in input_string:
char = char.upper()
if char in alpha:
position = alpha.find(char)
atbash_string += reverse_alpha[position]
else:
atbash_string = None
break
return atbash_string
# smoke test
atbash_encrypt('Hello')
Moving to a module…#
Now that we have our working function, we should move it to a module file
don’t forget to import your new module at the top of the test file
Note on imports
: If you want to be able to use modules (imports) within a module/script, be sure to import it at the top. This applies to test files as well.
!pytest test_atbash.py
atbash_decrypt
#
# reminder: consider code style!
def atbash_decrypt(atbash_string):
ALPHA='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
REVERSEALPHA='ZYXWVUTSRQPONMLKJIHGFEDCBA'
atbash_string=atbash_string.upper()
decrypted_string=''
for l in atbash_string:
if l in REVERSEALPHA:
letterindex=REVERSEALPHA.find(l)
decrypted_string=decrypted_string+ALPHA[letterindex]
else: decrypted_string=decrypted_string+l
return decrypted_string
# smoke test
atbash_decrypt('SVOOL')
!pytest test_atbash.py
atbash_wrapper
#
def atbash_wrapper(input_string, method='encrypt'):
if method == 'encrypt':
output_string = atbash_encrypt(input_string)
elif method == 'decrypt':
output_string = atbash_decrypt(input_string)
else:
output_string = "method should be either 'decrypt' or 'encrypt'"
return output_string
# smoke test
atbash_wrapper('hello', 'encrypt')
!pytest test_atbash.py
Documentation#
Code in final project/exam requires:
numpy-style docstrings
code comments
Putting it all together#
from atbash import atbash_wrapper
atbash_wrapper('hello')
atbash_wrapper('svool', method='decrypt')
atbash_wrapper('hello', method='blargh')
Refactoring#
Think of this as restructuring and final edits on your essay.
Nesting Functions - If you have a whole bunch of functions, if statements, and for/while loops together within a single function, you probably want (need?) to refactor.
Clean functions accomplish a single task!
DRY: Don’t Repeat Yourself
Refactoring Example: Chatbot#
import random
def have_a_chat():
"""Main function to run our chatbot."""
chat = True
while chat:
# Get a message from the user
msg = input('INPUT :\t')
out_msg = None
# Check if the input is a question
input_string = msg
if '?' in input_string:
question = True
else:
question = False
# Check for an end msg
if 'quit' in input_string:
out_msg = 'Bye!'
chat = False
# If we don't have an output yet, but the input was a question,
# return msg related to it being a question
if not out_msg and question:
out_msg = "I'm too shy to answer questions. What do you want to talk about?"
# Catch-all to say something if msg not caught & processed so far
if not out_msg:
out_msg = random.choice(['Good.', 'Okay', 'Huh?', 'Yeah!', 'Thanks!'])
print('OUTPUT:', out_msg)
have_a_chat()
Refactored Example: Chatbot#
What this function does:
takes an input
checks if input is a question
checks if input is supposed to end the chat
return appropriate response if question, end chat, or other
That’s four different things! Functions should do a single thing…
def get_input():
"""ask user for an input message"""
msg = input('INPUT :\t')
out_msg = None
return msg, out_msg
def is_question(input_string):
"""determine if input from user is a question"""
if '?' in input_string:
output = True
else:
output = False
return output
def end_chat(input_list):
"""identify if user says 'quit' in input and end chat"""
if 'quit' in input_list:
output = 'Bye'
chat = False
else:
output = None
chat = True
return output, chat
def return_message(out_msg, question):
"""generic responses for the chatbot to return"""
# If we don't have an output yet, but the input was a question,
# return msg related to it being a question
if not out_msg and question:
out_msg = "I'm too shy to answer questions. What do you want to talk about?"
# Catch-all to say something if msg not caught & processed so far
if not out_msg:
out_msg = random.choice(['Good.', 'Okay', 'Huh?', 'Yeah!', 'Thanks!'])
return out_msg
def have_a_chat():
"""Main function to run our chatbot."""
chat = True
while chat:
# Get input message from the user
msg, out_msg = get_input()
# Check if the input is a question
question = is_question(msg)
# Check for an end msg
out_msg, chat = end_chat(msg)
# specify what to return
out_msg = return_message(out_msg = out_msg, question = question)
print('OUTPUT:', out_msg)
have_a_chat()