<jupyter_start><jupyter_text>Introduction to Working with User Inputs**Last Updated:** 2021-04-16In the previous modules, we worked with scripts that required editing the source code (e.g., Jupyter notebooks) to modify the behavior. As you expand your toolset and tackle more complex projects, you may find yourself building scripts that need to respond to various user inputs. Because there is a wide variety of methods for working with user inputs, this module will be the first in a series covering this topic. In this module, we will introduce:1. How to prompt a user for information while a script is running2. How to use `sys.argv` to access command-line arguments Table of Contents1. [Using This Notebook](Using-This-Notebook)2. [Requesting User Input While a Script is Running](Requesting-User-Input-While-a-Script-is-Running)3. [Simple Command-Line Argument Parsing Using `sys.argv`](Simple-Command-Line-Argument-Parsing-Using-sys.argv)4. [Example Problems](Example-Problems)5. [Closing](Closing)6. [Release Notes](Release-Notes) Using This NotebookUnlike notebooks from previous modules, **you should not run all cells in this notebook**. This notebook relies on iterative updates to common script files, so working with cells out of order may result in unexpected outputs.This notebook uses Jupyter Notebook magic functions to help simulate command line inputs. In particular, two magic functions are essential to this module:- `%writefile`: Saves the content of the code cell to the specified filename- `%run `: Runs the following command via a virtual terminal    - To run any scripts below on your local terminal after downloading them, you would use `python3 my_script.py`  Requesting User Input While a Script is RunningOne common task that scripts may do is prompt a user for additional input after running (e.g., request a file path). To showcase this functionality, let's first introduce a simple script that will print out an introduction containing your name and the number of pets you have (`greetings.py`).<jupyter_code>%%writefile greetings.py
#!usr/bin/bash python

my_name = "Anthony"
number_of_pets = 1

print("My name is {} and I have {} pet(s).".format(my_name, number_of_pets))
%run greetings.py<jupyter_output>My name is Anthony and I have 1 pet(s).<jupyter_text>While this script does the job admirably, what would happen if someone else wanted to use the script to generate their introduction? Since we have hard-coded in our name and number of pets, they would have to go in and change the source code themselves. For a simple script like `greetings.py`, the change may not present an issue. However, you usually don't want others editing your code to personalize its behavior.Instead, we make use of the `input([prompt])` function (See [documentation](https://docs.python.org/3/library/functions.htmlinput)). This function will print out the `prompt` to the terminal and wait until the user inputs text. Once the user inputs text, it will return it as a *string* that can be stored like any other variable. Let's use this function to improve `greetings.py` to request the user's details instead of including them in the source.<jupyter_code>%%writefile greetings.py
#!usr/bin/bash python

my_name = input("Name: ")
number_of_pets = input("Number of Pets: ")

print("My name is {} and I have {} pet(s).".format(my_name, number_of_pets))
%run greetings.py<jupyter_output>Name:  Anthony
Number of Pets:  1<jupyter_text>> **Style Suggestion**: Leave a trailing space at the end of the `input()` prompt to leave a space between the prompt and the user input on the command line.Next, let's create another script that will print out the sum of the two integers a user provides (`int_sum.py`).<jupyter_code>%%writefile int_sum.py
#!usr/bin/bash python

num_one = input("First Number: ")
num_two = input("Second Number: ")

print(num_one + num_two)
%run int_sum.py<jupyter_output>First Number:  1
Second Number:  2<jupyter_text>When you run this script, you should immediately notice something wrong: the addition operator conducted *string addition* rather than integer addition. This error stems from the fact that `input()` **always returns a string**. In the example above, because we do not recast the inputs as integers, Python interprets them as a string. Let's modify the script so that we now explicitly recast the inputs appropriately. Try running the script with integer-like inputs!<jupyter_code>%%writefile int_sum.py
#!usr/bin/bash python

num_one = int(input("First Number: "))
num_two = int(input("Second Number: "))

