Code Projects & Wrap Up#

Open In Colab

Q&A

Q: Could you give more examples on code style? I would be very helpful.
A: Yup - we’ll do a few more today!

Q: How is code style tested on the final?
A: We’ll use a linter, with a permissive threshold (discussed today)

Q: (in reference to improved code style) im not sure what is the difference btw this and the normal code? can you explain more pls
A: Functionally, for the computer, there’s nothing different. But, when we improve code style, it makes the code easier to read/understand by the humans.

Q: Will we have to remember any specific NumPy or Pandas methods on the Final?
A: No - all methods will be explained what they’re function is. You’ll need to know how to use them once you know what they do.

Course Announcements

Due this week:

  • CL9 (due Fri; optional, for 2 pts EC…but you should do it to get testing practice)

  • E1 or E2 retake (optional)

  • Final Exam (6/7-6/13)

    • Do the practice final exam

  • Post-course assessement (due 6/13; required; will be available Friday)

Notes:

  • Please complete your SETs - opportunity for EC

    • If >= 70% of the class completes their SETs for the course -> +0.5% (currently: 22%)

    • If any one staff member gets 7% of the class (43 people) to give them evals -> +0.5%

  • Office Hours end this Friday (no office hours during finals week; I will monitor Ed/email)

  • I do not round grades up

The Plan#

  • Code Projects

    • Projects you can now do…

    • Atbash encryption project example

    • Refactoring Code

  • Where to after COGS 18?

Some examples of projects you could try after COGS 18#

  • 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#

  1. Design and write tests

  2. Write some code

  3. Test to make sure it works

  4. Check code style (naming, spacing, etc.)

  5. Add documentation

  6. Move to module (if necessary)

  7. 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 string

      • inputs: input_string

      • returns: atbash_string

    • atbash_decrypt() : take encrypted string and decrypt using atbash

      • inputs: atbash_string

      • returns: decrypted_string

    • atbash_wrapper() : does either of the above, as specified with input parameter

      • inputs: input_string, method (either ‘encrypt’ or ‘decrypt’, default: ‘encrypt’)

      • returns output_string

Adding Unit Tests#

  • consider imports at the top

  • tests to check each function

    • check is callable/output is correct type

    • check specific output is correct (consider capitalization)

import unittest
from atbash import atbash_encrypt, atbash_decrypt, atbash_wrapper

class TestEncrypt(unittest.TestCase):
    
    def test_exist(self):
        self.assertTrue(callable(atbash_encrypt))

    def test_type(self):
        self.assertIsInstance(atbash_encrypt('hello'), str)

    def test_output(self):
        self.assertEqual('SVOOL', atbash_encrypt('HELLO'))
        self.assertEqual('SVOOL', atbash_encrypt('hello'))


class TestDecrypt(unittest.TestCase):
    
    def test_exist(self):
        self.assertTrue(callable(atbash_decrypt))

    def test_type(self):
        self.assertIsInstance(atbash_decrypt('hello'), str)

    def test_output(self):
        self.assertEqual('HELLO', atbash_decrypt('SVOOL'))
        self.assertEqual('HELLO', atbash_decrypt('svool'))


class TestWrapper(unittest.TestCase):
    
    def test_exist(self):
        self.assertTrue(callable(atbash_wrapper))

    def test_type(self):
        self.assertIsInstance(atbash_wrapper('hello', method='encrypt'), str)

    def test_output_encrypt(self):
        self.assertEqual('SVOOL', atbash_wrapper('hello', method='encrypt'))
        self.assertEqual('SVOOL', atbash_wrapper('HELLO', method='encrypt'))

    def test_output_decrypt(self):
        self.assertEqual('HELLO', atbash_wrapper('SVOOL', method='decrypt'))
        self.assertEqual('HELLO', atbash_wrapper('svool', method='decrypt'))

    def test_output_other(self):
        self.assertEqual("method should be either 'decrypt' or 'encrypt'", atbash_wrapper('svool', method='blargh'))

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')
'SVOOL'

Moving to a module…#

  • consider imports at the top

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.