print(num_one + num_two)
%run int_sum.py<jupyter_output>First Number:  1
Second Number:  2<jupyter_text>While this iteration may appear to fix the input errors, there's actually one more that we need to address. Try rerunning the script and pass in an input that *cannot be converted to an integer* (e.g., "3.2" or "1w"). Using this type of input should raise an exception—the `int` casting fails because of an invalid input. While it can be tempting to assume that this type of error would not occur, it's quite common (e.g., a user types the wrong key) and, if left unaddressed, would completely crash the script. We will use a `try-except` pattern to safely catch the error caused by invalid inputs to address this issue in the new script iteration below:<jupyter_code>%%writefile user_sum.py
#!usr/bin/bash python

def prompt_for_integer(prompt):
    while True:
        try:
            return(int(input(prompt)))
        except ValueError:
            print("Please enter a valid integer.")

num_one = prompt_for_integer("First Number: ")
num_two = prompt_for_integer("Second Number: ")

print(num_one + num_two)
%run user_sum.py<jupyter_output>First Number:  3b<jupyter_text>While we haven't covered error handling yet, the basic idea of `try-except` loop is similar to that of an `if` loop. The code within the `try` block is executed first. If it would raise an exception, the code in the `except` block is executed (instead of crashing). In the script above, since we have wrapped the `try-except` pattern in a `while True` loop, the script will only break out of the loop if the user inputs a valid integer.With this validating function in place, we now have all of the basic functionality we need to get a user prompt while executing a script safely. In summary, we have implemented a way to:1. Prompt a user for a specific type of input2. Validate that the input conforms to the expected type3. Reprompt the user if the input is not valid.  Simple Command-Line Argument Parsing Using `sys.argv`In the first section of this module, we introduced a method for accepting inputs *in the middle of executing a script*. However, many scripts accept inputs, or "command-line arguments," when they are initially called. Passing these command-line arguments to a Python script follows the standard syntax for any command or language:```python3 my_script.py argument_1 argument_2```Once you pass these arguments to a script, there are many different ways we can access and use these inputs. Some methods provide a minimal amount of functionality, while others create entire frameworks for custom interfaces. The `sys` module is an essential Python module for interacting with the local operating system. For our purposes, we are only interested in one particular part of the `sys` module: the `sys.argv` list.  The `sys.argv` list contains all of the arguments a user passes when they call a Python script. Importantly, `sys.argv` is just a Python list, so all list methods can be applied! Let's use a simple script (`print_sys_argv.py`) to explore some of the details.**One thing to think about**: What type will the arguments be?<jupyter_code>%%writefile print_sys_argv.py
#!usr/bin/bash python

import sys


# Print out the length of the list
print("Length of sys.argv: {}".format(len(sys.argv)))

# Print out the entire list
print(sys.argv)  

# Loop over each argument and print
for arg in sys.argv:
    print("{}: {}".format(arg, type(arg)))<jupyter_output>Writing print_sys_argv.py<jupyter_text>This script will print out the entire list, followed by each argument and its type. The cell below will call the script using three arguments: 1) a string-like argument, 2) an integer-like argument, and 3) a float-like argument.<jupyter_code>%run print_sys_argv.py argument_1 2 3.0<jupyter_output>Length of sys.argv: 4
['print_sys_argv.py', 'argument_1', '2', '3.0']
print_sys_argv.py: <class 'str'>
argument_1: <class 'str'>
2: <class 'str'>
3.0: <class 'str'><jupyter_text>After running the cell above, take a look at the printed output. As expected, `sys.argv` is a Python list of all of the arguments. However, there it may seem like there is 1 extra argument: the name of the script. If you pay attention to the syntax of running a Python script, we are *actually* running `python3` and not the script itself, so `sys.argv` captures the script name as the "first" argument. Practically, this just means:1. We'll always know what script we initially ran (good for complicated cases)2. The arguments list (minus the script name) is effectively 1-indexed (the 2nd argument is `sys.argv[2]`)Next, notice that all of the arguments are initially parsed as *strings*, just like the `input()` method! As a result, you will still use similar typecasting and valdiation approaches introduced earlier when prompting the user for non-string inputs.Let's now rewrite our `greetings.py` script to utilize command line arguments instead (saved as `greetings_cli.py`).<jupyter_code>%%writefile greetings_cli.py
#!usr/bin/bash python

import sys

my_name = sys.argv[1]
number_of_pets = sys.argv[2]