# Run tests
import unittest
suite = unittest.TestLoader().discover('.', pattern='test_atbash.py')
unittest.TextTestRunner(verbosity=2).run(suite)
test_exist (test_atbash.TestEncrypt.test_exist) ... ok
test_output (test_atbash.TestEncrypt.test_output) ... ok
test_type (test_atbash.TestEncrypt.test_type) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK
<unittest.runner.TextTestResult run=3 errors=0 failures=0>

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')
'HELLO'
# Run tests
import unittest
suite = unittest.TestLoader().discover('.', pattern='test_atbash.py')
unittest.TextTestRunner(verbosity=2).run(suite)
test_exist (test_atbash.TestDecrypt.test_exist) ... ok
test_output (test_atbash.TestDecrypt.test_output) ... ok
test_type (test_atbash.TestDecrypt.test_type) ... ok
test_exist (test_atbash.TestEncrypt.test_exist) ... ok
test_output (test_atbash.TestEncrypt.test_output) ... ok
test_type (test_atbash.TestEncrypt.test_type) ... ok

----------------------------------------------------------------------
Ran 6 tests in 0.003s

OK
<unittest.runner.TextTestResult run=6 errors=0 failures=0>

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')
'SVOOL'
import unittest
# Run tests
suite = unittest.TestLoader().discover('.', pattern='test_atbash.py')
unittest.TextTestRunner(verbosity=2).run(suite)
test_exist (test_atbash.TestDecrypt.test_exist) ... ok
test_output (test_atbash.TestDecrypt.test_output) ... ok
test_type (test_atbash.TestDecrypt.test_type) ... ok
test_exist (test_atbash.TestEncrypt.test_exist) ... ok
test_output (test_atbash.TestEncrypt.test_output) ... ok
test_type (test_atbash.TestEncrypt.test_type) ... ok
test_exist (test_atbash.TestWrapper.test_exist) ... ok
test_output_decrypt (test_atbash.TestWrapper.test_output_decrypt) ... ok
test_output_encrypt (test_atbash.TestWrapper.test_output_encrypt) ... ok
test_output_other (test_atbash.TestWrapper.test_output_other) ... ok
test_type (test_atbash.TestWrapper.test_type) ... ok

----------------------------------------------------------------------
Ran 11 tests in 0.006s

OK
<unittest.runner.TextTestResult run=11 errors=0 failures=0>

Documentation#

  • Let’s add:

    • numpy-style docstrings

    • code comments

Putting it all together#

from atbash import atbash_wrapper
atbash_wrapper('hello')
'SVOOL'
atbash_wrapper('svool', method='decrypt')
'HELLO'
atbash_wrapper('hello', method='blargh')
"method should be either 'decrypt' or 'encrypt'"

Refactoring#

Refactoring is the process of restructuring existing computer code, without changing its external behaviour.

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()  
OUTPUT: Okay
OUTPUT: I'm too shy to answer questions. What do you want to talk about?
OUTPUT: Okay
OUTPUT: Bye!

Refactored Example: Chatbot#

What this function does:

  1. takes an input

  2. checks if input is a question

  3. checks if input is supposed to end the chat

  4. 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()  
OUTPUT: Thanks!
OUTPUT: I'm too shy to answer questions. What do you want to talk about?
OUTPUT: Thanks!
OUTPUT: I'm too shy to answer questions. What do you want to talk about?
OUTPUT: Thanks!
OUTPUT: Bye

The Goal#

To teach you a skill - of how to do things with Python.

You’ve been more formally trained than many people out in the world programming.

Where We’ve Been:#

  • Python & Jupyter

  • Variables

  • Operators

  • Conditionals

  • Functions

  • Lists, Tuples & Dictionaries

  • Loops

  • Objects & Classes

  • Command Line

  • Scientific Computing

  • Documentation, Code Style, Code Testing

Activity: Wrap-up#

Please complete the following Google Form: https://forms.gle/xdUjB27ywgG5bn4t7

How to Continue with Coding#

  • Write Code

  • Read Code

  • Learn and follow standard procedures

  • Do code reviews

  • Interact with the community

  • Build a code portfolio

Where do you go from here? (things)#

  • git & GitHub - version control, code!

  • Code in production: deubuggers (pdb)

  • Dependancy-management (PyPI, conda)

  • IDEs: VSCode

  • if want_more_data_science:

    • go to COGS 9

  • if you want to do more_data_science:

    • and Python: take COGS 108

    • and R: take COGS 137

  • if interested_in_research:

    • look for labs, tell them you can code

  • if you want_something_else:

    • go do it!

Acknowledgments#

Thank you to the TAs & IAs for their tireless work on this class.

Thank you students for you time, effort and patience.

The End