print("My name is {} and I have {} pet(s).".format(my_name, number_of_pets))
%run greetings_cli.py Anthony 1<jupyter_output>My name is Anthony and I have 1 pet(s).<jupyter_text>We can now prompt the user to input all of the information *at the beginning* instead of stopping to request input multiple times throughout the script! However, there are important limits and downsides for directly using `sys.argv` for parsing user inputs. For example, nothing prevents a user from providng the inputs *in the wrong order or from passing any number of arguments*:<jupyter_code>%run greetings_cli.py 1 Anthony argument_3 3453 adsg<jupyter_output>My name is 1 and I have <NAME>(s).<jupyter_text>In fact, *unless a user opens up and inspects the script directly, there is no way of figuring out what the expected arguments are*. For simple cases like the one above or in early development, this method may be all you need. For more complex or user-focused cases, we would need to start looking at more complex argument parsing modules, such as the `argparse` module.  Example ProblemsTo test your understanding, two example problems are provided below. A solutions notebook is available at the following links:[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/anthony-agbay/introduction-to-python-environment/main?urlpath=git-pull%3Frepo%3Dhttps%253A%252F%252Fgithub.com%252Fanthony-agbay%252Fintroduction-to-python%26urlpath%3Dlab%252Ftree%252Fintroduction-to-python%252Fmodules%252Fintroduction-user-inputs%252Fintroduction-user-inputs-solutions.ipynb%26branch%3Dmain) [![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/anthony-agbay/introduction-to-python/blob/main/modules/introduction-user-inputs/introduction-user-inputs-solutions.ipynb)  Example Problem 1: Calculate the Area of a Circle for a Specified RadiusA user would like to have a script that will calculate the area of a circle when they input a given radius. The cell below contains the basic framework for how this script can be implemented. GoalImplement `calculate_area()` and fulfill the following requirements:1. Validate that the input is a valid floating point number without crashing the script.2. Reprompt the user for a new number if the previous input is not valid.3. `calculate_area()` should return the area of the circle as a float. Additional Notes- The math constant, `pi`, has been provided as an import.- Additional helper functions may or may not be needed.<jupyter_code>%%writefile example_circle_area.py
#!usr/bin/bash python

from math import pi

def calculate_area():
    # Your implementation goes here // You can delete this line
    
##### DO NOT EDIT THE CODE BELOW THIS LINE #####
print("Area: {}".format(calculate_area()))
%run example_circle_area.py<jupyter_output><empty_output><jupyter_text>Solution ValidationTo validate your solution, **pass in "1" as the radius input**. The output should match the text below.     Solution Output Radius: 1Area: 3.141592653589793      Example Problem 2: Use Command-Line Arguments to Describe A Favorite BookA user would like to have a script that will generate a sentence that prints out a description of their favorite book using 1) the title of the book and, 2) the author of the book. The sentence should look like the following:> My favorite book is Dune by <NAME>! GoalImplement `generate_description()` below using command-line arguments and fulfill the following requirements:1. The printed output (other than the book title and author name) should match the example string above.2. You do not need to worry about cases where a user passes an incorrect number of arguments Additional Notes- To pass multi-word strings as a command-line argument, you must wrap the string in quotes. (E.g., `"<NAME>"`)- Additional helper functions may or may not be needed.<jupyter_code>%%writefile example_favorite_book.py
#!usr/bin/bash python

def generate_description():
    # Your implementation goes here // You can delete this line


##### DO NOT EDIT THE CODE BELOW THIS LINE #####
print(generate_description())
%run example_favorite_book.py <REPLACE-WITH-YOUR-ARGUMENTS><jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07609636468551645, 'fraction_numerical': 0.009737449509521061, 'mean_word_length': 5.041394335511982, 'pattern_counts': {'":': 0, '<': 44, '<?xml version=': 0, '>': 46, 'https://': 5, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '614367', 'license': 'MIT', 'n_tokens_mistral': 3866, 'n_tokens_neox': 3694, 'n_words': 1843, 'path': 'modules/introduction-user-inputs/introduction-user-inputs.ipynb', 'repo_name': 'anthony-agbay/introduction-to-python', 'size': '13883'}